0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-01-20 22:42:53 -05:00

🐛 re-run migration for sqlite/pg (#7323)

closes #7192

- add 008 migration
- added script to re-run 006/01
- re-run 006/01 migration for postgres in any timezone (transform formats only)
- re-run 006/01 migration for sqlite (transform formats only)
- rely on format checks for sqlite, do not check server TZ
This commit is contained in:
Katharina Irrgang 2016-09-14 16:52:45 +00:00 committed by Hannah Wolfe
parent b598656ff1
commit 774a662fb2
4 changed files with 288 additions and 19 deletions

View file

@ -39,18 +39,16 @@ module.exports = function transformDatesIntoUTC(options, logger) {
return sequence([
function databaseCheck() {
// we have to change the sqlite format, because it stores dates as integer
if (ServerTimezoneOffset === 0 && config.database.client !== 'sqlite3') {
if (ServerTimezoneOffset === 0 && config.database.client === 'mysql') {
return Promise.reject(new Error('skip'));
}
if (config.database.isPostgreSQL()) {
return Promise.reject(new Error('skip'));
}
if (config.database.client === 'sqlite3') {
_private.noOffset = true;
} else {
} else if (config.database.client === 'mysql') {
_private.noOffset = false;
} else if (config.database.client === 'sqlite3') {
_private.noOffset = true;
}
logger.info(messagePrefix + '(could take a while)...');

View file

@ -0,0 +1,68 @@
var config = require('../../../../config'),
_ = require('lodash'),
models = require(config.paths.corePath + '/server/models'),
transfomDatesIntoUTC = require(config.paths.corePath + '/server/data/migration/fixtures/006/01-transform-dates-into-utc'),
Promise = require('bluebird'),
messagePrefix = 'Fix sqlite/pg format: ',
_private = {};
_private.rerunDateMigration = function rerunDateMigration(options, logger) {
var settingsMigrations, settingsKey = '006/01';
return models.Settings.findOne({key: 'migrations'}, options)
.then(function removeMigrationSettings(result) {
try {
settingsMigrations = JSON.parse(result.attributes.value) || {};
} catch (err) {
return Promise.reject(err);
}
// CASE: migration ran already
if (settingsMigrations.hasOwnProperty(settingsKey)) {
delete settingsMigrations[settingsKey];
return models.Settings.edit({
key: 'migrations',
value: JSON.stringify(settingsMigrations)
}, options);
}
})
.then(function () {
return transfomDatesIntoUTC(options, logger);
});
};
/**
* this migration script is a very special one for people who run their server in UTC and use sqlite3 or run their server in any TZ and use postgres
* 006/01-transform-dates-into-utc had a bug for this case, see what happen because of this bug https://github.com/TryGhost/Ghost/issues/7192
*/
module.exports = function fixSqliteFormat(options, logger) {
// CASE: skip this script when using mysql
if (config.database.client === 'mysql') {
logger.warn(messagePrefix + 'This script only runs, when using sqlite/postgres as database.');
return Promise.resolve();
}
// CASE: database is postgres, server is in ANY TZ, run 006/001 again
// we can't check the format for PG somehow, so we just run the migration again
if (config.database.isPostgreSQL()) {
return _private.rerunDateMigration(options, logger);
}
// CASE: sqlite3 and server is UTC, we check if the date migration was already running
return options.transacting.raw('select created_at from users')
.then(function (users) {
// safety measure
if (!users || !users.length) {
return;
}
// CASE: if type is string and sqlite, then it already has the correct date format
if (!_.isNumber(users[0].created_at)) {
logger.warn(messagePrefix + 'Your dates are in correct format.');
return;
}
return _private.rerunDateMigration(options, logger);
});
};

View file

@ -0,0 +1,3 @@
module.exports = [
require('./01-fix-sqlite-pg-format')
];

View file

@ -19,6 +19,7 @@ var should = require('should'),
fixtures005 = require('../../server/data/migration/fixtures/005'),
fixtures006 = require('../../server/data/migration/fixtures/006'),
fixtures007 = require('../../server/data/migration/fixtures/007'),
fixtures008 = require('../../server/data/migration/fixtures/008'),
sandbox = sinon.sandbox.create();
@ -953,6 +954,8 @@ describe('Fixtures', function () {
});
describe('Tasks:', function () {
var isPostgres = false;
it('should have tasks for 006', function () {
should.exist(fixtures006);
fixtures006.should.be.an.Array().with.lengthOf(1);
@ -965,7 +968,7 @@ describe('Fixtures', function () {
beforeEach(function () {
configUtils.config.database.isPostgreSQL = function () {
return false;
return isPostgres;
};
sandbox.stub(Date.prototype, 'getTimezoneOffset', function () {
@ -973,6 +976,10 @@ describe('Fixtures', function () {
});
});
afterEach(function () {
isPostgres = false;
});
describe('error cases', function () {
before(function () {
serverTimezoneOffset = 0;
@ -996,18 +1003,6 @@ describe('Fixtures', function () {
.catch(done);
});
it('server offset is 0 and postgresql', function (done) {
migrationsSettingsValue = '{}';
configUtils.config.database.client = 'pg';
updateClient({}, loggerStub)
.then(function () {
loggerStub.warn.called.should.be.true();
done();
})
.catch(done);
});
it('migration already ran', function (done) {
migrationsSettingsValue = '{ "006/01": "timestamp" }';
@ -1069,6 +1064,45 @@ describe('Fixtures', function () {
sandbox.stub(api.settings, 'updateSettingsCache').returns(Promise.resolve({}));
});
it('pg: server TZ is UTC, only format is changing', function (done) {
createdAt = moment(1464798678537).toDate();
configUtils.config.database.client = 'pg';
isPostgres = true;
serverTimezoneOffset = 0;
moment(createdAt).format('YYYY-MM-DD HH:mm:ss').should.eql('2016-06-01 16:31:18');
updateClient({}, loggerStub)
.then(function () {
_.each(newModels, function (model) {
moment(model.get('created_at')).format('YYYY-MM-DD HH:mm:ss').should.eql('2016-06-01 16:31:18');
});
migrationsSettingsWasUpdated.should.eql(true);
done();
})
.catch(done);
});
it('pg: server TZ is non UTC, only format is changing', function (done) {
createdAt = moment(1464798678537).toDate();
configUtils.config.database.client = 'pg';
isPostgres = true;
moment(createdAt).format('YYYY-MM-DD HH:mm:ss').should.eql('2016-06-01 16:31:18');
updateClient({}, loggerStub)
.then(function () {
_.each(newModels, function (model) {
moment(model.get('created_at')).format('YYYY-MM-DD HH:mm:ss').should.eql('2016-06-01 16:31:18');
});
migrationsSettingsWasUpdated.should.eql(true);
done();
})
.catch(done);
});
it('server offset is 0 and sqlite', function (done) {
serverTimezoneOffset = 0;
createdAt = moment(1464798678537).toDate();
@ -1204,6 +1238,172 @@ describe('Fixtures', function () {
});
});
});
describe('Update to 008', function () {
it('should call all the 008 fixture upgrades', function (done) {
// Setup
// Create a new stub, this will replace sequence, so that db calls don't actually get run
var sequenceStub = sandbox.stub(),
sequenceReset = update.__set__('sequence', sequenceStub),
tasks = versioning.getUpdateFixturesTasks('008', loggerStub);
sequenceStub.returns(Promise.resolve([]));
update(tasks, loggerStub, {transacting: transactionStub}).then(function (result) {
should.exist(result);
loggerStub.info.calledOnce.should.be.true();
loggerStub.warn.called.should.be.false();
sequenceStub.calledOnce.should.be.true();
sequenceStub.firstCall.calledWith(sinon.match.array, sinon.match.object, loggerStub).should.be.true();
sequenceStub.firstCall.args[0].should.be.an.Array().with.lengthOf(1);
sequenceStub.firstCall.args[0][0].should.be.a.Function().with.property('name', 'fixSqliteFormat');
// Reset
sequenceReset();
done();
}).catch(done);
});
describe('Tasks:', function () {
it('should have tasks for 008', function () {
should.exist(fixtures008);
fixtures008.should.be.an.Array().with.lengthOf(1);
});
describe('01-fix-sqlite-pg-format', function () {
var updateClient = rewire('../../server/data/migration/fixtures/008/01-fix-sqlite-pg-format'),
serverTimezoneOffset = 60,
transfomDatesIntoUTCStub, rawStub, isPostgres = false, isPostgreSQLWasCalled = false;
beforeEach(function () {
configUtils.config.database.isPostgreSQL = function () {
isPostgreSQLWasCalled = true;
return isPostgres;
};
sandbox.stub(Date.prototype, 'getTimezoneOffset', function () {
return serverTimezoneOffset;
});
});
afterEach(function () {
serverTimezoneOffset = 60;
isPostgres = false;
isPostgreSQLWasCalled = false;
});
describe('success', function () {
beforeEach(function () {
sandbox.stub(models.Settings, 'findOne', function (options) {
if (options.key === 'migrations') {
return Promise.resolve({attributes: {value: '{"006/01":"2016-09-05T12:39:11Z", "005/02": "2015-09-05T12:39:11Z"}'}});
}
return Promise.resolve();
});
sandbox.stub(models.Settings, 'edit', function (data) {
data.key.should.eql('migrations');
data.value.should.eql('{"005/02":"2015-09-05T12:39:11Z"}');
return Promise.resolve();
});
transfomDatesIntoUTCStub = sandbox.stub().returns(Promise.resolve());
updateClient.__set__('transfomDatesIntoUTC', transfomDatesIntoUTCStub);
});
it('sqlite and server TZ is UTC: date format is integer', function (done) {
serverTimezoneOffset = 0;
configUtils.config.database.client = 'sqlite3';
rawStub = sandbox.stub().returns(Promise.resolve([{created_at: Date.now()}]));
updateClient({transacting: {raw: rawStub}}, loggerStub)
.then(function () {
models.Settings.edit.callCount.should.eql(1);
models.Settings.findOne.callCount.should.eql(1);
transfomDatesIntoUTCStub.callCount.should.eql(1);
done();
})
.catch(done);
});
it('postgres and server TZ is UTC', function (done) {
serverTimezoneOffset = 0;
configUtils.config.database.client = 'pg';
isPostgres = true;
updateClient({}, loggerStub)
.then(function () {
isPostgreSQLWasCalled.should.eql(true);
models.Settings.edit.callCount.should.eql(1);
models.Settings.findOne.callCount.should.eql(1);
transfomDatesIntoUTCStub.callCount.should.eql(1);
done();
})
.catch(done);
});
it('postgres and server TZ is not UTC', function (done) {
configUtils.config.database.client = 'pg';
isPostgres = true;
updateClient({}, loggerStub)
.then(function () {
isPostgreSQLWasCalled.should.eql(true);
models.Settings.edit.callCount.should.eql(1);
models.Settings.findOne.callCount.should.eql(1);
transfomDatesIntoUTCStub.callCount.should.eql(1);
done();
})
.catch(done);
});
});
describe('error', function () {
it('skip mysql', function (done) {
configUtils.config.database.client = 'mysql';
updateClient({}, loggerStub)
.then(function () {
loggerStub.warn.called.should.be.true();
done();
})
.catch(done);
});
it('skip sqlite and non UTC server timezone', function (done) {
configUtils.config.database.client = 'sqlite3';
rawStub = sandbox.stub().returns(Promise.resolve([{created_at: moment().format('YYYY-MM-DD HH:mm:ss')}]));
updateClient({transacting: {raw:rawStub}}, loggerStub)
.then(function () {
loggerStub.warn.called.should.be.true();
done();
})
.catch(done);
});
it('skip sqlite with UTC server timezone, but correct format', function (done) {
configUtils.config.database.client = 'sqlite3';
serverTimezoneOffset = 0;
rawStub = sandbox.stub().returns(Promise.resolve([{created_at: moment().format('YYYY-MM-DD HH:mm:ss')}]));
updateClient({transacting: {raw: rawStub}}, loggerStub)
.then(function () {
loggerStub.warn.called.should.be.true();
done();
})
.catch(done);
});
});
});
});
});
});
describe('Populate fixtures', function () {