Commit 923a85d2 authored by Bryan Kaperick's avatar Bryan Kaperick

Added revision_history property on historystorage initialization to allow any...

Added revision_history property on historystorage initialization to allow any ids and metadata properties to be set without conflicts 'under the hood'.
parent f9bdfa9b
......@@ -15,13 +15,6 @@
return timestamp + "-" + uuid;
}
function isTimestamp(id) {
//A timestamp is of the form
//"[13 digit number]-[4 numbers/lowercase letters]"
var re = /^[0-9]{13}-[a-z0-9]{4}$/;
return re.test(id);
}
function throwCantFindError(id) {
throw new jIO.util.jIOError(
"HistoryStorage: cannot find object '" + id + "'",
......@@ -44,24 +37,27 @@
*/
function HistoryStorage(spec) {
this._sub_storage = jIO.createJIO(spec.sub_storage);
this._include_revisions = spec.include_revisions;
if (spec.hasOwnProperty("include_revisions")) {
this._include_revisions = spec.include_revisions;
} else {
this._include_revisions = false;
}
}
HistoryStorage.prototype.get = function (id_in) {
if (isTimestamp(id_in)) {
if (this._include_revisions) {
// Try to treat id_in as a timestamp instead of a name
return this._sub_storage.get(id_in)
.push(function (result) {
if (result.op === "put") {
return result.doc;
}
throwCantFindError(id_in);
throwRemovedError(id_in);
}, function (error) {
if (error.status_code === 404 &&
error instanceof jIO.util.jIOError) {
throwRemovedError(id_in);
throwCantFindError(id_in);
}
throw error;
});
......@@ -92,7 +88,7 @@
};
return substorage.allDocs(options)
.push(function (results) {
if (results.data.rows.length > 0) {
if (results.data.total_rows > 0) {
if (results.data.rows[0].value.op === "put") {
return substorage.get(results.data.rows[0].id)
.push(function (result) {
......@@ -107,12 +103,6 @@
HistoryStorage.prototype.put = function (id, data) {
if (isTimestamp(id)) {
throw new jIO.util.jIOError(
"Document cannot have id of the same form as a timestamp",
422
);
}
var timestamp = generateUniqueTimestamp(Date.now()),
metadata = {
// XXX: remove this attribute once query can sort_on id
......@@ -145,7 +135,7 @@
query_doc_id,
options_remcheck;
if (isTimestamp(id)) {
if (this._include_revisions) {
query_doc_id = new SimpleQuery({
operator: "<=",
key: "timestamp",
......@@ -253,7 +243,7 @@
HistoryStorage.prototype.getAttachment = function (id, name) {
if (isTimestamp(id)) {
if (this._include_revisions) {
return this._sub_storage.getAttachment(id, name)
.push(undefined, function (error) {
if (error.status_code === 404 &&
......@@ -304,7 +294,7 @@
};
return substorage.allDocs(options)
.push(function (results) {
if (results.data.rows.length > 0) {
if (results.data.total_rows > 0) {
if (results.data.rows[0].value.op === "remove" ||
results.data.rows[0].value.op === "removeAttachment") {
throwRemovedError(id);
......@@ -352,7 +342,7 @@
// Check if query involved _timestamp.
// If not, use default behavior and only query on latest revisions
rev_query = options.include_revisions,
rev_query = this._include_revisions,
doc_id_name,
timestamp_name;
......@@ -393,13 +383,16 @@
}
if (rev_query) {
// Only query on documents which are puts are putAttachments
// We only query on versions mapping to puts or putAttachments
results = results.map(function (docum, ind) {
var data_key;
if (docum.op === "put") {
return docum;
}
if (docum.op === "putAttachment") {
// putAttachment document does not contain doc metadata, so we
// add it from the most recent non-removed put on same id
docum.doc = {};
for (i = ind + 1; i < results.length; i += 1) {
if (results[i].doc_id === docum.doc_id) {
......@@ -411,10 +404,9 @@
}
return docum;
}
// If most recent edit on document was a remove before this
// attachment, then don't include attachment in query
if (results[i].doc_id === "remove") {
//console.log("not returning putAttachment at ",
// docum.timestamp,
// " because it was attached to a removed document");
return false;
}
}
......@@ -423,20 +415,26 @@
return false;
});
} else {
// Only query on latest revisions of non-removed documents/attachment
// edits
results = results.map(function (docum, ind) {
var data_key;
if (docum.op === "put") {
// Mark as read and include in query
if (!seen.hasOwnProperty(docum.doc_id)) {
seen[docum.doc_id] = {};
//console.log("returning put at ", docum.timestamp,
// " because it is most recent edit to " + docum.doc_id);
return docum;
}
//console.log("not returning put at ", docum.timestamp,
// " because it was edited later");
} else if (docum.op === "remove") {
} else if (docum.op === "remove" ||
docum.op === "removeAttachment") {
// Mark as read but do not include in query
seen[docum.doc_id] = {};
} else if (docum.op === "putAttachment") {
// If latest edit, mark as read, add document metadata from most
// recent put, and add to query
if (!seen.hasOwnProperty(docum.doc_id)) {
seen[docum.doc_id] = {};
docum.doc = {};
......@@ -448,41 +446,37 @@
docum.doc[data_key] = results[i].doc[data_key];
}
}
/**console.log("returning putAttachment at ",
docum.timestamp,
" because it is most recent edit to attachment " +
docum.name + " of document " + docum.doc_id);
**/
return docum;
}
if (results[i].doc_id === "remove") {
/**console.log("not returning putAttachment at ",
docum.timestamp,
" because it was attached to a removed document");
**/
// If most recent edit on document was a remove before
// this attachment, then don't include attachment in query
return false;
}
}
}
}
} else if (docum.op === "removeAttachment") {
seen[docum.doc_id] = {};
}
return false;
});
}
docs_to_query = results
.filter(function (docum) {
return docum;
})
.map(function (docum) {
// Save timestamp and id information for retrieval at the end of
// buildQuery
docum.doc[timestamp_name] = docum.timestamp;
docum.doc[doc_id_name] = docum.doc_id;
return docum.doc;
});
// Return timestamp and id information from query
options.select_list.push(doc_id_name);
options.select_list.push(timestamp_name);
// Sort on timestamp with updated timestamp_name
options.sort_on[options.sort_on.length - 1] = [
timestamp_name, "descending"
];
......
......@@ -48,6 +48,20 @@
}
}
});
this.history = jIO.createJIO({
type: "history",
include_revisions: true,
sub_storage: {
type: "query",
sub_storage: {
type: "uuid",
sub_storage: {
type: "indexeddb",
database: dbname
}
}
}
});
this.not_history = jIO.createJIO({
type: "query",
sub_storage: {
......@@ -66,6 +80,7 @@
stop();
expect(10);
var jio = this.jio,
history = this.history,
not_history = this.not_history,
timestamps,
blob2 = this.blob2,
......@@ -120,7 +135,7 @@
blob2,
"Return the attachment information with getAttachment"
);
return jio.getAttachment(
return history.getAttachment(
timestamps[3],
"attacheddata"
);
......@@ -131,7 +146,7 @@
"Return the attachment information with getAttachment for " +
"current revision"
);
return jio.getAttachment(
return history.getAttachment(
timestamps[2],
"attacheddata"
);
......@@ -313,7 +328,7 @@
return jio.allDocs();
})
.push(function (results) {
equal(results.data.rows.length,
equal(results.data.total_rows,
2,
"Two documents in accessible storage");
return jio.get(results.data.rows[1].id);
......@@ -373,6 +388,7 @@
stop();
expect(8);
var jio = this.jio,
history = this.history,
not_history = this.not_history,
blob1 = new Blob(['a']),
blob11 = new Blob(['ab']),
......@@ -430,12 +446,12 @@
},
"Current state of document is correct");
return jio.allAttachments(timestamps[0]);
return history.allAttachments(timestamps[0]);
})
.push(function (results) {
deepEqual(results, {}, "First version of document has 0 attachments");
return jio.allAttachments(timestamps[1]);
return history.allAttachments(timestamps[1]);
})
.push(function (results) {
deepEqual(results, {
......@@ -443,13 +459,13 @@
data2: blob2
}, "Both attachments are included in allAttachments");
return jio.allAttachments(timestamps[2]);
return history.allAttachments(timestamps[2]);
})
.push(function (results) {
deepEqual(results, {
data: blob1
}, "Removed attachment does not show up in allAttachments");
return jio.allAttachments(timestamps[3]);
return history.allAttachments(timestamps[3]);
})
.push(function () {
ok(false, "This query should have thrown a 404 error");
......@@ -465,7 +481,7 @@
"Error is handled by Historystorage.");
})
.push(function () {
return jio.allAttachments(timestamps[4]);
return history.allAttachments(timestamps[4]);
})
.push(function (results) {
deepEqual(results, {
......@@ -501,6 +517,20 @@
}
}
});
this.history = jIO.createJIO({
type: "history",
include_revisions: true,
sub_storage: {
type: "query",
sub_storage: {
type: "uuid",
sub_storage: {
type: "indexeddb",
database: dbname
}
}
}
});
this.not_history = jIO.createJIO({
type: "query",
sub_storage: {
......@@ -519,17 +549,60 @@
stop();
expect(2);
var jio = this.jio,
BADINPUT_ERRCODE = 422;
history = this.history,
timestamp;
jio.put("doc", {title: "foo"})
.push(function () {
return history.allDocs();
})
.push(function (res) {
timestamp = res.data.rows[0].timestamp;
return history.put(timestamp, {key: "val"});
})
.push(function () {
return jio.get("doc");
})
.push(function (result) {
deepEqual(result, {
title: "foo"
}, "Saving document with timestamp id does not cause issues (1)");
return history.get(timestamp);
})
.push(function (result) {
deepEqual(result, {
title: "foo"
}, "Saving document with timestamp id does not cause issues (2)");
return history.get(timestamp);
})
.fail(function (error) {
//console.log(error);
ok(false, error);
})
.always(function () {start(); });
});
jio.put("1234567891123-ab7d", {})
test("Getting a non-existent document",
function () {
stop();
expect(3);
var jio = this.jio;
jio.put("not_doc", {})
.push(function () {
return jio.get("doc");
})
.push(function () {
ok(false, "This statement should not be reached");
}, function (error) {
//console.log(error);
ok(error instanceof jIO.util.jIOError, "Correct type of error");
deepEqual(error.status_code,
BADINPUT_ERRCODE,
"Can't save a document with a timestamp-formatted id"
404,
"Correct status code for getting a non-existent document"
);
deepEqual(error.message,
"HistoryStorage: cannot find object 'doc'",
"Error is handled by history storage before reaching console");
})
.fail(function (error) {
//console.log(error);
......@@ -538,11 +611,13 @@
.always(function () {start(); });
});
test("Getting a non-existent document",
test("Getting a document with timestamp when include_revisions is false",
function () {
stop();
expect(3);
var jio = this.jio;
expect(9);
var jio = this.jio,
history = this.history,
timestamp;
jio.put("not_doc", {})
.push(function () {
return jio.get("doc");
......@@ -560,6 +635,41 @@
"HistoryStorage: cannot find object 'doc'",
"Error is handled by history storage before reaching console");
})
.push(function () {
return history.allDocs();
})
.push(function (results) {
timestamp = results.data.rows[0].timestamp;
return jio.get(timestamp);
})
.push(function () {
ok(false, "This statement should not be reached");
}, function (error) {
ok(error instanceof jIO.util.jIOError, "Correct type of error");
deepEqual(error.status_code,
404,
"Correct status code for getting a non-existent document"
);
deepEqual(error.message,
"HistoryStorage: cannot find object '" + timestamp + "'",
"Error is handled by history storage before reaching console");
})
.push(function () {
return history.get("doc");
})
.push(function () {
ok(false, "This statement should not be reached");
}, function (error) {
//console.log(error);
ok(error instanceof jIO.util.jIOError, "Correct type of error");
deepEqual(error.status_code,
404,
"Correct status code for getting a non-existent document"
);
deepEqual(error.message,
"HistoryStorage: cannot find object 'doc'",
"Error is handled by history storage before reaching console");
})
.fail(function (error) {
//console.log(error);
ok(false, error);
......@@ -570,8 +680,9 @@
test("Creating a document with put and retrieving it with get",
function () {
stop();
expect(7);
expect(5);
var jio = this.jio,
history = this.history,
not_history = this.not_history,
timestamps;
jio.put("doc", {title: "version0"})
......@@ -590,7 +701,7 @@
1,
"One revision is saved in storage"
);
return jio.get(timestamps[0]);
return history.get(timestamps[0]);
})
.push(function (result) {
deepEqual(result, {
......@@ -623,18 +734,6 @@
"Can't access non-existent document"
);
})
.push(function () {
return jio.get("1234567891123-abcd");
})
.push(function () {
ok(false, "Trying to get a non-existent id should have raised 404");
}, function (error) {
ok(error instanceof jIO.util.jIOError, "Correct type of error");
deepEqual(error.status_code,
404,
"Can't access document by getting with non-existent id"
);
})
.fail(function (error) {
//console.log(error);
ok(false, error);
......@@ -647,6 +746,7 @@
stop();
expect(7);
var jio = this.jio,
history = this.history,
not_history = this.not_history,
timestamps;
......@@ -682,7 +782,7 @@
title: "t3",
subtitle: "s3"
}, "Get returns latest revision");
return jio.get(timestamps[0]);
return history.get(timestamps[0]);
}, function (err) {
ok(false, err);
})
......@@ -691,14 +791,14 @@
title: "t0",
subtitle: "s0"
}, "Get returns first version");
return jio.get(timestamps[1]);
return history.get(timestamps[1]);
})
.push(function (result) {
deepEqual(result, {
title: "t1",
subtitle: "s1"
}, "Get returns second version");
return jio.get(timestamps[2]);
return history.get(timestamps[2]);
}, function (err) {
ok(false, err);
})
......@@ -707,20 +807,20 @@
title: "t2",
subtitle: "s2"
}, "Get returns third version");
return jio.get(timestamps[3]);
return history.get(timestamps[3]);
}, function (err) {
ok(false, err);
})
.push(function () {
ok(false, "This should have thrown a 404 error");
return jio.get(timestamps[4]);
return history.get(timestamps[4]);
},
function (error) {
ok(error instanceof jIO.util.jIOError, "Correct type of error");
deepEqual(error.status_code,
404,
"Error if you try to go back more revisions than what exists");
return jio.get(timestamps[4]);
return history.get(timestamps[4]);
})
.push(function (result) {
deepEqual(result, {
......@@ -767,7 +867,7 @@
});
})
.push(function (results) {
equal(results.data.rows.length,
equal(results.data.total_rows,
9,
"All nine versions exist in storage");
return not_history.get(results.data.rows[0].id);
......@@ -801,7 +901,7 @@
});
})
.push(function (results) {
equal(results.data.rows.length,
equal(results.data.total_rows,
10,
"Remove operation is recorded");
return not_history.get(results.data.rows[0].id);
......@@ -841,6 +941,20 @@
}
}
});
this.history = jIO.createJIO({
type: "history",
include_revisions: true,
sub_storage: {
type: "query",
sub_storage: {
type: "uuid",
sub_storage: {
type: "indexeddb",
database: dbname
}
}
}
});
this.not_history = jIO.createJIO({
type: "query",
sub_storage: {
......@@ -889,7 +1003,7 @@
return results[0];
})
.push(function (results) {
equal(results.data.rows.length,
equal(results.data.total_rows,
1,
"Exactly one result returned");
deepEqual(results.data.rows[0], {
......@@ -903,7 +1017,7 @@
return not_history.allDocs();
})
.push(function (results) {
equal(results.data.rows.length,
equal(results.data.total_rows,
1,
"Exactly one result returned");
return not_history.get(results.data.rows[0].id);
......@@ -989,6 +1103,7 @@
stop();
expect(10);
var jio = this.jio,
history = this.history,
not_history = this.not_history,
timestamps;
jio.put("doc", {
......@@ -1055,7 +1170,7 @@
return results[0];
})
.push(function (results) {
equal(results.data.rows.length,
equal(results.data.total_rows,
1,
"Exactly one result returned");
deepEqual(results.data.rows[0], {
......@@ -1071,14 +1186,13 @@
);
})
.push(function () {
return jio.allDocs({
return history.allDocs({
query: "",
select_list: ["title", "subtitle"],
include_revisions: true
select_list: ["title", "subtitle"]
});
})
.push(function (results) {
equal(results.data.rows.length,
equal(results.data.total_rows,
3,
"Querying with include_revisions retrieves all versions");
deepEqual(results.data.rows, [
......@@ -1214,7 +1328,7 @@
return jio.allDocs({sort_on: [["timestamp", "descending"]]});
})
.push(function (results) {
equal(results.data.rows.length,
equal(results.data.total_rows,
2,
"Only two non-removed unique documents exist."
);
......@@ -1308,7 +1422,7 @@
});
})
.push(function (results) {
equal(results.data.rows.length, 3);
equal(results.data.total_rows, 3);
deepEqual(results.data.rows, [
{
doc: {},
......@@ -1347,6 +1461,7 @@
stop();
expect(3);
var jio = this.jio,
history = this.history,
not_history = this.not_history,
timestamps,
docs = [
......@@ -1540,12 +1655,11 @@
], "All versions of documents are stored correctly");
})
.push(function () {
return jio.allDocs({
return history.allDocs({
query: "NOT (date: >= 2 AND date: <= 3)",
select_list: ["date", "non-existent-key", "type", "title"],
sort_on: [["date", "descending"]
],
include_revisions: true
]
});
})
.push(function (results) {
......@@ -1636,6 +1750,7 @@
this.blob2 = new Blob(['b']);
this.blob3 = new Blob(['ccc']);
this.other_blob = new Blob(['1']);
this.jio = jIO.createJIO({
type: "history",
sub_storage: {
......@@ -1649,6 +1764,20 @@
}
}
});
this.history = jIO.createJIO({
type: "history",
include_revisions: true,
sub_storage: {
type: "query",
sub_storage: {
type: "uuid",
sub_storage: {
type: "indexeddb",
database: dbname
}
}
}
});
this.not_history = jIO.createJIO({
type: "query",
sub_storage: {
......@@ -1667,6 +1796,7 @@
stop();
expect(1);
var jio = this.jio,
history = this.history,
not_history = this.not_history,
timestamps,
blobs1 = [
......@@ -1716,9 +1846,8 @@
})
.push(function () {
return jio.allDocs({
select_list: ["title"],
include_revisions: true
return history.allDocs({
select_list: ["title"]
});
})
.push(function (results) {
......@@ -1824,6 +1953,7 @@
expect(1);
var jio = this.jio,
not_history = this.not_history,
history = this.history,
timestamps,
blobs1 = [
new Blob(['a']),
......@@ -1859,9 +1989,8 @@
})
.push(function () {
return jio.allDocs({
select_list: ["title"],
include_revisions: true
return history.allDocs({
select_list: ["title"]
});
})
.push(function (results) {
......@@ -1913,6 +2042,7 @@
expect(2);
var jio = this.jio,
not_history = this.not_history,
history = this.history,
timestamps,
blobs1 = [
new Blob(['a']),
......@@ -1948,8 +2078,7 @@
})
.push(function () {
return jio.allDocs({
select_list: ["title"],
include_revisions: false
select_list: ["title"]
});
})
.push(function (results) {
......@@ -1970,9 +2099,8 @@
"allDocs with include_revisions false should return all revisions");
})
.push(function () {
return jio.allDocs({
select_list: ["title"],
include_revisions: true
return history.allDocs({
select_list: ["title"]
});
})
.push(function (results) {
......
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