Skip to content

Instantly share code, notes, and snippets.

@ingorammer
Created January 1, 2014 13:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ingorammer/8208056 to your computer and use it in GitHub Desktop.
Save ingorammer/8208056 to your computer and use it in GitHub Desktop.
(Addendum to https://github.com/daleharvey/pouchdb/issues/1201) I've created this gist to detail some PouchDB Local->Remote issues which I've noticed with continuous replication when resolving conflicts (i.e. when remove()-ing the conflicting revisions after performing some app-specific conflict resolution magic).
For continuous replication, the following POSTs are sent to _revs_diff and _bulk_docs:
POST to _revs_diff:
{
"foo": ["3-c"]
}
RESPONSE:
{
"foo": {
"missing": ["3-c"],
"possible_ancestors": ["2-b", "2-c"]
}
}
Resulting in the following POST to _bulk_docs:
{
"docs": [{
"value": "db1",
"_id": "foo",
"_rev": "3-c",
"_revisions": {
"start": 2,
"ids": ["c", "a"]
}
}],
"new_edits": false
}
For manual replication, the following POSTs are sent to _revs_diff and _bulk_docs:
POST to _revs_diff:
{
"foo": ["3-5e51174f2ba73f498b3689beb8364276", "3-c"]
}
RESPONSE:
{
"foo": {
"missing": ["3-5e51174f2ba73f498b3689beb8364276", "3-c"],
"possible_ancestors": ["2-b", "2-c"]
}
}
Resulting in the following POST to _bulk_docs:
{
"docs": [{
"_id": "foo",
"_rev": "3-5e51174f2ba73f498b3689beb8364276",
"_deleted": true,
"_revisions": {
"start": 3,
"ids": ["5e51174f2ba73f498b3689beb8364276", "b", "a"]
}
}, {
"value": "db1",
"_id": "foo",
"_rev": "3-c",
"_revisions": {
"start": 2,
"ids": ["c", "a"]
}
}],
"new_edits": false
}
"use strict";
var adapters = [
['local-1', 'http-1'],
['http-1', 'local-1']
];
if (typeof module !== undefined && module.exports) {
var PouchDB = require('../lib');
var testUtils = require('./test.utils.js');
}
adapters.map(function (adapters) {
QUnit.module('continuous replication with conflict resolution: ' + adapters[0] + ':' + adapters[1], {
setup: function () {
this.name = testUtils.generateAdapterUrl(adapters[0]);
this.remote = testUtils.generateAdapterUrl(adapters[1]);
PouchDB.enableAllDbs = true;
},
teardown: testUtils.cleanupTestDatabases
});
asyncTest('Testing conflict resolution with subsequent manual unidirectional replication', function () {
// we indeed needed replication to create failing test here!
testUtils.initDBPair(this.name, this.remote, function (db1, db2) {
var doc = {
_id: "foo",
_rev: "1-a",
value: "generic"
};
db1.put(doc, {new_edits: false}, function (err, res) {
db2.put(doc, {new_edits: false}, function (err, res) {
testUtils.putAfter(db2, {_id: "foo", _rev: "2-b", value: "db2"}, "1-a", function (err, res) {
testUtils.putAfter(db1, {_id: "foo", _rev: "2-c", value: "whatever"}, "1-a", function (err, res) {
testUtils.putAfter(db1, {_id: "foo", _rev: "3-c", value: "db1"}, "2-c", function (err, res) {
db1.get("foo", {conflicts: true}, function (err, doc) {
ok(doc.value === "db1", "db1 has correct value (get)");
ok(!doc._conflicts || doc._conflicts.length === 0, "db1 has no conflicts before replication");
db2.get("foo", function (err, doc) {
ok(doc.value === "db2", "db2 has correct value (get)");
ok(!doc._conflicts || doc._conflicts.length === 0, "db2 sees no conflicts before replication");
PouchDB.replicate(db1, db2, function () {
PouchDB.replicate(db2, db1, function () {
db1.get("foo", {conflicts: true}, function (err, doc) {
ok(doc.value === "db1", "db1 has correct value (get after replication)");
ok(doc._conflicts && doc._conflicts.length === 1, "db1 sees conflict after replication");
var conflictRev = doc._conflicts[0];
db2.get("foo", {conflicts: true}, function (err, doc) {
ok(doc.value === "db1", "db2 has correct value (get after replication)");
ok(doc._conflicts && doc._conflicts.length === 1, "db2 sees conflict after replication");
db1.remove({_id: "foo", _rev: conflictRev}, function (err, res) {
ok(res.ok, "Conflicting document removed on db1");
db1.get("foo", {conflicts: true}, function (err, doc) {
ok(!doc._conflicts || doc._conflicts.length === 0, "db1 sees no more conflicts after removing it");
PouchDB.replicate(db1, db2, function () {
db1.get("foo", {conflicts: true}, function (err, doc) {
ok(doc.value === "db1", "db1 has correct value (get after replication)");
ok(!doc._conflicts || doc._conflicts.length === 0, "db1 sees no conflict after replication");
console.log("DB1 document");
console.dir(doc);
db2.get("foo", {conflicts: true}, function (err, doc) {
ok(doc.value === "db1", "db2 has correct value (get after replication)");
ok(!doc._conflicts || doc._conflicts.length === 0, "db2 sees no conflict after replication");
console.log("DB2 document");
console.dir(doc);
start();
});
});
});
});
});
});
});
});
});
});
});
});
});
});
});
});
});
});
asyncTest('Testing conflict resolution with continuous replication', function () {
// we indeed needed replication to create failing test here!
testUtils.initDBPair(this.name, this.remote, function (db1, db2) {
var doc = {
_id: "foo",
_rev: "1-a",
value: "generic"
};
db1.put(doc, {new_edits: false}, function (err, res) {
db2.put(doc, {new_edits: false}, function (err, res) {
testUtils.putAfter(db2, {_id: "foo", _rev: "2-b", value: "db2"}, "1-a", function (err, res) {
testUtils.putAfter(db1, {_id: "foo", _rev: "2-c", value: "whatever"}, "1-a", function (err, res) {
testUtils.putAfter(db1, {_id: "foo", _rev: "3-c", value: "db1"}, "2-c", function (err, res) {
db1.get("foo", {conflicts: true}, function (err, doc) {
ok(doc.value === "db1", "db1 has correct value (get)");
ok(!doc._conflicts || doc._conflicts.length === 0, "db1 has no conflicts before replication");
db2.get("foo", function (err, doc) {
ok(doc.value === "db2", "db2 has correct value (get)");
ok(!doc._conflicts || doc._conflicts.length === 0, "db2 sees no conflicts before replication");
PouchDB.replicate(db1, db2, function () {
PouchDB.replicate(db2, db1, function () {
db1.get("foo", {conflicts: true}, function (err, doc) {
ok(doc.value === "db1", "db1 has correct value (get after replication)");
ok(doc._conflicts && doc._conflicts.length === 1, "db1 sees conflict after replication");
var conflictRev = doc._conflicts[0];
console.log("LOCAL BEFORE SYNC");
console.dir(doc);
db2.get("foo", {conflicts: true}, function (err, doc) {
console.log("REMOTE BEFORE SYNC");
console.dir(doc);
ok(doc.value === "db1", "db2 has correct value (get after replication)");
ok(doc._conflicts && doc._conflicts.length === 1, "db2 sees conflict after replication");
var initialChangeComplete = false;
function continueAfterFirstSync() {
console.log("Deleting");
db1.remove({_id: "foo", _rev: conflictRev}, function (err, res) {
ok(res.ok, "Conflicting document removed on db1");
db1.get("foo", {conflicts: true}, function (err, doc) {
ok(!doc._conflicts || doc._conflicts.length === 0, "db1 sees no more conflicts after removing it");
setTimeout(function () {
console.log("Resuming after wait ...")
db2.get("foo", {conflicts: true}, function (err, doc) {
ok(doc.value === "db1", "db2 has correct value (get after replication)");
ok(!doc._conflicts || doc._conflicts.length === 0, "db2 sees no conflict after replication");
console.log("DB2 document");
console.dir(doc);
start();
});
}, 1000);
});
});
}
PouchDB.replicate(db1, db2, {
continuous: true,
onChange: function (status) {
console.log("==== SYNC CHANGES ====");
console.dir(status);
}});
// continue with change watching after a couple of milliseconds to allow for first sync for local->remote ...
setTimeout(function () {
continueAfterFirstSync();
}, 500);
});
});
});
});
});
});
});
});
});
});
});
});
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment