Fix issues when using MongoDB for replication
This commit is contained in:
parent
5bf1f76762
commit
908221416e
|
@ -179,7 +179,7 @@ Change.prototype.rectify = function(cb) {
|
||||||
function updateCheckpoint(cb) {
|
function updateCheckpoint(cb) {
|
||||||
change.constructor.getCheckpointModel().current(function(err, checkpoint) {
|
change.constructor.getCheckpointModel().current(function(err, checkpoint) {
|
||||||
if(err) return Change.handleError(err);
|
if(err) return Change.handleError(err);
|
||||||
change.checkpoint = ++checkpoint;
|
change.checkpoint = checkpoint;
|
||||||
cb();
|
cb();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -328,7 +328,7 @@ Change.diff = function(modelName, since, remoteChanges, callback) {
|
||||||
where: {
|
where: {
|
||||||
modelName: modelName,
|
modelName: modelName,
|
||||||
modelId: {inq: modelIds},
|
modelId: {inq: modelIds},
|
||||||
checkpoint: {gt: since}
|
checkpoint: {gte: since}
|
||||||
}
|
}
|
||||||
}, function(err, localChanges) {
|
}, function(err, localChanges) {
|
||||||
if(err) return callback(err);
|
if(err) return callback(err);
|
||||||
|
@ -400,6 +400,15 @@ Change.handleError = function(err) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Change.prototype.getModelId = function() {
|
||||||
|
// TODO(ritch) get rid of the need to create an instance
|
||||||
|
var Model = this.constructor.settings.model;
|
||||||
|
var id = this.modelId;
|
||||||
|
var m = new Model();
|
||||||
|
m.setId(id);
|
||||||
|
return m.getId();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When two changes conflict a conflict is created.
|
* When two changes conflict a conflict is created.
|
||||||
*
|
*
|
||||||
|
|
|
@ -11,8 +11,8 @@ var DataModel = require('../loopback').DataModel
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var properties = {
|
var properties = {
|
||||||
id: {type: Number, generated: true, id: true},
|
seq: {type: Number},
|
||||||
time: {type: Number, generated: true, default: Date.now},
|
time: {type: Date, default: Date},
|
||||||
sourceId: {type: String}
|
sourceId: {type: String}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -45,13 +45,33 @@ var Checkpoint = module.exports = DataModel.extend('Checkpoint', properties, opt
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Checkpoint.current = function(cb) {
|
Checkpoint.current = function(cb) {
|
||||||
|
var Checkpoint = this;
|
||||||
this.find({
|
this.find({
|
||||||
limit: 1,
|
limit: 1,
|
||||||
sort: 'id DESC'
|
sort: 'seq DESC'
|
||||||
}, function(err, checkpoints) {
|
}, function(err, checkpoints) {
|
||||||
if(err) return cb(err);
|
if(err) return cb(err);
|
||||||
var checkpoint = checkpoints[0] || {id: 0};
|
var checkpoint = checkpoints[0];
|
||||||
cb(null, checkpoint.id);
|
if(checkpoint) {
|
||||||
|
cb(null, checkpoint.seq);
|
||||||
|
} else {
|
||||||
|
Checkpoint.create({seq: 0}, function(err, checkpoint) {
|
||||||
|
if(err) return cb(err);
|
||||||
|
cb(null, checkpoint.seq);
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Checkpoint.beforeSave = function(next, model) {
|
||||||
|
if(!model.getId() && model.seq === undefined) {
|
||||||
|
model.constructor.current(function(err, seq) {
|
||||||
|
if(err) return next(err);
|
||||||
|
model.seq = seq + 1;
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var Model = require('./model');
|
var Model = require('./model');
|
||||||
|
var loopback = require('../loopback');
|
||||||
var RemoteObjects = require('strong-remoting');
|
var RemoteObjects = require('strong-remoting');
|
||||||
var assert = require('assert');
|
var assert = require('assert');
|
||||||
var async = require('async');
|
var async = require('async');
|
||||||
|
@ -505,7 +506,7 @@ DataModel.setupRemoting = function() {
|
||||||
{arg: 'remoteChanges', type: 'array', description: 'an array of change objects',
|
{arg: 'remoteChanges', type: 'array', description: 'an array of change objects',
|
||||||
http: {source: 'body'}}
|
http: {source: 'body'}}
|
||||||
],
|
],
|
||||||
returns: {arg: 'deltas', type: 'array', root: true},
|
returns: {arg: 'result', type: 'object', root: true},
|
||||||
http: {verb: 'post', path: '/diff'}
|
http: {verb: 'post', path: '/diff'}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -532,11 +533,29 @@ DataModel.setupRemoting = function() {
|
||||||
http: {verb: 'get', path: '/checkpoint'}
|
http: {verb: 'get', path: '/checkpoint'}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
setRemoting(DataModel.createUpdates, {
|
||||||
|
description: 'Create an update list from a delta list',
|
||||||
|
accepts: {arg: 'deltas', type: 'array', http: {source: 'body'}},
|
||||||
|
returns: {arg: 'updates', type: 'array', root: true},
|
||||||
|
http: {verb: 'post', path: '/create-updates'}
|
||||||
|
});
|
||||||
|
|
||||||
setRemoting(DataModel.bulkUpdate, {
|
setRemoting(DataModel.bulkUpdate, {
|
||||||
description: 'Run multiple updates at once. Note: this is not atomic.',
|
description: 'Run multiple updates at once. Note: this is not atomic.',
|
||||||
accepts: {arg: 'updates', type: 'array'},
|
accepts: {arg: 'updates', type: 'array'},
|
||||||
http: {verb: 'post', path: '/bulk-update'}
|
http: {verb: 'post', path: '/bulk-update'}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
setRemoting(DataModel.rectifyAllChanges, {
|
||||||
|
description: 'Rectify all Model changes.',
|
||||||
|
http: {verb: 'post', path: '/rectify-all'}
|
||||||
|
});
|
||||||
|
|
||||||
|
setRemoting(DataModel.rectifyChange, {
|
||||||
|
description: 'Tell loopback that a change to the model with the given id has occurred.',
|
||||||
|
accepts: {arg: 'id', type: 'any', http: {source: 'path'}},
|
||||||
|
http: {verb: 'post', path: '/:id/rectify-change'}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -568,6 +587,12 @@ DataModel.diff = function(since, remoteChanges, callback) {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
DataModel.changes = function(since, filter, callback) {
|
DataModel.changes = function(since, filter, callback) {
|
||||||
|
if(typeof since === 'function') {
|
||||||
|
filter = {};
|
||||||
|
callback = since;
|
||||||
|
since = -1;
|
||||||
|
}
|
||||||
|
|
||||||
var idName = this.dataSource.idName(this.modelName);
|
var idName = this.dataSource.idName(this.modelName);
|
||||||
var Change = this.getChangeModel();
|
var Change = this.getChangeModel();
|
||||||
var model = this;
|
var model = this;
|
||||||
|
@ -577,14 +602,14 @@ DataModel.changes = function(since, filter, callback) {
|
||||||
filter.where = filter.where || {};
|
filter.where = filter.where || {};
|
||||||
filter.fields[idName] = true;
|
filter.fields[idName] = true;
|
||||||
|
|
||||||
// this whole thing could be optimized a bit more
|
// TODO(ritch) this whole thing could be optimized a bit more
|
||||||
Change.find({
|
Change.find({
|
||||||
checkpoint: {gt: since},
|
checkpoint: {gt: since},
|
||||||
modelName: this.modelName
|
modelName: this.modelName
|
||||||
}, function(err, changes) {
|
}, function(err, changes) {
|
||||||
if(err) return cb(err);
|
if(err) return cb(err);
|
||||||
var ids = changes.map(function(change) {
|
var ids = changes.map(function(change) {
|
||||||
return change.modelId.toString();
|
return change.getModelId();
|
||||||
});
|
});
|
||||||
filter.where[idName] = {inq: ids};
|
filter.where[idName] = {inq: ids};
|
||||||
model.find(filter, function(err, models) {
|
model.find(filter, function(err, models) {
|
||||||
|
@ -709,7 +734,7 @@ DataModel.replicate = function(since, targetModel, options, callback) {
|
||||||
if(diff && diff.deltas && diff.deltas.length) {
|
if(diff && diff.deltas && diff.deltas.length) {
|
||||||
sourceModel.createUpdates(diff.deltas, cb);
|
sourceModel.createUpdates(diff.deltas, cb);
|
||||||
} else {
|
} else {
|
||||||
// done
|
// nothing to replicate
|
||||||
callback(null, []);
|
callback(null, []);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -863,44 +888,72 @@ DataModel.enableChangeTracking = function() {
|
||||||
Change.getCheckpointModel().attachTo(this.dataSource);
|
Change.getCheckpointModel().attachTo(this.dataSource);
|
||||||
|
|
||||||
Model.on('changed', function(obj) {
|
Model.on('changed', function(obj) {
|
||||||
Change.rectifyModelChanges(Model.modelName, [obj.id], function(err) {
|
Model.rectifyChange(obj.getId(), Model.handleChangeError);
|
||||||
if(err) {
|
|
||||||
console.error(Model.modelName + ' Change Tracking Error:');
|
|
||||||
console.error(err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Model.on('deleted', function(obj) {
|
Model.on('deleted', function(id) {
|
||||||
Change.rectifyModelChanges(Model.modelName, [obj.id], function(err) {
|
Model.rectifyChange(id, Model.handleChangeError);
|
||||||
if(err) {
|
|
||||||
console.error(Model.modelName + ' Change Tracking Error:');
|
|
||||||
console.error(err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Model.on('deletedAll', cleanup);
|
Model.on('deletedAll', cleanup);
|
||||||
|
|
||||||
// initial cleanup
|
if(loopback.isServer) {
|
||||||
cleanup();
|
// initial cleanup
|
||||||
|
cleanup();
|
||||||
|
|
||||||
// cleanup
|
// cleanup
|
||||||
setInterval(cleanup, cleanupInterval);
|
setInterval(cleanup, cleanupInterval);
|
||||||
|
|
||||||
function cleanup() {
|
function cleanup() {
|
||||||
Change.rectifyAll(function(err) {
|
Model.rectifyAllChanges(function(err) {
|
||||||
if(err) {
|
if(err) {
|
||||||
console.error(Model.modelName + ' Change Cleanup Error:');
|
console.error(Model.modelName + ' Change Cleanup Error:');
|
||||||
console.error(err);
|
console.error(err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DataModel._defineChangeModel = function() {
|
DataModel._defineChangeModel = function() {
|
||||||
var BaseChangeModel = require('./change');
|
var BaseChangeModel = require('./change');
|
||||||
return this.Change = BaseChangeModel.extend(this.modelName + '-change');
|
return this.Change = BaseChangeModel.extend(this.modelName + '-change',
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
model: this
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
DataModel.rectifyAllChanges = function(callback) {
|
||||||
|
this.getChangeModel().rectifyAll(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle a change error. Override this method in a subclassing model to customize
|
||||||
|
* change error handling.
|
||||||
|
*
|
||||||
|
* @param {Error} err
|
||||||
|
*/
|
||||||
|
|
||||||
|
DataModel.handleChangeError = function(err) {
|
||||||
|
if(err) {
|
||||||
|
console.error(Model.modelName + ' Change Tracking Error:');
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tell loopback that a change to the model with the given id has occurred.
|
||||||
|
*
|
||||||
|
* @param {*} id The id of the model that has changed
|
||||||
|
* @callback {Function} callback
|
||||||
|
* @param {Error} err
|
||||||
|
*/
|
||||||
|
|
||||||
|
DataModel.rectifyChange = function(id, callback) {
|
||||||
|
var Change = this.getChangeModel();
|
||||||
|
Change.rectifyModelChanges(this.modelName, [id], callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
DataModel.setup();
|
DataModel.setup();
|
||||||
|
|
Loading…
Reference in New Issue