Commit 450a8e3e authored by Tristan Cavelier's avatar Tristan Cavelier

replicatestorage improved + jio tests

parent 1edc8e48
...@@ -21,6 +21,7 @@ jIO.addStorageType('replicaterevision', function (spec, my) { ...@@ -21,6 +21,7 @@ jIO.addStorageType('replicaterevision', function (spec, my) {
priv.storage_list_key = "storage_list"; priv.storage_list_key = "storage_list";
priv.storage_list = spec[priv.storage_list_key]; priv.storage_list = spec[priv.storage_list_key];
my.env = my.env || spec.env || {}; my.env = my.env || spec.env || {};
priv.emptyFunction = function () {};
that.specToStore = function () { that.specToStore = function () {
var o = {}; var o = {};
...@@ -52,13 +53,34 @@ jIO.addStorageType('replicaterevision', function (spec, my) { ...@@ -52,13 +53,34 @@ jIO.addStorageType('replicaterevision', function (spec, my) {
}; };
/** /**
* Generates a hash code of a string * Create an array containing dictionnary keys
* @method hashCode * @method dictKeys2Array
* @param {object} dict The object to convert
* @return {array} The array of keys
*/
priv.dictKeys2Array = function (dict) {
var k, newlist = [];
for (k in dict) {
if (dict.hasOwnProperty(k)) {
newlist.push(k);
}
}
return newlist;
};
/**
* Generates the next revision
* @method generateNextRevision
* @param {number|string} previous_revision The previous revision
* @param {string} docid The document id
* @return {string} The next revision * @return {string} The next revision
*/ */
priv.getNextRevision = function (docid) { priv.generateNextRevision = function (previous_revision, docid) {
my.env[docid].id += 1; my.env[docid].id += 1;
return my.env[docid].id.toString(); if (typeof previous_revision === "string") {
previous_revision = parseInt(previous_revision.split("-")[0], 10);
}
return (previous_revision + 1) + "-" + my.env[docid].id.toString();
}; };
/** /**
...@@ -68,7 +90,7 @@ jIO.addStorageType('replicaterevision', function (spec, my) { ...@@ -68,7 +90,7 @@ jIO.addStorageType('replicaterevision', function (spec, my) {
* @return {boolean} True if ok, else false * @return {boolean} True if ok, else false
*/ */
priv.checkRevisionFormat = function (revision) { priv.checkRevisionFormat = function (revision) {
return (/^[0-9a-zA-Z_]+$/.test(revision)); return (/^[0-9]+-[0-9a-zA-Z_]+$/.test(revision));
}; };
/** /**
...@@ -100,10 +122,10 @@ jIO.addStorageType('replicaterevision', function (spec, my) { ...@@ -100,10 +122,10 @@ jIO.addStorageType('replicaterevision', function (spec, my) {
}; };
/** /**
* Clones an object * Clones an object in deep (without functions)
* @method cloneObject * @method clone
* @param {object} object The object to clone * @param {any} object The object to clone
* @return {object} The cloned object * @return {any} The cloned object
*/ */
priv.clone = function (object) { priv.clone = function (object) {
var tmp = JSON.stringify(object); var tmp = JSON.stringify(object);
...@@ -113,8 +135,22 @@ jIO.addStorageType('replicaterevision', function (spec, my) { ...@@ -113,8 +135,22 @@ jIO.addStorageType('replicaterevision', function (spec, my) {
return JSON.parse(tmp); return JSON.parse(tmp);
}; };
/**
* Like addJob but also return the method and the index of the storage
* @method send
* @param {string} method The request method
* @param {number} index The storage index
* @param {object} doc The document object
* @param {object} option The request object
* @param {function} callback The callback. Parameters:
* - {string} The request method
* - {number} The storage index
* - {object} The error object
* - {object} The response object
*/
priv.send = function (method, index, doc, option, callback) { priv.send = function (method, index, doc, option, callback) {
var wrapped_callback_success, wrapped_callback_error; var wrapped_callback_success, wrapped_callback_error;
callback = callback || priv.emptyFunction;
wrapped_callback_success = function (response) { wrapped_callback_success = function (response) {
callback(method, index, undefined, response); callback(method, index, undefined, response);
}; };
...@@ -131,6 +167,19 @@ jIO.addStorageType('replicaterevision', function (spec, my) { ...@@ -131,6 +167,19 @@ jIO.addStorageType('replicaterevision', function (spec, my) {
); );
}; };
/**
* Use "send" method to all sub storages.
* Calling "callback" for each storage response.
* @method sendToAll
* @param {string} method The request method
* @param {object} doc The document object
* @param {object} option The request option
* @param {function} callback The callback. Parameters:
* - {string} The request method
* - {number} The storage index
* - {object} The error object
* - {object} The response object
*/
priv.sendToAll = function (method, doc, option, callback) { priv.sendToAll = function (method, doc, option, callback) {
var i; var i;
for (i = 0; i < priv.storage_list.length; i += 1) { for (i = 0; i < priv.storage_list.length; i += 1) {
...@@ -170,30 +219,20 @@ jIO.addStorageType('replicaterevision', function (spec, my) { ...@@ -170,30 +219,20 @@ jIO.addStorageType('replicaterevision', function (spec, my) {
} else { } else {
doc_env = priv.initEnv(doc._id); doc_env = priv.initEnv(doc._id);
} }
if (!priv.post_allowed && !doc_env.my_revisions[doc._rev]) { my_rev = priv.generateNextRevision(doc._rev || 0, doc._id);
that.error({
"status": 409,
"statusText": "Conflict",
"error": "conflict",
"message": "Cannot update a document",
"reason": "Document update conflict"
});
return;
}
my_rev = priv.getNextRevision(doc._id);
functions.sendDocument(); functions.sendDocument();
}; };
functions.sendDocument = function () { functions.sendDocument = function () {
var i; var i, cloned_doc;
for (i = 0; i < priv.storage_list.length; i += 1) { for (i = 0; i < priv.storage_list.length; i += 1) {
var cloned_doc = priv.clone(doc); cloned_doc = priv.clone(doc);
if (typeof cloned_doc._rev === "string" && if (typeof cloned_doc._rev === "string" &&
doc_env.my_revisions[cloned_doc._rev] !== undefined) { doc_env.my_revisions[cloned_doc._rev] !== undefined) {
cloned_doc._rev = doc_env.my_revisions[cloned_doc._rev][i]; cloned_doc._rev = doc_env.my_revisions[cloned_doc._rev][i];
} }
priv.send( priv.send(
doc_env.last_revisions[i] === "unique_" + i || doc_env.last_revisions[i] === "unique_" + i ||
cloned_doc._rev !== undefined ? "put" : "post", priv.put_only ? "put" : "post",
i, i,
cloned_doc, cloned_doc,
command.cloneOption(), command.cloneOption(),
...@@ -213,7 +252,7 @@ jIO.addStorageType('replicaterevision', function (spec, my) { ...@@ -213,7 +252,7 @@ jIO.addStorageType('replicaterevision', function (spec, my) {
return; return;
} }
} }
priv.updateEnv(doc_env, my_rev, index, undefined); priv.updateEnv(doc_env, my_rev, index, null);
functions.error(err); functions.error(err);
return; return;
} }
...@@ -229,62 +268,142 @@ jIO.addStorageType('replicaterevision', function (spec, my) { ...@@ -229,62 +268,142 @@ jIO.addStorageType('replicaterevision', function (spec, my) {
functions.success = function (response) { functions.success = function (response) {
// can be called once // can be called once
that.success(response); that.success(response);
functions.success = function () {}; functions.success = priv.emptyFunction;
}; };
functions.error_count = 0; functions.error_count = 0;
functions.error = function (err) { functions.error = function (err) {
functions.error_count += 1; functions.error_count += 1;
if (functions.error_count === priv.storage_list.length) { if (functions.error_count === priv.storage_list.length) {
that.error(err); that.error(err);
functions.error = function () {}; functions.error = priv.emptyFunction;
} }
}; };
functions.begin(); functions.begin();
}; };
/** /**
* Get the document metadata from all sub storages, get the fastest. * Put the document metadata to all sub storages
* @method put
* @param {object} command The JIO command
*/
that.put = function (command) {
priv.put_only = true;
that.post(command);
};
/**
* Put an attachment to a document to all sub storages
* @method putAttachment
* @param {object} command The JIO command
*/
// that.putAttachment = function (command) {
// };
/**
* Get the document or attachment from all sub storages, get the fastest.
* @method get * @method get
* @param {object} command The JIO command * @param {object} command The JIO command
*/ */
that.get = function (command) { that.get = function (command) {
var functions = {}, doc_env, docid, my_rev, waiting_response; var functions = {}, doc_env, docid, my_rev, revs_array = [];
functions.begin = function () { functions.begin = function () {
docid = command.cloneDocId(); var i, option;
docid = command.getDocId();
doc_env = my.env[doc._id]; doc_env = my.env[docid];
if (!doc_env || !doc_env.id) { if (!doc_env || !doc_env.id) {
// document environment is not set // document environment is not set
doc_env = priv.initEnv(doc._id); doc_env = priv.initEnv(docid);
}
// document environment is set now
revs_array.length = priv.storage_list.length;
option = command.cloneOption() || {};
my_rev = option.rev;
if (my_rev) {
functions.update_env = false;
}
for (i = 0; i < priv.storage_list.length; i += 1) {
// request all sub storages
if (doc_env.my_revisions[my_rev]) {
// if my_rev exist, convert it to distant revision
option.rev = doc_env.my_revisions[my_rev][i];
}
priv.send("get", i, docid, priv.clone(option), functions.callback);
} }
my_rev = priv.getNextRevision(doc._id);
priv.sendToAll("get", docid, command.cloneOption(), functions.callback);
}; };
functions.update_env = true;
functions.callback = function (method, index, err, response) { functions.callback = function (method, index, err, response) {
if (err) { if (err) {
priv.updateEnv(doc_env, my_rev, index, undefined); revs_array[index] = null;
functions.error(err); functions.error(err);
return; return;
} }
priv.updateEnv( doc_env.last_revisions[index] = response._rev || "unique_" + index;
doc_env, revs_array[index] = response._rev || "unique_" + index;
my_rev, if (doc_env.distant_revisions[response._rev || "unique_" + index]) {
index, // the document revision is already known
response._rev || "unique_" + index if (functions.update_env === true) {
); my_rev = doc_env.distant_revisions[response._rev ||
"unique_" + index];
}
} else {
// the document revision is unknown
if (functions.update_env === true) {
my_rev = priv.generateNextRevision(0, docid);
doc_env.my_revisions[my_rev] = revs_array;
doc_env.distant_revisions[response._rev || "unique_" + index] =
my_rev;
}
functions.update_env = false;
}
response._rev = my_rev; response._rev = my_rev;
functions.success(response); functions.success(response);
}; };
functions.success = function (response) { functions.success = function (response) {
var i, start, tmp, tmp_object;
functions.success = priv.emptyFunction;
if (doc_env.my_revisions[my_rev]) {
// this was not a specific revision
// we can convert revisions recieved by the sub storage
if (response._conflicts) {
// convert conflicting revisions to replicate revisions
tmp_object = {};
for (i = 0; i < response._conflicts.length; i += 1) {
tmp_object[doc_env.distant_revisions[response._conflicts[i]] ||
response._conflicts[i]] = true;
}
response._conflicts = priv.dictKeys2Array(tmp_object);
}
if (response._revisions) {
// convert revisions history to replicate revisions
tmp_object = {};
start = response._revisions.start;
for (i = 0; i < response._revisions.ids.length; i += 1, start -= 1) {
tmp = doc_env.distant_revisions[start + "-" + response._revisions.ids[i]];
if (tmp) {
response._revisions.ids[i] = tmp.split("-").slice(1).join("-");
}
}
}
if (response._revs_info) {
// convert revs info to replicate revisions
for (i = 0; i < response._revs_info.length; i += 1) {
tmp = doc_env.distant_revisions[response._revs_info[i].rev];
if (tmp) {
response._revs_info[i].rev = tmp;
}
}
}
}
that.success(response); that.success(response);
functions.success = function () {};
}; };
functions.error_count = 0; functions.error_count = 0;
functions.error = function (err) { functions.error = function (err) {
functions.error_count += 1; functions.error_count += 1;
if (functions.error_count === priv.storage_list.length) { if (functions.error_count === priv.storage_list.length) {
that.error(err); that.error(err);
functions.error = function () {}; functions.error = priv.emptyFunction;
} }
}; };
functions.begin(); functions.begin();
......
...@@ -2004,7 +2004,6 @@ module ("JIO Replicate Revision Storage"); ...@@ -2004,7 +2004,6 @@ module ("JIO Replicate Revision Storage");
// post a new document without id // post a new document without id
o.doc = {"title": "post document without id"}; o.doc = {"title": "post document without id"};
o.revision = {"start": 0, "ids": []};
o.spy(o, "status", undefined, "Post document (without id)"); o.spy(o, "status", undefined, "Post document (without id)");
o.jio.post(o.doc, function (err, response) { o.jio.post(o.doc, function (err, response) {
o.f.apply(arguments); o.f.apply(arguments);
...@@ -2021,7 +2020,8 @@ module ("JIO Replicate Revision Storage"); ...@@ -2021,7 +2020,8 @@ module ("JIO Replicate Revision Storage");
// check document // check document
o.doc._id = o.uuid; o.doc._id = o.uuid;
o.rev = "1"; o.revision = {"start": 0, "ids": []};
o.rev = "1-1";
o.local_rev = "1-" + generateRevisionHash(o.doc, o.revision); o.local_rev = "1-" + generateRevisionHash(o.doc, o.revision);
o.leavesAction(function (storage_description, param) { o.leavesAction(function (storage_description, param) {
var suffix = "", doc = clone(o.doc); var suffix = "", doc = clone(o.doc);
...@@ -2037,16 +2037,33 @@ module ("JIO Replicate Revision Storage"); ...@@ -2037,16 +2037,33 @@ module ("JIO Replicate Revision Storage");
); );
}); });
// get the post document without revision
o.spy(o, "value", {
"_id": o.uuid,
"title": "post document without id",
"_rev": "1-1",
"_revisions": {"start": 1, "ids": ["1"]},
"_revs_info": [{"rev": "1-1", "status": "available"}]
}, "Get the previous document (without revision)");
o.jio.get(o.uuid, {
"conflicts": true,
"revs": true,
"revs_info": true
}, o.f);
o.tick(o);
// post a new document with id // post a new document with id
o.doc = {"_id": "post1", "title": "post new doc with id"}; o.doc = {"_id": "doc1", "title": "post new doc with id"};
o.rev = "1" o.spy(o, "value", {"ok": true, "id": "doc1", "rev": o.rev},
o.spy(o, "value", {"ok": true, "id": "post1", "rev": o.rev},
"Post document (with id)"); "Post document (with id)");
o.jio.post(o.doc, o.f); o.jio.post(o.doc, o.f);
o.tick(o); o.tick(o);
// check document // check document
o.local_rev = "1-" + generateRevisionHash(o.doc, o.revision); o.local_rev_hash = generateRevisionHash(o.doc, o.revision);
o.local_rev = "1-" + o.local_rev_hash;
o.specific_rev_hash = o.local_rev_hash;
o.specific_rev = o.local_rev;
o.leavesAction(function (storage_description, param) { o.leavesAction(function (storage_description, param) {
var suffix = "", doc = clone(o.doc); var suffix = "", doc = clone(o.doc);
if (param.revision) { if (param.revision) {
...@@ -2055,15 +2072,30 @@ module ("JIO Replicate Revision Storage"); ...@@ -2055,15 +2072,30 @@ module ("JIO Replicate Revision Storage");
} }
deepEqual( deepEqual(
localstorage.getItem(generateLocalPath(storage_description) + localstorage.getItem(generateLocalPath(storage_description) +
"/post1" + suffix), "/doc1" + suffix),
doc, "Check document" doc, "Check document"
); );
}); });
// get the post document without revision
o.spy(o, "value", {
"_id": "doc1",
"title": "post new doc with id",
"_rev": "1-1",
"_revisions": {"start": 1, "ids": ["1"]},
"_revs_info": [{"rev": "1-1", "status": "available"}]
}, "Get the previous document (without revision)");
o.jio.get("doc1", {
"conflicts": true,
"revs": true,
"revs_info": true
}, o.f);
o.tick(o);
// post same document without revision // post same document without revision
o.doc = {"_id": "post1", "title": "post same document without revision"}; o.doc = {"_id": "doc1", "title": "post same document without revision"};
o.rev = "2"; o.rev = "1-2";
o.spy(o, "value", {"ok": true, "id": "post1", "rev": o.rev}, o.spy(o, "value", {"ok": true, "id": "doc1", "rev": o.rev},
"Post same document (without revision)"); "Post same document (without revision)");
o.jio.post(o.doc, o.f); o.jio.post(o.doc, o.f);
o.tick(o); o.tick(o);
...@@ -2078,15 +2110,15 @@ module ("JIO Replicate Revision Storage"); ...@@ -2078,15 +2110,15 @@ module ("JIO Replicate Revision Storage");
} }
deepEqual( deepEqual(
localstorage.getItem(generateLocalPath(storage_description) + localstorage.getItem(generateLocalPath(storage_description) +
"/post1" + suffix), "/doc1" + suffix),
doc, "Check document" doc, "Check document"
); );
}); });
// post a new revision // post a new revision
o.doc = {"_id": "post1", "title": "post new revision", "_rev": o.rev}; o.doc = {"_id": "doc1", "title": "post new revision", "_rev": o.rev};
o.rev = "3"; o.rev = "2-3";
o.spy(o, "value", {"ok": true, "id": "post1", "rev": o.rev}, o.spy(o, "value", {"ok": true, "id": "doc1", "rev": o.rev},
"Post document (with revision)"); "Post document (with revision)");
o.jio.post(o.doc, o.f); o.jio.post(o.doc, o.f);
o.tick(o); o.tick(o);
...@@ -2096,6 +2128,7 @@ module ("JIO Replicate Revision Storage"); ...@@ -2096,6 +2128,7 @@ module ("JIO Replicate Revision Storage");
o.revision.ids.unshift(o.local_rev.split("-").slice(1).join("-")); o.revision.ids.unshift(o.local_rev.split("-").slice(1).join("-"));
o.doc._rev = o.local_rev; o.doc._rev = o.local_rev;
o.local_rev = "2-" + generateRevisionHash(o.doc, o.revision); o.local_rev = "2-" + generateRevisionHash(o.doc, o.revision);
o.specific_rev_conflict = o.local_rev;
o.leavesAction(function (storage_description, param) { o.leavesAction(function (storage_description, param) {
var suffix = "", doc = clone(o.doc); var suffix = "", doc = clone(o.doc);
delete doc._rev; delete doc._rev;
...@@ -2105,25 +2138,50 @@ module ("JIO Replicate Revision Storage"); ...@@ -2105,25 +2138,50 @@ module ("JIO Replicate Revision Storage");
} }
deepEqual( deepEqual(
localstorage.getItem(generateLocalPath(storage_description) + localstorage.getItem(generateLocalPath(storage_description) +
"/post1" + suffix), "/doc1" + suffix),
doc, "Check document" doc, "Check document"
); );
}); });
// get the post document with revision
o.spy(o, "value", {
"_id": "doc1",
"title": "post same document without revision",
"_rev": "1-2",
"_revisions": {"start": 1, "ids": ["2"]},
"_revs_info": [{"rev": "1-2", "status": "available"}],
"_conflicts": ["1-1"]
}, "Get the previous document (with revision)");
o.jio.get("doc1", {
"conflicts": true,
"revs": true,
"revs_info": true,
"rev": "1-2"
}, o.f);
o.tick(o);
// get the post document with specific revision
console.log(o.specific_rev);
o.spy(o, "value", {
"_id": "doc1",
"title": "post new doc with id",
"_rev": o.specific_rev,
"_revisions": {"start": 1, "ids": [o.specific_rev_hash]},
"_revs_info": [{"rev": o.specific_rev, "status": "available"}],
"_conflicts": [o.specific_rev_conflict]
}, "Get a previous document (with local storage revision)");
o.jio.get("doc1", {
"conflicts": true,
"revs": true,
"revs_info": true,
"rev": o.specific_rev
}, o.f);
o.tick(o);
o.jio.stop(); o.jio.stop();
}; };
test ("[Local Storage] Scenario", function () {
testReplicateRevisionStorageGenerator(this, {
"type": "replicaterevision",
"storage_list": [{
"type": "local",
"username": "ureploc",
"application_name": "areploc"
}]
});
});
test ("[Revision + Local Storage] Scenario", function () { test ("[Revision + Local Storage] Scenario", function () {
testReplicateRevisionStorageGenerator(this, { testReplicateRevisionStorageGenerator(this, {
"type": "replicaterevision", "type": "replicaterevision",
...@@ -2137,20 +2195,23 @@ module ("JIO Replicate Revision Storage"); ...@@ -2137,20 +2195,23 @@ module ("JIO Replicate Revision Storage");
}] }]
}); });
}); });
test ("[Revision + Local Storage, Local Storage] Scenario", function () { test ("2x [Revision + Local Storage] Scenario", function () {
testReplicateRevisionStorageGenerator(this, { testReplicateRevisionStorageGenerator(this, {
"type": "replicaterevision", "type": "replicaterevision",
"storage_list": [{ "storage_list": [{
"type": "revision", "type": "revision",
"sub_storage": { "sub_storage": {
"type": "local", "type": "local",
"username": "ureprevlocloc", "username": "ureprevlocloc1",
"application_name": "areprevlocloc" "application_name": "areprevloc1"
} }
},{ }, {
"type": "revision",
"sub_storage": {
"type": "local", "type": "local",
"username": "ureprevlocloc2", "username": "ureprevlocloc2",
"application_name": "areprevlocloc2" "application_name": "areprevloc2"
}
}] }]
}); });
}); });
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment