Commit 69da7ee5 authored by Bryan Kaperick's avatar Bryan Kaperick

Added functionality to .get which allows the user to step backward through the...

Added functionality to .get which allows the user to step backward through the absolute revision history of a document, as well as the branch of revisions which have produced the latest form.
parent 22aba909
......@@ -26,21 +26,44 @@
type: "query",
sub_storage: spec.sub_storage
});
this._lastseen = undefined;
}
BryanStorage.prototype.get = function (id_in, revision_steps) {
BryanStorage.prototype.get = function (id_in, revision) {
// Default behavior, get() returns the most recent revision
if (revision_steps === undefined) {
revision_steps = 0;
if (revision === undefined) {
revision = {
steps: 0,
path: "absolute"
};
}
// Default type of traversal is absolute:
// "absolute" -- step backward in chronological order of changes to document
// "consistent" -- step backward in chronological order of only edits the
// most recent version is based on. Other branches of edits are ignored
if (revision.path === undefined) {
revision.path = "absolute";
}
// Query to get the last edit made to this document
var substorage = this._sub_storage,
var storage = this,
substorage = this._sub_storage,
options = {
query: "doc_id: " + id_in,
sort_on: [["timestamp", "descending"]],
limit: [revision_steps, 1]
sort_on: [["timestamp", "descending"]]
};
// In "absolute" path, .get returns the revision.steps-most-recent revision
if (revision.path === "absolute") {
options.limit = [revision.steps, 1];
// In "consistent path, .get returns the most recent revision and looks
// deeper into history with the result's .lastseen attribute
} else if (revision.path === "consistent") {
options.limit = [0, 1];
}
return substorage.allDocs(options)
.push(function (results) {
if (results.data.rows.length > 0) {
......@@ -52,20 +75,62 @@
);
})
// Decide return based on last edit type
.push(function (result) {
// Function used to chain together substorage.get's for "consistent"
// traversal
function recurse_get(result) {
if (result.lastseen === undefined) {
throw new jIO.util.jIOError(
"bryanstorage: cannot find object '" +
id_in +
"' (end of history)",
404
);
}
return substorage.get(result.lastseen);
}
// If last edit was a remove, throw a 'not found' error
if (result.op === "remove") {
if (result.op === "remove" && revision.path === "absolute") {
throw new jIO.util.jIOError(
"bryanstorage: cannot find object '" + id_in + "' (removed)",
404
);
}
// If last edit was a put, return the document data
if (result.op === "put") {
// The query for "absolute" traversal returns exactly the document
// requested
if (revision.path === "absolute" || revision.steps === 0) {
storage._lastseen = result.timestamp;
return result.doc;
}
if (revision.path === "consistent") {
// Chain together promises to access history of document
var promise = substorage.get(result.lastseen);
while (revision.steps > 1) {
promise = promise.push(recurse_get);
revision.steps -= 1;
}
// Once at desired depth, update storage._lastseen and return doc
return promise.push(function (result) {
storage._lastseen = result.timestamp;
if (result.op === "remove") {
throw new jIO.util.jIOError(
"bryanstorage: cannot find object '" +
result.doc_id +
"' (removed)",
404
);
}
return result.doc;
});
}
}
});
};
......@@ -81,8 +146,11 @@
timestamp: timestamp,
doc_id: id,
doc: data,
op: "put"
op: "put",
lastseen: this._lastseen
};
this._lastseen = timestamp;
//console.log(metadata.doc.k, timestamp, metadata.lastseen);
return this._sub_storage.put(timestamp, metadata);
};
......@@ -92,8 +160,10 @@
// XXX: remove this attribute once query can sort_on id
timestamp: timestamp,
doc_id: id,
op: "remove"
op: "remove",
lastseen: this._lastseen
};
this._lastseen = timestamp;
return this._sub_storage.put(timestamp, metadata);
};
......
......@@ -83,7 +83,8 @@
title: "foo0"
},
timestamp: results.timestamp,
op: "put"
op: "put",
lastseen: undefined
}, "The first item in the log is pushing bar's title to 'foo0'");
return jio.remove("bar");
})
......@@ -117,7 +118,8 @@
deepEqual(result, {
doc_id: "bar",
timestamp: result.timestamp,
op: "remove"
op: "remove",
lastseen: result.lastseen
});
})
.fail(function (error) {
......@@ -325,19 +327,19 @@
deepEqual(result,
{"k4": "v4"},
"By default, .get returns latest revision");
return jio.get("doc", 0);
return jio.get("doc", {steps: 0});
})
.push(function (result) {
deepEqual(result,
{"k4": "v4"},
".get returns latest revision with second input = 0");
return jio.get("doc", 1);
return jio.get("doc", {steps: 1});
})
.push(function (result) {
deepEqual(result,
{"k3": "v3"},
"Walk back one revision with second input = 1");
return jio.get("doc", 2);
return jio.get("doc", {steps: 2});
})
.push(function () {
ok(false, "This query should have thrown a 404 error");
......@@ -346,25 +348,25 @@
deepEqual(error.status_code,
404,
"Current state of document is 'removed'.");
return jio.get("doc", 3);
return jio.get("doc", {steps: 3});
})
.push(function (result) {
deepEqual(result,
{"k2": "v2"},
"Walk back three revisions with second input = 3");
return jio.get("doc", 4);
return jio.get("doc", {steps: 4});
})
.push(function (result) {
deepEqual(result,
{"k1": "v1"},
"Walk back four revisions with second input = 4");
return jio.get("doc", 5);
return jio.get("doc", {steps: 5});
})
.push(function (result) {
deepEqual(result,
{"k0": "v0"},
"Walk back five revisions with second input = 5");
return jio.get("doc", 6);
return jio.get("doc", {steps: 6});
})
.push(function () {
ok(false, "This query should have thrown a 404 error");
......@@ -380,4 +382,504 @@
})
.always(function () {start(); });
});
/////////////////////////////////////////////////////////////////
// Accessing older revisions with multiple users
/////////////////////////////////////////////////////////////////
module("bryanStorage.accessing_older_revisions_multiple_users");
test("Testing retrieval of older revisions of documents with multiple users",
function () {
stop();
expect(34);
// create storage of type "bryan" with memory as substorage
var dbname = "multi_user_db" + Date.now(),
jio1 = jIO.createJIO({
type: "bryan",
sub_storage: {
type: "uuid",
sub_storage: {
type: "indexeddb",
database: dbname
}
}
}),
jio2 = jIO.createJIO({
type: "bryan",
sub_storage: {
type: "uuid",
sub_storage: {
type: "indexeddb",
database: dbname
}
}
}),
jio3 = jIO.createJIO({
type: "bryan",
sub_storage: {
type: "uuid",
sub_storage: {
type: "indexeddb",
database: dbname
}
}
});
jio1.put("doc", {
"k": "v0.1"
})
.push(function () {
return jio2.get("doc");
})
.push(function () {
return jio3.get("doc");
})
.push(function () {
return jio2.put("doc", {
"k": "v0.1.2"
});
})
.push(function () {
return jio3.put("doc", {
"k": "v0.1.3"
});
})
/**
.push(function () {
return jio2.put("doc", {
"k": "v0.1.2.2"
});
})
**/
.push(function () {
return jio2.remove("doc");
})
.push(function () {
return jio3.put("doc", {
"k": "v0.1.3.3"
});
})
.push(function () {
return jio1.get("doc");
})
.push(function () {
return jio1.put("doc", {
"k": "v0.1.3.3.1"
});
})
.push(function () {
return jio2.put("doc", {
"k": "v0.1.2.2.2"
});
})
.push(function () {
return jio3.put("doc", {
"k": "v0.1.3.3.3"
});
})
.push(function () {
return jio1.get("doc");
})
// jio2 has a different version than 1 & 3 as its latest revision
/**
.push(function () {
return jio2.get("doc");
})
**/
.push(function () {
return jio3.get("doc");
})
// Test all lastseens are the same
.push(function () {
equal(jio1._lastseen, jio2._lastseen, "All users see same revision");
equal(jio1._lastseen, jio3._lastseen, "All users see same revision");
//
// Test consistent history of user 1
//
return jio1.get("doc", {
path: "consistent",
steps: 0
});
})
.push(function (result) {
deepEqual(result, {
"k": "v0.1.3.3.3"
}, "Get of depth 0 returns latest version"
);
return jio1.get("doc", {
path: "consistent",
steps: 1
});
})
.push(function (result) {
deepEqual(result, {
"k": "v0.1.3.3"
}, "Get of consistent depth 1 returns correct version"
);
return jio1.get("doc", {
path: "consistent",
steps: 2
});
})
.push(function (result) {
deepEqual(result, {
"k": "v0.1.3"
}, "Get of consistent depth 2 returns correct version"
);
return jio1.get("doc", {
path: "consistent",
steps: 3
});
})
.push(function (result) {
deepEqual(result, {
"k": "v0.1"
}, "Get of consistent depth 3 returns correct version"
);
return jio1.get("doc", {
path: "consistent",
steps: 4
});
})
.push(function () {
ok(false, "This query should have thrown a 404 error");
},
function (error) {
deepEqual(error.status_code,
404,
"There are only 3 previous states of this document: " + error);
})
.push(function () {
//
// Test consistent history of user 2 (Is the same as 1 & 3 even though
// User 2 has not explicitly called .get since the latest changes
// were made)
//
return jio2.get("doc", {
path: "consistent",
steps: 0
});
})
.push(function (result) {
deepEqual(result, {
"k": "v0.1.3.3.3"
}, "Get of depth 0 returns latest version"
);
return jio2.get("doc", {
path: "consistent",
steps: 1
});
})
.push(function (result) {
deepEqual(result, {
"k": "v0.1.3.3"
}, "Get of depth 0 returns latest version"
);
return jio2.get("doc", {
path: "consistent",
steps: 2
});
})
.push(function (result) {
deepEqual(result, {
"k": "v0.1.3"
}, "Get of consistent depth 2 returns correct version"
);
return jio2.get("doc", {
path: "consistent",
steps: 3
});
})
.push(function (result) {
deepEqual(result, {
"k": "v0.1"
}, "Get of consistent depth 3 returns correct version"
);
return jio2.get("doc", {
path: "consistent",
steps: 4
});
})
.push(function () {
ok(false, "This query should have thrown a 404 error");
},
function (error) {
deepEqual(error.status_code,
404,
"There are only 3 previous states of this document: " + error);
})
.push(function () {
//
// Test consistent history of user 3 (Should be same as user 1)
//
return jio3.get("doc", {
path: "consistent",
steps: 0
});
})
.push(function (result) {
deepEqual(result, {
"k": "v0.1.3.3.3"
}, "User 2 consistent history is same as user 1"
);
return jio3.get("doc", {
path: "consistent",
steps: 1
});
})
.push(function (result) {
deepEqual(result, {
"k": "v0.1.3.3"
}, "User 2 consistent history is same as user 1"
);
return jio3.get("doc", {
path: "consistent",
steps: 2
});
})
.push(function (result) {
deepEqual(result, {
"k": "v0.1.3"
}, "User 2 consistent history is same as user 1"
);
return jio3.get("doc", {
path: "consistent",
steps: 3
});
})
.push(function (result) {
deepEqual(result, {
"k": "v0.1"
}, "User 2 consistent history is same as user 1"
);
return jio3.get("doc", {
path: "consistent",
steps: 4
});
})
.push(function () {
ok(false, "This query should have thrown a 404 error");
},
function (error) {
deepEqual(error.status_code,
404,
"There are only 3 previous states of this document");
})
//
// Test absolute history of user 1
//
.push(function () {
return jio1.get("doc", {
path: "absolute",
steps: 0
});
})
.push(function (result) {
deepEqual(result, {
"k": "v0.1.3.3.3"
}, "Get of absolute depth 0 returns latest version"
);
return jio1.get("doc", {
path: "absolute",
steps: 1
});
})
.push(function (result) {
deepEqual(result, {
"k": "v0.1.2.2.2"
}, "Get of absolute depth 1 returns correct version"
);
return jio1.get("doc", {
path: "absolute",
steps: 2
});
})
.push(function (result) {
deepEqual(result, {
"k": "v0.1.3.3.1"
}, "Get of absolute depth 2 returns correct version"
);
return jio1.get("doc", {
path: "absolute",
steps: 3
});
})
.push(function (result) {
deepEqual(result, {
"k": "v0.1.3.3"
}, "Get of absolute depth 3 returns correct version"
);
return jio1.get("doc", {
path: "absolute",
steps: 4
});
})
.push(function () {
ok(false, "This query should have thrown a 404 error");
},
function (error) {
deepEqual(error.status_code,
404,
"Document has been removed at this point");
return jio1.get("doc", {
path: "absolute",
steps: 5
});
})
.push(function (result) {
deepEqual(result, {
"k": "v0.1.3"
}, "Get of absolute depth 5 returns correct version"
);
return jio1.get("doc", {
path: "absolute",
steps: 6
});
})
.push(function (result) {
deepEqual(result, {
"k": "v0.1.2"
}, "Get of absolute depth 6 returns correct version"
);
return jio1.get("doc", {
path: "absolute",
steps: 7
});
})
.push(function (result) {
deepEqual(result, {
"k": "v0.1"
});
return jio1.get("doc", {
path: "absolute",
steps: 8
});
})
.push(function () {
ok(false, "This query should have thrown a 404 error");
},
function (error) {
deepEqual(error.status_code,
404,
"There are only 3 previous states of this document");
})
//
// Test absolute history of user 2
//
.push(function () {
return jio2.get("doc", {
path: "absolute",
steps: 0
});
})
.push(function (result) {
deepEqual(result, {
"k": "v0.1.3.3.3"
});
return jio2.get("doc", {
path: "absolute",
steps: 1
});
})
.push(function (result) {
deepEqual(result, {
"k": "v0.1.2.2.2"
});
return jio2.get("doc", {
path: "absolute",
steps: 2
});
})
.push(function (result) {
deepEqual(result, {
"k": "v0.1.3.3.1"
});
return jio2.get("doc", {
path: "absolute",
steps: 3
});
})
.push(function (result) {
deepEqual(result, {
"k": "v0.1.3.3"
});
return jio2.get("doc", {
path: "absolute",
steps: 4
});
})
.push(function () {
ok(false, "This query should have thrown a 404 error");
},
function (error) {
deepEqual(error.status_code,
404,
"Document has been removed at this point");
return jio2.get("doc", {
path: "absolute",
steps: 5
});
})
.push(function (result) {
deepEqual(result, {
"k": "v0.1.3"
});
return jio2.get("doc", {
path: "absolute",
steps: 6
});
})
.push(function (result) {
deepEqual(result, {
"k": "v0.1.2"
});
return jio2.get("doc", {
path: "absolute",
steps: 7
});
})
.push(function (result) {
deepEqual(result, {
"k": "v0.1"
});
return jio2.get("doc", {
path: "absolute",
steps: 8
});
})
.push(function () {
ok(false, "This query should have thrown a 404 error");
},
function (error) {
deepEqual(error.status_code,
404,
"There are only 3 previous states of this document");
})
.fail(function (error) {
//console.log(error);
ok(false, error);
})
.always(function () {start(); });
});
}(jIO, QUnit));
\ No newline at end of file
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