Commit cf5671c3 authored by Bryan Kaperick's avatar Bryan Kaperick

Changed revision access so that specific timestamps can be requested rather...

Changed revision access so that specific timestamps can be requested rather than steps back from the current revision.
parent 4376eb42
This source diff could not be displayed because it is too large. You can view the blob instead.
/*jslint nomen: true*/ /*jslint nomen: true*/
/*global RSVP*/ /*global RSVP, SimpleQuery, ComplexQuery*/
(function (jIO, RSVP) { (function (jIO, RSVP, SimpleQuery, ComplexQuery) {
"use strict"; "use strict";
// Used to distinguish between operations done within the same millisecond // Used to distinguish between operations done within the same millisecond
...@@ -12,88 +12,7 @@ ...@@ -12,88 +12,7 @@
.toString(16)).slice(-4), .toString(16)).slice(-4),
timestamp = Date.now().toString(); timestamp = Date.now().toString();
return timestamp + "-" + uuid; return timestamp + "-" + uuid;
}, };
// Helper function for getAttachment
findAttachment = function (substorage, name, metadata_query, steps) {
var options = {
query: metadata_query,
sort_on: [["timestamp", "descending"], ["op", "ascending"]],
select_list: ["op", "name"]
};
return substorage.allDocs(options)
.push(function (results) {
var ind,
id = metadata_query.value,
count = 0;
// At the least, a document needs to have been put and an attachment
// needs to have been put
if (results.data.rows.length > 1) {
for (ind = 0; ind < results.data.rows.length; ind += 1) {
// Cannot get the attachment of a removed document
if (results.data.rows[ind].value.op === "remove") {
throw new jIO.util.jIOError(
"HistoryStorage: cannot find attachment '" + name +
"' of object '" + id + "' (removed)",
404
);
}
// Make sure to get the correct revision of the attachment
// and throw 404 error if it was removed
if (results.data.rows[ind].value.name === name) {
if (count === steps) {
if (results.data.rows[ind].value.op === "removeAttachment") {
throw new jIO.util.jIOError(
"HistoryStorage: cannot find attachment '" + name +
"' of object '" + id + "' (removed)",
404
);
}
return substorage.getAttachment(
results.data.rows[ind].id,
name
);
}
count += 1;
}
}
}
throw new jIO.util.jIOError(
"HistoryStorage: cannot find attachment '" + name +
"' of object '" + id + "'",
404
);
});
},
findDoc = function (substorage, metadata_query, steps) {
var options = {
query: metadata_query,
sort_on: [["timestamp", "descending"]],
select_list: ["op"],
limit: [steps, 1]
};
return substorage.allDocs(options)
.push(function (results) {
var id_in = metadata_query.query_list[0].value;
if (results.data.rows.length > 0) {
if (results.data.rows[0].value.op === "put") {
return substorage.get(results.data.rows[0].id)
.push(function (result) {
return result.doc;
});
}
throw new jIO.util.jIOError(
"HistoryStorage: cannot find object '" + id_in + "' (removed)",
404
);
}
throw new jIO.util.jIOError(
"HistoryStorage: cannot find object '" + id_in + "'",
404
);
});
};
/** /**
...@@ -104,55 +23,90 @@ ...@@ -104,55 +23,90 @@
*/ */
function HistoryStorage(spec) { function HistoryStorage(spec) {
this._sub_storage = jIO.createJIO(spec.sub_storage); this._sub_storage = jIO.createJIO(spec.sub_storage);
this._timestamps = {};
} }
HistoryStorage.prototype.get = function (id_in) { HistoryStorage.prototype.get = function (id_in) {
// Query to get the last edit made to this document // Query to get the last edit made to this document
var substorage = this._sub_storage, var substorage = this._sub_storage,
metadata_query;
// Include id_in as value in query object for safety
metadata_query = jIO.QueryFactory.create(
"(doc_id: undefined) AND ((op: put) OR (op: remove))"
);
metadata_query.query_list[0].value = id_in;
return findDoc(substorage, metadata_query, 0) // Include id_in as value in query object for safety
.push(undefined, metadata_query = new ComplexQuery({
// If no documents returned in first query, check if the id is encoding operator: "AND",
// revision information query_list: [
function (error) { new SimpleQuery({key: "doc_id", value: id_in}),
new ComplexQuery({
if (!(error instanceof jIO.util.jIOError) || operator: "OR",
(error.status_code !== 404) || query_list: [
(error.message !== "HistoryStorage: cannot find object '" + new SimpleQuery({key: "op", value: "remove"}),
id_in + "'")) { new SimpleQuery({key: "op", value: "put"})
throw error; ]
})
]
}),
options = {
query: metadata_query,
select_list: ["op"],
limit: [0, 1],
sort_on: [["timestamp", "descending"]]
};
return substorage.allDocs(options)
.push(function (results) {
if (results.data.rows.length > 0) {
if (results.data.rows[0].value.op === "put") {
return substorage.get(results.data.rows[0].id)
.push(function (result) {
return result.doc;
}, function (error) {
if (error.status_code === 400 &&
error instanceof jIO.util.jIOError) {
throw new jIO.util.jIOError(
"HistoryStorage: cannot find object '" + id_in +
"'",
404
);
}
});
} }
throw new jIO.util.jIOError(
// "_-" is the revision signature used to indicate a previous revision "HistoryStorage: cannot find object '" + id_in + "' (removed)",
var steps, 404
steps_loc = id_in.lastIndexOf("_-"); );
}
// If the revision signature '_-' is not contained in the id, then // Try again by treating id_in as a timestamp instead of a name
// the first findDoc call should have found the id if it exists return substorage.get(id_in)
if (steps_loc === -1) { .push(function (result) {
if (result.op === "put") {
return result.doc;
}
throw new jIO.util.jIOError( throw new jIO.util.jIOError(
"HistoryStorage: cannot find object '" + id_in + "'", "HistoryStorage: cannot find object '" + id_in +
"' (removed)",
404 404
); );
} }, function (error) {
if (error.status_code === 400 &&
// If revision signature is found, query storage based on this error instanceof jIO.util.jIOError) {
steps = Number(id_in.slice(steps_loc + 2)); throw new jIO.util.jIOError(
id_in = id_in.slice(0, steps_loc); "HistoryStorage: cannot find object '" + id_in +
metadata_query.query_list[0].value = id_in; "'",
return findDoc(substorage, metadata_query, steps); 404
}); );
}
});
});
}; };
HistoryStorage.prototype.put = function (id, data) { HistoryStorage.prototype.put = function (id, data) {
if (data.hasOwnProperty("_timestamp")) {
throw new jIO.util.jIOError(
"Document cannot have metadata attribute '_timestamp'",
422
);
}
var timestamp = unique_timestamp(), var timestamp = unique_timestamp(),
metadata = { metadata = {
// XXX: remove this attribute once query can sort_on id // XXX: remove this attribute once query can sort_on id
...@@ -161,6 +115,11 @@ ...@@ -161,6 +115,11 @@
doc: data, doc: data,
op: "put" op: "put"
}; };
if (this._timestamps.hasOwnProperty(id)) {
this._timestamps[id].push(timestamp);
} else {
this._timestamps[id] = [timestamp];
}
return this._sub_storage.put(timestamp, metadata); return this._sub_storage.put(timestamp, metadata);
}; };
...@@ -172,55 +131,57 @@ ...@@ -172,55 +131,57 @@
doc_id: id, doc_id: id,
op: "remove" op: "remove"
}; };
this._timestamps[id].push(timestamp);
return this._sub_storage.put(timestamp, metadata); return this._sub_storage.put(timestamp, metadata);
}; };
HistoryStorage.prototype.allAttachments = function (id) { HistoryStorage.prototype.allAttachments = function (id) {
// XXX: Do we need to be able to retrieve older revisions with // XXX: If instead you passed a timestamp in as `id`, we could retrieve all
// allAttachments? // the attachments of the document at that point in time. Not sure if this
// would be useful.
// XXX: If document is removed, should this throw a 404?
var substorage = this._sub_storage, var substorage = this._sub_storage,
query_obj, // Include id as value in query object for safety (as opposed to string
options; // concatenation)
query_obj = new ComplexQuery({
// Include id as value in query object for safety (as opposed to string operator: "AND",
// concatenation) query_list: [
query_obj = jIO.QueryFactory.create( new SimpleQuery({key: "doc_id", value: id}),
"(doc_id: undefined) AND ((op: putAttachment) OR (op: removeAttachment))" new ComplexQuery({
); operator: "OR",
query_obj.query_list[0].value = id; query_list: [
new SimpleQuery({key: "op", value: "putAttachment"}),
new SimpleQuery({key: "op", value: "removeAttachment"})
]
})
]
}),
// Only query for attachment edits // Only query for attachment edits
options = { options = {
query: query_obj, query: query_obj,
sort_on: [["timestamp", "descending"]] sort_on: [["timestamp", "descending"]],
}; select_list: ["op", "timestamp", "name"]
};
return this._sub_storage.allDocs(options) return this._sub_storage.allDocs(options)
.push(function (results) {
var promises = results.data.rows.map(function (data) {
return substorage.get(data.id);
});
return RSVP.all(promises);
})
.push(function (results) { .push(function (results) {
var seen = {}, var seen = {},
attachments = {}, attachments = [],
attachment_promises = [],
ind, ind,
doc; entry;
// Only include attachments whose most recent edit is a putAttachment attachments = results.data.rows.filter(function (docum) {
// (and not a removeAttachment) if (!seen.hasOwnProperty(docum.value.name)) {
for (ind = 0; ind < results.length; ind += 1) { var output = (docum.value.op === "putAttachment");
doc = results[ind]; seen[docum.value.name] = {};
if (!seen.hasOwnProperty(doc.name)) { return output;
if (doc.op === "putAttachment") {
attachments[doc.name] = {};
}
seen[doc.name] = {};
} }
});
for (ind = 0; ind < attachments.length; ind += 1) {
entry = attachments[ind];
attachment_promises[entry.value.name] =
substorage.getAttachment(entry.id, entry.value.name);
} }
return attachments; return RSVP.hash(attachment_promises);
}); });
}; };
...@@ -234,6 +195,11 @@ ...@@ -234,6 +195,11 @@
op: "putAttachment" op: "putAttachment"
}, },
substorage = this._sub_storage; substorage = this._sub_storage;
if (this._timestamps[id].hasOwnProperty(name)) {
this._timestamps[id][name].push(timestamp);
} else {
this._timestamps[id][name] = [timestamp];
}
return this._sub_storage.put(timestamp, metadata) return this._sub_storage.put(timestamp, metadata)
.push(function () { .push(function () {
return substorage.putAttachment(timestamp, name, blob); return substorage.putAttachment(timestamp, name, blob);
...@@ -244,45 +210,95 @@ ...@@ -244,45 +210,95 @@
// Query to get the last edit made to this document // Query to get the last edit made to this document
var substorage = this._sub_storage, var substorage = this._sub_storage,
metadata_query;
// Include id_in as value in query object for safety
metadata_query = jIO.QueryFactory.create(
"(doc_id: undefined)"
);
metadata_query.value = id;
return findAttachment(substorage, name, metadata_query, 0)
.push(undefined,
// If no documents returned in first query, check if the id is encoding
// revision information
function (error) {
if (!(error instanceof jIO.util.jIOError) ||
(error.status_code !== 404) ||
(error.message !== "HistoryStorage: cannot find attachment '" +
name + "' of object '" + id + "'")) {
throw error;
}
var steps, // Include id_in as value in query object for safety
steps_loc = name.lastIndexOf("_-"); // "doc_id: id AND
// (op: remove OR ((op: putAttachment OR op: removeAttachment) AND
// If revision signature is not in id_in, than return 404, since id // name: name))"
// is not found metadata_query = new ComplexQuery({
if (steps_loc === -1) { operator: "AND",
query_list: [
new SimpleQuery({key: "doc_id", value: id}),
new ComplexQuery({
operator: "OR",
query_list: [
new SimpleQuery({key: "op", value: "remove"}),
new ComplexQuery({
operator: "AND",
query_list: [
new ComplexQuery({
operator: "OR",
query_list: [
new SimpleQuery({key: "op", value: "putAttachment"}),
new SimpleQuery({key: "op", value: "removeAttachment"})
]
}),
new SimpleQuery({key: "name", value: name})
]
})
]
})
]
}),
options = {
query: metadata_query,
sort_on: [["timestamp", "descending"]],
limit: [0, 1],
select_list: ["op", "name"]
};
return substorage.allDocs(options)
.push(function (results) {
if (results.data.rows.length > 0) {
if (results.data.rows[0].value.op === "remove" ||
results.data.rows[0].value.op === "removeAttachment") {
throw new jIO.util.jIOError( throw new jIO.util.jIOError(
"HistoryStorage: cannot find attachment '" + name + "HistoryStorage: cannot find object '" + id + "' (removed)",
"' of object '" + id + "'",
404 404
); );
} }
return substorage.getAttachment(results.data.rows[0].id, name)
// If revision signature is found, query storage based on this .push(undefined, function (error) {
steps = Number(name.slice(steps_loc + 2)); if (error.status_code === 404 &&
name = name.slice(0, steps_loc); error instanceof jIO.util.jIOError) {
return findAttachment(substorage, name, metadata_query, steps); throw new jIO.util.jIOError(
}); "HistoryStorage: cannot find object '" + id + "'",
404
);
}
throw error;
});
}
return substorage.get(id)
.push(function (result) {
if (result.op === "putAttachment") {
return substorage.getAttachment(id, result.name);
}
throw new jIO.util.jIOError(
"HistoryStorage: cannot find object '" + id + "' (removed)",
404
);
},
function (error) {
if (error.status_code === 404 &&
error instanceof jIO.util.jIOError) {
throw new jIO.util.jIOError(
"HistoryStorage: cannot find object '" + id + "'",
404
);
}
throw error;
})
.push(undefined, function (error) {
if (error.status_code === 404 &&
error instanceof jIO.util.jIOError) {
throw new jIO.util.jIOError(
"HistoryStorage: cannot find object '" + id + "'",
404
);
}
throw error;
});
});
}; };
HistoryStorage.prototype.removeAttachment = function (id, name) { HistoryStorage.prototype.removeAttachment = function (id, name) {
...@@ -294,6 +310,7 @@ ...@@ -294,6 +310,7 @@
name: name, name: name,
op: "removeAttachment" op: "removeAttachment"
}; };
this._timestamps[id][name].push(timestamp);
return this._sub_storage.put(timestamp, metadata); return this._sub_storage.put(timestamp, metadata);
}; };
HistoryStorage.prototype.repair = function () { HistoryStorage.prototype.repair = function () {
...@@ -328,19 +345,20 @@ ...@@ -328,19 +345,20 @@
query_obj = options.query, query_obj = options.query,
query_stack = [], query_stack = [],
ind; ind;
if (query_obj.hasOwnProperty("query_list")) {
if (query_obj instanceof ComplexQuery) {
query_stack.push(query_obj); query_stack.push(query_obj);
} else { } else {
rev_query = (query_obj.key === "_REVISION"); rev_query = (query_obj.key === "_timestamp");
} }
// Traverse through query tree to find mentions of _REVISION // Traverse through query tree to find mentions of _timestamp
// and stop as soon as it is found once // and stop as soon as it is found once
while (query_stack.length > 0 && (!rev_query)) { while (query_stack.length > 0 && (!rev_query)) {
query_obj = query_stack.pop(); query_obj = query_stack.pop();
for (ind = 0; ind < query_obj.query_list.length; ind += 1) { for (ind = 0; ind < query_obj.query_list.length; ind += 1) {
if (query_obj.query_list[ind].hasOwnProperty("query_list")) { if (query_obj.query_list[ind].hasOwnProperty("query_list")) {
query_stack.push(query_obj.query_list[ind]); query_stack.push(query_obj.query_list[ind]);
} else if (query_obj.query_list[ind].key === "_REVISION") { } else if (query_obj.query_list[ind].key === "_timestamp") {
rev_query = true; rev_query = true;
break; break;
} }
...@@ -363,95 +381,50 @@ ...@@ -363,95 +381,50 @@
}); });
return RSVP.all(promises); return RSVP.all(promises);
}) })
.push(function (results) { .push(function (results) {
// Label all documents with their current revision status var seen = {},
var docum, query_matches,
revision_tracker = {}, docs_to_query;
latest_rev_query, // If !rev_query, then by default only consider latest revisions of
results_reduced; // documents
for (ind = 0; ind < results.length; ind += 1) { results = results.filter(function (docum) {
docum = results[ind]; if (rev_query) {
if (revision_tracker.hasOwnProperty(docum.doc_id)) { return docum.op === "put";
revision_tracker[docum.doc_id] += 1;
} else {
revision_tracker[docum.doc_id] = 0;
} }
if (docum.op === "remove") { if (!seen.hasOwnProperty(docum.doc_id)) {
docum.doc = {}; seen[docum.doc_id] = {};
return docum.op === "put";
} }
return false;
// Add op and _REVISION to the docum.doc (temporarily) so the });
// document can be matched manually with the inputted query docs_to_query = results.map(function (docum) {
results[ind].doc._REVISION = revision_tracker[docum.doc_id]; // If it's a "remove" operation
results[ind].doc.op = docum.op; if (!docum.hasOwnProperty("doc")) {
} docum.doc = {};
// Create a new query to only get non-removed revisions and abide by
// whatever the inputted query says
latest_rev_query = jIO.QueryFactory.create(
"(_REVISION: >= 0) AND (op: put)"
);
// If query does not use _REVISION, then by default set _REVISION := 0
if (rev_query) {
latest_rev_query.query_list[0] = options.query;
} else {
latest_rev_query.query_list[0] = jIO.QueryFactory.create(
"(_REVISION: = 0)"
);
// Check if options.query is nonempty
if (options.query.type === "simple" ||
options.query.type === "complex") {
latest_rev_query.query_list.push(options.query);
} }
} docum.doc._doc_id = docum.doc_id;
//return results docum.doc._timestamp = docum.timestamp;
results_reduced = results return docum.doc;
// Only return results which match latest_rev_query });
.filter(function (docum) { options.select_list.push("_doc_id");
var filtered_res = latest_rev_query.match(docum.doc); query_matches = options.query.exec(docs_to_query, options);
return query_matches;
// Remove extra metadata used in revision query })
delete docum.doc.op; // Format the results of the query, and return
delete docum.doc._REVISION; .push(function (query_matches) {
return filtered_res; return query_matches.map(function (docum) {
}); var doc_id = docum._doc_id;
return results_reduced delete docum._timestamp;
delete docum._doc_id;
// Only return the correct range of valid results specified by return {
// options.limit doc: {},
.filter(function (doc, ind) { value: docum,
if (doc && options.hasOwnProperty("limit")) { id: doc_id
return (ind >= options.limit[0] && };
options.limit[1] + options.limit[0] > ind); });
}
return true;
})
// Return certain attributes in .val as specified by
// options.select_list
.map(function (current_doc) {
var val = {},
ind,
key;
for (ind = 0; ind < options.select_list.length; ind += 1) {
key = options.select_list[ind];
if (current_doc.doc.hasOwnProperty(key)) {
val[key] = current_doc.doc[key];
}
}
// Format results to be expected output of allDocs
return {
doc: current_doc.doc,
value: val,
id: current_doc.doc_id
};
});
}); });
}; };
jIO.addStorage('history', HistoryStorage); jIO.addStorage('history', HistoryStorage);
}(jIO, RSVP)); }(jIO, RSVP, SimpleQuery, ComplexQuery));
\ No newline at end of file \ No newline at end of file
...@@ -24,182 +24,20 @@ ...@@ -24,182 +24,20 @@
///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////
// historyStorage.querying_from_historystorage // Attachments
///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////
/**
module("HistoryStorage.querying_from_historystorage");
test("verifying the correct results are returned from historyStorage.allDocs",
function () {
stop();
expect(10);
module("HistoryStorage.attachments", {
setup: function () {
// create storage of type "history" with memory as substorage // create storage of type "history" with memory as substorage
var jio = jIO.createJIO({ var dbname = "db_" + Date.now();
this.blob1 = new Blob(['a']);
this.blob2 = new Blob(['b']);
this.blob3 = new Blob(['ccc']);
this.other_blob = new Blob(['1']);
this.jio = jIO.createJIO({
type: "history", type: "history",
sub_storage: { sub_storage: {
type: "uuid",
sub_storage: {
type: "memory"
}
}
});
jio.put("bar", {"title": "foo0"})
.push(function () {
return RSVP.all([
jio.remove("bar"),
jio.put("bar", {"title": "foo1"}),
jio.put("bar", {"title": "foo2"}),
jio.put("bar", {"title": "foo3"}),
jio.put("barbar", {"title": "attr0"}),
jio.put("barbar", {"title": "attr1"}),
jio.put("barbar", {"title": "attr2"}),
jio.put("barbarbar", {"title": "val0"}),
jio.put("barbarbarbar", {"title": "prop0"})
]);
})
// Make two final puts so we know what to expect as the current state of
// each document.
.push(function () {
return jio.put("barbar", {"title": "attr3"});
})
.push(function () {
return jio.put("bar", {"title": "foo4"});
})
// Queries should only include information about the final two versions
.push(function () {
return jio.allDocs({
query: "",
sort_on: [["title", "ascending"]]
});
})
.push(function (results) {
equal(results.data.rows.length,
4,
"Empty query yields four results since there are four unique docs");
return jio.get(results.data.rows[0].id);
},
function (error) {
return ok(false, "Query failed: " + error);
})
.push(function (result) {
deepEqual(result, {
title: "attr3"
},
"NOT IMPLEMENTED: Retrieve correct sort order with no metadata");
},
function () {
return ok(false, "Couldn't find document in storage");
})
// Querying with a limit
.push(function () {
return jio.allDocs({
query: "",
sort_on: [["title", "ascending"]],
limit: [0, 1]
});
})
.push(function (results) {
equal(results.data.rows.length,
1,
"Since limit [0,1] was supplied, only 1st document is returned");
return jio.get(results.data.rows[0].id);
})
.push(function (result) {
deepEqual(result, {
title: "attr3"
},
"NOT IMPLEMENTED: retrieving documents in specified sort_on order");
})
// Querying with a more complicated limit
.push(function () {
return jio.allDocs({
query: "",
sort_on: [["title", "ascending"]],
limit: [2, 2]
});
})
.push(function (results) {
equal(results.data.rows.length,
2,
"Retrieving the correct documents when options.limit is specified");
deepEqual(results.data.rows[0].id,
"barbarbarbar",
"NOT IMPLEMENTED: retrieving documents in specified sort_on order");
deepEqual(results.data.rows[1].id,
"barbarbar",
"NOT IMPLEMENTED: retrieving documents in specified sort_on order");
return jio.get(results.data.rows[0].id);
})
.push(function (result) {
deepEqual(result, {
title: "property0"
},
"NOT IMPLEMENTED: retrieving documents in specified sort_on order");
})
// Querying for a specific id
.push(function () {
return jio.allDocs({
query: "id: bar"
});
})
.push(function (results) {
equal(results.data.rows.length,
1,
"NOT IMPLEMENTED: query involving specific document attributes");
return jio.get(results.data.rows[0].id);
})
.push(function (result) {
deepEqual(result, {
title: "foo4"
},
"NOT IMPLEMENTED: query involving specific document attributes");
},
function () {
ok(false,
"NOT IMPLEMENTED: query involving specific document attributes"
);
})
.fail(function (error) {
//console.log(error);
ok(false, error);
})
.always(function () {start(); });
});
**/
/////////////////////////////////////////////////////////////////
// Attachments
/////////////////////////////////////////////////////////////////
module("HistoryStorage.attachments");
test("Testing proper adding/removing attachments",
function () {
stop();
expect(26);
// create storage of type "history" with memory as substorage
var dbname = "db_" + Date.now(),
jio = jIO.createJIO({
type: "history",
sub_storage: {
type: "query",
sub_storage: {
type: "uuid",
sub_storage: {
type: "indexeddb",
database: dbname
}
}
}
}),
not_history = jIO.createJIO({
type: "query", type: "query",
sub_storage: { sub_storage: {
type: "uuid", type: "uuid",
...@@ -208,24 +46,42 @@ ...@@ -208,24 +46,42 @@
database: dbname database: dbname
} }
} }
}), }
});
this.not_history = jIO.createJIO({
type: "query",
sub_storage: {
type: "uuid",
sub_storage: {
type: "indexeddb",
database: dbname
}
}
});
}
});
test("Testing proper adding/removing attachments",
function () {
stop();
expect(7);
var jio = this.jio,
timestamps = this.jio.__storage._timestamps,
blob2 = this.blob2,
blob1 = this.blob1,
other_blob = this.other_blob;
blob1 = new Blob(['a']),
blob2 = new Blob(['b']),
blob3 = new Blob(['ccc']),
other_blob = new Blob(['1']);
jio.put("doc", {title: "foo0"}) jio.put("doc", {title: "foo0"})
.push(function () { .push(function () {
return jio.put("doc2", {key: "val"}); return jio.put("doc2", {key: "val"});
}) })
.push(function () { .push(function () {
return jio.putAttachment("doc", "attached", blob1); return jio.putAttachment("doc", "attacheddata", blob1);
}) })
.push(function () { .push(function () {
return jio.putAttachment("doc", "attached", blob2); return jio.putAttachment("doc", "attacheddata", blob2);
}) })
.push(function () { .push(function () {
return jio.putAttachment("doc", "other_attached", other_blob); return jio.putAttachment("doc", "other_attacheddata", other_blob);
}) })
.push(function () { .push(function () {
return jio.get("doc"); return jio.get("doc");
...@@ -234,14 +90,17 @@ ...@@ -234,14 +90,17 @@
deepEqual(result, { deepEqual(result, {
title: "foo0" title: "foo0"
}, "Get does not return any attachment/revision information"); }, "Get does not return any attachment/revision information");
return jio.getAttachment("doc", "attached"); return jio.getAttachment("doc", "attacheddata");
}) })
.push(function (result) { .push(function (result) {
deepEqual(result, deepEqual(result,
blob2, blob2,
"Return the attachment information with getAttachment" "Return the attachment information with getAttachment"
); );
return jio.getAttachment("doc", "attached_-0"); return jio.getAttachment(
timestamps.doc.attacheddata[1],
"attacheddata"
);
}) })
.push(function (result) { .push(function (result) {
deepEqual(result, deepEqual(result,
...@@ -249,8 +108,12 @@ ...@@ -249,8 +108,12 @@
"Return the attachment information with getAttachment for " + "Return the attachment information with getAttachment for " +
"current revision" "current revision"
); );
return jio.getAttachment("doc", "attached_-1"); return jio.getAttachment(
timestamps.doc.attacheddata[0],
"attacheddata"
);
}, function (error) { }, function (error) {
//console.log(error);
ok(false, error); ok(false, error);
}) })
.push(function (result) { .push(function (result) {
...@@ -259,7 +122,7 @@ ...@@ -259,7 +122,7 @@
"Return the attachment information with getAttachment for " + "Return the attachment information with getAttachment for " +
"previous revision" "previous revision"
); );
return jio.getAttachment("doc", "attached_-2"); return jio.getAttachment(timestamps.doc[0], "attached");
}, function (error) { }, function (error) {
ok(false, error); ok(false, error);
}) })
...@@ -270,22 +133,56 @@ ...@@ -270,22 +133,56 @@
ok(error instanceof jIO.util.jIOError, "Correct type of error"); ok(error instanceof jIO.util.jIOError, "Correct type of error");
deepEqual(error.status_code, deepEqual(error.status_code,
404, 404,
"Error if you try to go back more revisions than what exists"); "Error if you try to go back to a nonexistent timestamp");
return jio.getAttachment("doc", "other_attached"); return jio.getAttachment("doc", "other_attacheddata");
}) })
.push(function (result) { .push(function (result) {
deepEqual(result, deepEqual(result,
other_blob, other_blob,
"Other document successfully queried" "Other document successfully queried"
); );
})
.fail(function (error) {
//console.log(error);
ok(false, error);
})
.always(function () {start(); });
});
test("Correctness of allAttachments method",
function () {
stop();
expect(11);
var jio = this.jio,
not_history = this.not_history,
blob1 = this.blob1,
blob2 = this.blob2,
blob3 = this.blob3,
other_blob = this.other_blob;
jio.put("doc", {title: "foo0"})
.push(function () {
return jio.put("doc2", {key: "val"});
})
.push(function () {
return jio.putAttachment("doc", "attacheddata", blob1);
})
.push(function () {
return jio.putAttachment("doc", "attacheddata", blob2);
})
.push(function () {
return jio.putAttachment("doc", "other_attacheddata", other_blob);
})
.push(function () {
return jio.allAttachments("doc"); return jio.allAttachments("doc");
}) })
.push(function (results) { .push(function (results) {
deepEqual(results, { deepEqual(results, {
"attached": {}, "attacheddata": blob2,
"other_attached": {} "other_attacheddata": other_blob
}, "allAttachments works as expected."); }, "allAttachments works as expected.");
return jio.removeAttachment("doc", "attached"); return jio.removeAttachment("doc", "attacheddata");
}) })
.push(function () { .push(function () {
return jio.get("doc"); return jio.get("doc");
...@@ -293,8 +190,8 @@ ...@@ -293,8 +190,8 @@
.push(function (result) { .push(function (result) {
deepEqual(result, { deepEqual(result, {
title: "foo0" title: "foo0"
}, "Get does not return any attachment information (9)"); }, "Get does not return any attachment information");
return jio.getAttachment("doc", "attached"); return jio.getAttachment("doc", "attacheddata");
}) })
.push(function () { .push(function () {
ok(false, "This query should have thrown a 404 error"); ok(false, "This query should have thrown a 404 error");
...@@ -303,14 +200,14 @@ ...@@ -303,14 +200,14 @@
ok(error instanceof jIO.util.jIOError, "Correct type of error"); ok(error instanceof jIO.util.jIOError, "Correct type of error");
deepEqual(error.status_code, deepEqual(error.status_code,
404, 404,
"Removed attachments cannot be queried"); "Removed attachments cannot be queried (4)");
return jio.allAttachments("doc"); return jio.allAttachments("doc");
}) })
.push(function (results) { .push(function (results) {
deepEqual(results, { deepEqual(results, {
"other_attached": {} "other_attacheddata": blob2
}, "allAttachments works as expected with a removed attachment"); }, "allAttachments works as expected with a removed attachment");
return jio.putAttachment("doc", "attached", blob3); return jio.putAttachment("doc", "attacheddata", blob3);
}) })
.push(function () { .push(function () {
return not_history.allDocs(); return not_history.allDocs();
...@@ -328,68 +225,18 @@ ...@@ -328,68 +225,18 @@
{timestamp: results[1].timestamp, {timestamp: results[1].timestamp,
doc_id: "doc2", doc: results[1].doc, op: "put"}, doc_id: "doc2", doc: results[1].doc, op: "put"},
{timestamp: results[2].timestamp, {timestamp: results[2].timestamp,
doc_id: "doc", name: "attached", op: "putAttachment"}, doc_id: "doc", name: "attacheddata", op: "putAttachment"},
{timestamp: results[3].timestamp, {timestamp: results[3].timestamp,
doc_id: "doc", name: "attached", op: "putAttachment"}, doc_id: "doc", name: "attacheddata", op: "putAttachment"},
{timestamp: results[4].timestamp, {timestamp: results[4].timestamp,
doc_id: "doc", name: "other_attached", op: "putAttachment"}, doc_id: "doc", name: "other_attacheddata", op: "putAttachment"},
{timestamp: results[5].timestamp, {timestamp: results[5].timestamp,
doc_id: "doc", name: "attached", op: "removeAttachment"}, doc_id: "doc", name: "attacheddata", op: "removeAttachment"},
{timestamp: results[6].timestamp, {timestamp: results[6].timestamp,
doc_id: "doc", name: "attached", op: "putAttachment"} doc_id: "doc", name: "attacheddata", op: "putAttachment"}
], "Other storage can access all document revisions." ], "Other storage can access all document revisions."
); );
}) })
.push(function () {
return jio.getAttachment("doc", "attached");
})
.push(function (result) {
deepEqual(result,
blob3,
"Return the attachment information with getAttachment"
);
return jio.getAttachment("doc", "attached_-0");
})
.push(function (result) {
deepEqual(result,
blob3,
"Return the attachment information with getAttachment"
);
return jio.getAttachment("doc", "attached_-1");
})
.push(function () {
ok(false, "This query should have thrown a 404 error");
},
function (error) {
ok(error instanceof jIO.util.jIOError, "Correct type of error");
deepEqual(error.status_code,
404,
"Error if you try to go back to a removed attachment state");
return jio.getAttachment("doc", "attached_-2");
})
.push(function (result) {
deepEqual(result,
blob2,
"Return the attachment information with getAttachment (17)"
);
return jio.getAttachment("doc", "attached_-3");
})
.push(function (result) {
deepEqual(result,
blob1,
"Return the attachment information with getAttachment"
);
return jio.getAttachment("doc", "attached_-4");
})
.push(function () {
ok(false, "This query should have thrown a 404 error");
},
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");
})
.push(function () { .push(function () {
return jio.allDocs(); return jio.allDocs();
}) })
...@@ -412,11 +259,11 @@ ...@@ -412,11 +259,11 @@
})); }));
}) })
.push(function (results) { .push(function (results) {
equal(results.length, 7, "Seven document revisions in storage (24)"); equal(results.length, 7, "Seven document revisions in storage (17)");
return jio.remove("doc"); return jio.remove("doc");
}) })
.push(function () { .push(function () {
return jio.getAttachment("doc", "attached"); return jio.getAttachment("doc", "attacheddata");
}) })
.push(function () { .push(function () {
ok(false, "This query should have thrown a 404 error"); ok(false, "This query should have thrown a 404 error");
...@@ -436,196 +283,177 @@ ...@@ -436,196 +283,177 @@
///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////
// Accessing older revisions // Querying older revisions
///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////
module("HistoryStorage.getting_and_putting"); module("HistoryStorage.get", {
test("Testing proper retrieval of older revisions of documents", setup: function () {
function () {
stop();
expect(18);
// create storage of type "history" with memory as substorage // create storage of type "history" with memory as substorage
var jio = jIO.createJIO({ var dbname = "db_" + Date.now();
this.jio = jIO.createJIO({
type: "history", type: "history",
sub_storage: { sub_storage: {
type: "query", type: "query",
sub_storage: { sub_storage: {
type: "uuid", type: "uuid",
sub_storage: { sub_storage: {
type: "memory" type: "indexeddb",
database: dbname
} }
} }
} }
}); });
jio.get("doc") this.not_history = jIO.createJIO({
.push(function () { type: "query",
ok(false, "This query should have thrown a 404 error"); sub_storage: {
}, type: "uuid",
function (error) { sub_storage: {
deepEqual(error.status_code, type: "indexeddb",
404, database: dbname
"Document does not exist yet."); }
}) }
.push(function () { });
return jio.put("doc", { }
"k0": "v0" });
});
}) test("Handling bad input",
function () {
stop();
expect(2);
var jio = this.jio,
BADINPUT_ERRCODE = 422;
jio.put("doc", {
"_timestamp": 3,
"other_attr": "other_val"
})
.push(function () { .push(function () {
return jio.get("doc_-0"); ok(false, "This statement should not be reached");
}) }, function (error) {
.push(function (result) { ok(error instanceof jIO.util.jIOError, "Correct type of error");
deepEqual(result, { deepEqual(error.status_code,
"k0": "v0" BADINPUT_ERRCODE,
}); "Can't save a document with a reserved keyword"
);
}) })
.push(function () { .fail(function (error) {
return jio.put("doc", {"k1": "v1"}); //console.log(error);
ok(false, error);
}) })
.always(function () {start(); });
});
test("Creating a document with put and retrieving it with get",
function () {
stop();
expect(4);
var jio = this.jio,
not_history = this.not_history,
timestamps = jio.__storage._timestamps;
jio.put("doc", {title: "version0"})
.push(function () { .push(function () {
return jio.get("doc_-0"); ok(timestamps.hasOwnProperty("doc"),
"jio._timestamps is updated with new document.");
equal(timestamps.doc.length,
1,
"One revision is logged in jio._timestamps"
);
return jio.get(timestamps.doc[0]);
}) })
.push(function (result) { .push(function (result) {
deepEqual(result, { deepEqual(result, {
"k1": "v1" title: "version0"
}); }, "Get document from history storage");
}) return not_history.get(
.push(function () { timestamps.doc[0]
return jio.get("doc_-1"); );
}) })
.push(function (result) { .push(function (result) {
deepEqual(result, { deepEqual(result, {
"k0": "v0" timestamp: timestamps.doc[0],
}); op: "put",
doc_id: "doc",
doc: {
title: "version0"
}
}, "Get document from non-history storage");
})
.fail(function (error) {
//console.log(error);
ok(false, error);
}) })
.always(function () {start(); });
});
test("Retrieving older revisions with get",
function () {
stop();
expect(7);
var jio = this.jio,
timestamps = this.jio.__storage._timestamps;
return jio.put("doc", {title: "t0", subtitle: "s0"})
.push(function () { .push(function () {
return jio.put("doc", {"k2": "v2"}); return jio.put("doc", {title: "t1", subtitle: "s1"});
}) })
.push(function () { .push(function () {
return jio.remove("doc"); return jio.put("doc", {title: "t2", subtitle: "s2"});
}) })
.push(function () { .push(function () {
return jio.put("doc", {"k3": "v3"}); jio.remove("doc");
}) })
.push(function () { .push(function () {
return jio.put("doc", {"k4": "v4"}); return jio.put("doc", {title: "t3", subtitle: "s3"});
}) })
.push(function () { .push(function () {
return jio.get("doc"); return jio.get("doc");
}) })
.push(function (result) { .push(function (result) {
deepEqual(result, deepEqual(result, {
{"k4": "v4"}, title: "t3",
"By default, .get returns latest revision"); subtitle: "s3"
return jio.get("doc"); }, "Get returns latest revision");
return jio.get(timestamps.doc[0]);
}) })
.push(function (result) { .push(function (result) {
deepEqual(result, deepEqual(result, {
{"k4": "v4"}, title: "t0",
".get returns latest revision with second input = 0"); subtitle: "s0"
return jio.get("doc_-1"); }, "Get returns first version");
return jio.get(timestamps.doc[1]);
}) })
.push(function (result) { .push(function (result) {
deepEqual(result, deepEqual(result, {
{"k3": "v3"}, title: "t1",
"Walk back one revision with second input = 1"); subtitle: "s1"
return jio.get("doc_-2"); }, "Get returns second version");
}) return jio.get(timestamps.doc[2]);
.push(function () {
ok(false, "This query should have thrown a 404 error");
},
function (error) {
deepEqual(error.status_code,
404,
"Current state of document is 'removed'.");
return jio.get("doc_-3");
})
.push(function (result) {
deepEqual(result,
{"k2": "v2"},
"Walk back three revisions with second input = 3");
return jio.get("doc_-4");
}) })
.push(function (result) { .push(function (result) {
deepEqual(result, deepEqual(result, {
{"k1": "v1"}, title: "t2",
"Walk back four revisions with second input = 4"); subtitle: "s2"
return jio.get("doc_-5"); }, "Get returns third version");
}) return jio.get(timestamps.doc[3]);
.push(function (result) {
deepEqual(result,
{"k0": "v0"},
"Walk back five revisions with second input = 5");
return jio.get("doc_-6");
}) })
.push(function () { .push(function () {
ok(false, "This query should have thrown a 404 error"); ok(false, "This should have thrown a 404 error");
return jio.get(timestamps.doc[4]);
}, },
function (error) { function (error) {
ok(error instanceof jIO.util.jIOError, "Correct type of error");
deepEqual(error.status_code, deepEqual(error.status_code,
404, 404,
"There are only 5 previous states of this document"); "Error if you try to go back more revisions than what exists");
return jio.get(timestamps.doc[4]);
}) })
// Adding documents with problematic doc_id's
.push(function () {
return jio.put("doc_-name", {
"key": "val0"
});
})
.push(function () {
return jio.put("document_-0", {
"key": "and val0"
});
})
.push(function () {
return jio.put("doc_-name", {
"key": "val1"
});
})
.push(function () {
return jio.get("doc_-name");
})
.push(function (result) {
deepEqual(result, {
"key": "val1"
});
return jio.get("doc_-name_-0");
})
.push(function (result) {
deepEqual(result, {
"key": "val1"
});
return jio.get("doc_-name_-1");
})
.push(function (result) { .push(function (result) {
deepEqual(result, { deepEqual(result, {
"key": "val0" title: "t3",
}); subtitle: "s3"
return jio.get("document_-0"); }, "Get returns latest version");
})
.push(function (result) {
deepEqual(result, {
"key": "and val0"
});
return jio.get("document_-0_-0");
}) })
.push(function (result) {
deepEqual(result, {
"key": "and val0"
});
return jio.get("document_-0_-1");
})
.push(function () {
ok(false, "This query should have thrown a 404 error");
},
function (error) {
deepEqual(error.status_code,
404,
"Document does not have this many revisions.");
})
.fail(function (error) { .fail(function (error) {
//console.log(error); //console.log(error);
ok(false, error); ok(false, error);
...@@ -633,39 +461,12 @@ ...@@ -633,39 +461,12 @@
.always(function () {start(); }); .always(function () {start(); });
}); });
test("verifying updates correctly when puts are done in parallel", test("verifying updates correctly when puts are done in parallel",
function () { function () {
stop(); stop();
expect(7); expect(8);
var jio = this.jio,
// create storage of type "history" with memory as substorage not_history = this.not_history;
var dbname = "rsvp_db_" + Date.now(),
jio = jIO.createJIO({
type: "history",
sub_storage: {
type: "query",
sub_storage: {
type: "uuid",
sub_storage: {
//type: "memory"
type: "indexeddb",
database: dbname
}
}
}
}),
not_history = jIO.createJIO({
type: "query",
sub_storage: {
type: "uuid",
sub_storage: {
//type: "memory"
type: "indexeddb",
database: dbname
}
}
});
jio.put("bar", {"title": "foo0"}) jio.put("bar", {"title": "foo0"})
.push(function () { .push(function () {
...@@ -713,11 +514,8 @@ ...@@ -713,11 +514,8 @@
.push(function () { .push(function () {
return jio.get("barbar"); return jio.get("barbar");
}, function (error) { }, function (error) {
deepEqual( ok(error instanceof jIO.util.jIOError, "Correct type of error");
error.message, equal(error.status_code, 404, "Correct error status code returned");
"HistoryStorage: cannot find object 'bar' (removed)",
"Appropriate error is sent explaining object has been removed"
);
return jio.get("barbar"); return jio.get("barbar");
}) })
.push(function (result) { .push(function (result) {
...@@ -747,268 +545,420 @@ ...@@ -747,268 +545,420 @@
.always(function () {start(); }); .always(function () {start(); });
}); });
///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////
// Querying older revisions // Querying older revisions
///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////
module("HistoryStorage.allDocs"); module("HistoryStorage.allDocs", {
test("Testing retrieval of older revisions via allDocs calls", setup: function () {
function () {
stop();
expect(42);
// create storage of type "history" with memory as substorage // create storage of type "history" with memory as substorage
var jio = jIO.createJIO({ var dbname = "db_" + Date.now();
this.jio = jIO.createJIO({
type: "history", type: "history",
sub_storage: { sub_storage: {
type: "query", type: "query",
sub_storage: { sub_storage: {
type: "uuid", type: "uuid",
sub_storage: { sub_storage: {
type: "memory" type: "indexeddb",
database: dbname
} }
} }
} }
}); });
jio.put("doc", { this.not_history = jIO.createJIO({
"k": "v0" type: "query",
}) sub_storage: {
.push(function () { type: "uuid",
return jio.put("doc", { sub_storage: {
"k": "v1" type: "indexeddb",
}); database: dbname
}) }
}
});
}
});
test("Putting a document and retrieving it with allDocs",
function () {
stop();
expect(7);
var jio = this.jio,
not_history = this.not_history;
jio.put("doc", {title: "version0"})
.push(function () { .push(function () {
return jio.put("doc", { return RSVP.all([
"k": "v2" jio.allDocs(),
}); jio.allDocs({query: "title: version0"}),
jio.allDocs({limit: [0, 1]}),
jio.allDocs({})
]);
}) })
.push(function () { .push(function (results) {
return jio.put("doc", { var ind = 0;
"k": "v3" for (ind = 0; ind < results.length - 1; ind += 1) {
}); deepEqual(results[ind],
results[ind + 1],
"Each query returns exactly the same correct output"
);
}
return results[0];
}) })
.push(function () { .push(function (results) {
return jio.allDocs({ equal(results.data.rows.length,
query: "_REVISION : 0" 1,
}); "Exactly one result returned");
deepEqual(results.data.rows[0], {
doc: {},
value: {},
id: "doc"
},
"Correct document format is returned."
);
return not_history.allDocs();
}) })
.push(function (results) { .push(function (results) {
deepEqual(results.data.rows.length, equal(results.data.rows.length,
1, 1,
"Only one query returned with options.revision_limit == [0,1]"); "Exactly one result returned");
return jio.get(results.data.rows[0].id); return not_history.get(results.data.rows[0].id);
}) })
.push(function (result) { .push(function (result) {
deepEqual(result, { deepEqual(result, {
"k": "v3" timestamp: jio.__storage._timestamps.doc[0],
}, "One correct result."); doc_id: "doc",
doc: {
title: "version0"
},
op: "put"
},
"When a different type of storage queries historystorage, all " +
"metadata is returned correctly"
);
})
.fail(function (error) {
//console.log(error);
ok(false, error);
}) })
.always(function () {start(); });
});
test("Putting a document, revising it, and retrieving revisions with allDocs",
function () {
stop();
expect(13);
var context = this,
timestamps;
context.jio.put("doc", {
title: "version0",
subtitle: "subvers0"
})
.push(function () { .push(function () {
return jio.allDocs({ return context.jio.put("doc", {
query: "_REVISION : =1" title: "version1",
subtitle: "subvers1"
}); });
}) })
.push(function (results) { .push(function () {
deepEqual(results.data.rows.length, timestamps = context.jio.__storage._timestamps.doc;
1, return context.jio.put("doc", {
"Only one query returned with options.revision_limit == [1,1]"); title: "version2",
deepEqual(results.data.rows[0].doc, { subtitle: "subvers2"
"k": "v2"
}, "One correct result.");
return jio.allDocs({
query: "_REVISION : =2"
}); });
}) })
.push(function (results) { .push(function () {
deepEqual(results.data.rows.length,
1, return RSVP.all([
"Only one query returned with options.revision_limit == [2,1]"); context.jio.allDocs({select_list: ["title", "subtitle"]}),
deepEqual(results.data.rows[0].doc, { context.jio.allDocs({
"k": "v1" query: "",
}); select_list: ["title", "subtitle"]
return jio.allDocs({ }),
query: "_REVISION : =3" context.jio.allDocs({
}); query: "title: version2",
select_list: ["title", "subtitle"]
}),
context.jio.allDocs({
query: "NOT (title: version1)",
select_list: ["title", "subtitle"]
}),
context.jio.allDocs({
query: "(NOT (subtitle: subvers1)) AND (NOT (title: version0))",
select_list: ["title", "subtitle"]
}),
context.jio.allDocs({
limit: [0, 1],
sort_on: [["title", "ascending"]],
select_list: ["title", "subtitle"]
})
]);
}) })
.push(function (results) { .push(function (results) {
deepEqual(results.data.rows.length, var ind = 0;
1, for (ind = 0; ind < results.length - 1; ind += 1) {
"Only one query returned with options.revision_limit == [3,1]"); deepEqual(results[ind],
deepEqual(results.data.rows[0].doc, { results[ind + 1],
"k": "v0" "Each query returns exactly the same correct output"
}); );
return jio.allDocs({ }
query: "_REVISION : =4" return results[0];
});
}) })
.push(function (results) { .push(function (results) {
equal(results.data.rows.length, 0, "Past all previous revisions"); equal(results.data.rows.length,
1,
"Exactly one result returned");
deepEqual(results.data.rows[0], {
value: {
title: "version2",
subtitle: "subvers2"
},
doc: {},
id: "doc"
},
"Correct document format is returned."
);
}) })
.push(function () { .push(function () {
return jio.allDocs({ return RSVP.all([
query: "_REVISION: <= 1" context.jio.allDocs({
}); query: "_timestamp: " + timestamps[1],
select_list: ["title", "subtitle"]
}),
context.jio.allDocs({
query: "_timestamp: =" + timestamps[1],
select_list: ["title", "subtitle"]
}),
context.jio.allDocs({
query: "_timestamp: >" + timestamps[0] + " AND title: version1",
select_list: ["title", "subtitle"]
}),
context.jio.allDocs({
query: "_timestamp: > " + timestamps[0] + " AND _timestamp: < " +
timestamps[2],
select_list: ["title", "subtitle"]
})
]);
}) })
.push(function (results) { .push(function (results) {
equal(results.data.rows.length, 2, equal(results[0].data.rows.length,
"Only retrieve two most recent revions"); 1,
deepEqual(results.data.rows[0].doc, { "Querying a specific timestamp retrieves one document");
"k": "v3" var ind = 0;
}, "First retrieved revision is correct"); for (ind = 0; ind < results.length - 1; ind += 1) {
deepEqual(results.data.rows[1].doc, { deepEqual(results[ind],
"k": "v2" results[ind + 1],
}, "Second retrieved revision is correct"); "Each query returns exactly the same correct output"
}) );
.push(function () { }
return jio.remove("doc"); return results[0];
}) })
.push(function () { .push(function (results) {
return jio.allDocs({ deepEqual(results.data.rows[0], {
query: "NOT (_REVISION: >= 1)", id: "doc",
revision_limit: [0, 1] value: {
title: "version1",
subtitle: "subvers1"
},
doc: {}
});
return context.not_history.allDocs({
sort_on: [["title", "ascending"]]
}); });
}) })
.push(function (results) { .push(function (results) {
equal(results.data.rows.length, 0, return RSVP.all(results.data.rows.map(function (d) {
"Query does not return removed doc"); return context.not_history.get(d.id);
}) }));
.push(function () {
return jio.allDocs({
query: "(_REVISION: >= 1) AND (_REVISION: <= 3)"
});
}) })
.push(function (results) { .push(function (results) {
equal(results.data.rows.length, 3); deepEqual(results, [
deepEqual(results.data.rows[0].doc, { {
"k": "v3" timestamp: timestamps[0],
}, "1st, 2nd, and 3rd versions removed from current are retrieved"); op: "put",
deepEqual(results.data.rows[1].doc, { doc_id: "doc",
"k": "v2" doc: {
}, "1st, 2nd, and 3rd versions removed from current are retrieved"); title: "version0",
deepEqual(results.data.rows[2].doc, { subtitle: "subvers0"
"k": "v1" }
}, "1st, 2nd, and 3rd versions removed from current are retrieved"); },
{
timestamp: timestamps[1],
op: "put",
doc_id: "doc",
doc: {
title: "version1",
subtitle: "subvers1"
}
},
{
timestamp: timestamps[2],
op: "put",
doc_id: "doc",
doc: {
title: "version2",
subtitle: "subvers2"
}
}
],
"A different storage type can retrieve all versions as expected.");
})
.fail(function (error) {
//console.log(error);
ok(false, error);
}) })
.always(function () {start(); });
});
test(
"Putting and removing documents, latest revisions and no removed documents",
function () {
stop();
expect(5);
var history = this.jio,
timestamps = this.jio.__storage._timestamps;
history.put("doc_a", {
title_a: "rev0",
subtitle_a: "subrev0"
})
.push(function () { .push(function () {
return jio.put("doc2", { return history.put("doc_a", {
"k2": "w0" title_a: "rev1",
subtitle_a: "subrev1"
}); });
}) })
.push(function () { .push(function () {
return jio.allDocs({ return history.put("doc_b", {
query: "(_REVISION: >0) AND (_REVISION: <= 3)" title_b: "rev0",
subtitle_b: "subrev0"
}); });
}) })
.push(function (results) { .push(function () {
equal(results.data.rows.length, 3); return history.remove("doc_b");
deepEqual(results.data.rows[0].doc, {
"k": "v3"
}, "Does not retrieve new document outside queried revision range");
deepEqual(results.data.rows[1].doc, {
"k": "v2"
}, "Does not retrieve new document outside queried revision range");
deepEqual(results.data.rows[2].doc, {
"k": "v1"
}, "Does not retrieve new document outside queried revision range");
}) })
.push(function () { .push(function () {
return jio.allDocs({ return history.put("doc_c", {
query: "(_REVISION: = 0) OR (_REVISION: = 1)" title_c: "rev0",
subtitle_c: "subrev0"
}); });
}) })
.push(function (results) {
equal(results.data.rows.length, 2);
deepEqual(results.data.rows[0].doc, {
"k2": "w0"
}, "Retrieves all documents with versions in queried revision range");
deepEqual(results.data.rows[1].doc, {
"k": "v3"
}, "Retrieves all documents with versions in queried revision range");
})
.push(function () { .push(function () {
return jio.put("doc2", { return history.put("doc_c", {
"k2": "w1" title_c: "rev1",
subtitle_c: "subrev1"
}); });
}) })
.push(function () { .push(function () {
return jio.allDocs(); return history.allDocs({sort_on: [["timestamp", "descending"]]});
}) })
.push(function (results) { .push(function (results) {
equal(results.data.rows.length, 1, equal(results.data.rows.length,
"There is only one non-removed doc."); 2,
deepEqual(results.data.rows[0].doc, { "Only two non-removed unique documents exist."
"k2": "w1" );
}, "Returned the one correct document."); deepEqual(results.data.rows, [
{
id: "doc_c",
value: {},
doc: {}
},
{
id: "doc_a",
value: {},
doc: {}
}
],
"Empty query returns latest revisions (and no removed documents)");
equal(timestamps.doc_a.length,
2,
"Correct number of revisions logged in doc_a");
equal(timestamps.doc_b.length,
2,
"Correct number of revisions logged in doc_b");
equal(timestamps.doc_c.length,
2,
"Correct number of revisions logged in doc_c");
}) })
.push(function () { .fail(function (error) {
return jio.remove("doc2"); //console.log(error);
ok(false, error);
}) })
.always(function () {start(); });
}
);
/////////////////////////////////////////////////////////////////
// Complex Queries
/////////////////////////////////////////////////////////////////
test("More complex query with different options (without revision queries)",
function () {
stop();
expect(2);
var history = this.jio,
docs = [
{
"date": 1,
"type": "foo",
"title": "doc"
},
{
"date": 2,
"type": "bar",
"title": "second_doc"
},
{
"date": 2,
"type": "barbar",
"title": "third_doc"
}
],
blobs = [
new Blob(['a']),
new Blob(['bcd']),
new Blob(['eeee'])
];
history.put("doc", {})
.push(function () { .push(function () {
return jio.allDocs({ return putFullDoc(history, "doc", docs[0], "data", blobs[0]);
query:
"_REVISION: 0 OR _REVISION: 1 OR " +
"(_REVISION: >= 2 AND _REVISION: <= 3)"
});
}) })
.push(function (results) { .push(function () {
equal(results.data.rows.length, 5); return putFullDoc(history, "second_doc", docs[1], "data", blobs[1]);
deepEqual(results.data.rows[0].doc, {
"k2": "w1"
});
deepEqual(results.data.rows[1].doc, {
"k2": "w0"
});
deepEqual(results.data.rows[2].doc, {
"k": "v3"
});
deepEqual(results.data.rows[3].doc, {
"k": "v2"
});
deepEqual(results.data.rows[4].doc, {
"k": "v1"
});
}) })
.push(function () { .push(function () {
return jio.allDocs({ return putFullDoc(history, "third_doc", docs[2], "data", blobs[2]);
query: "_REVISION: <= 3",
limit: [1, 4]
});
}) })
.push(function (results) { .push(function () {
equal(results.data.rows.length, 4, return history.allDocs({
"Correct number of results with options.limit set"); query: "(date: <= 2)",
deepEqual(results.data.rows[0].doc, { select_list: ["date", "non-existent-key"],
"k2": "w0" sort_on: [["date", "ascending"],
}, "Correct results with options.limit set"); ["non-existent-key", "ascending"]
deepEqual(results.data.rows[1].doc, { ]
"k": "v3"
}, "Correct results with options.limit set");
deepEqual(results.data.rows[2].doc, {
"k": "v2"
}, "Correct results with options.limit set");
deepEqual(results.data.rows[3].doc, {
"k": "v1"
}, "Correct results with options.limit set");
return jio.allDocs({
query: "_REVISION: = 1",
select_list: ["k"]
}); });
}) })
.push(function (results) { .push(function (results) {
equal(results.data.rows.length, 2); equal(results.data.rows.length, 3);
deepEqual(results.data.rows[0].doc, { deepEqual(results.data.rows, [
"k2": "w1" {
}); doc: {},
deepEqual(results.data.rows[0].value, {}); id: "doc",
deepEqual(results.data.rows[1].doc, { value: {date: 1}
"k": "v3" },
}); {
deepEqual(results.data.rows[1].value, { doc: {},
"k": "v3" id: "third_doc",
}); value: {date: 2}
},
{
doc: {},
id: "second_doc",
value: {date: 2}
}
],
"Query gives correct results in correct order");
}) })
.fail(function (error) { .fail(function (error) {
//console.log(error); //console.log(error);
...@@ -1018,67 +968,244 @@ ...@@ -1018,67 +968,244 @@
}); });
///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////
// Complex Queries // Complex Queries with Revision Querying
///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////
//module("HistoryStorage.complex_queries"); test("More complex query with different options (with revision queries)",
test("More complex queries with select_list option",
function () { function () {
stop(); stop();
expect(3); expect(3);
var jio = this.jio,
// create storage of type "history" with memory as substorage not_history = this.not_history,
var jio = jIO.createJIO({ timestamps = this.jio.__storage._timestamps,
type: "history", docs = [
sub_storage: { {
type: "query", "date": 1,
sub_storage: { "type": "foo",
type: "uuid", "title": "doc"
sub_storage: { },
type: "memory" {
} "date": 2,
"type": "bar",
"title": "second_doc"
} }
} ],
}), blobs = [
doc = { new Blob(['a']),
"modification_date": "a", new Blob(['bcd']),
"portal_type": "Foo", new Blob(['a2']),
"title": "foo_module/1" new Blob(['bcd2']),
}, new Blob(['a3'])
blob = new Blob(['a']); ];
putFullDoc(jio, "foo_module/1", doc, "data", blob) jio.put("doc", {})
.push(function () { .push(function () {
return jio.get("foo_module/1"); return putFullDoc(jio, "doc", docs[0], "data", blobs[0]);
}) })
.push(function (result) { .push(function () {
deepEqual(result, { return putFullDoc(jio, "second_doc", docs[1], "data", blobs[1]);
"modification_date": "a", })
"portal_type": "Foo", .push(function () {
"title": "foo_module/1" docs[0].date = 4;
}, "Can retrieve a document after attachment placed." docs[0].type = "foo2";
); docs[1].date = 4;
docs[1].type = "bar2";
})
.push(function () {
return putFullDoc(jio, "doc", docs[0], "data", blobs[2]);
})
.push(function () {
return jio.remove("second_doc");
})
.push(function () {
return putFullDoc(jio, "second_doc", docs[1], "data", blobs[3]);
})
.push(function () {
return not_history.allDocs({
sort_on: [["timestamp", "descending"]],
select_list: ["op", "doc_id", "timestamp"]
});
})
.push(function (results) {
deepEqual(results.data.rows, [
{
doc: {},
id: timestamps.second_doc.data[1],
value: {
"op": "putAttachment",
"doc_id": "second_doc",
"timestamp": timestamps.second_doc.data[1]
}
},
{
doc: {},
id: timestamps.second_doc[2],
value: {
"op": "put",
"doc_id": "second_doc",
"timestamp": timestamps.second_doc[2]
}
},
{
doc: {},
id: timestamps.second_doc[1],
value: {
"op": "remove",
"doc_id": "second_doc",
"timestamp": timestamps.second_doc[1]
}
},
{
doc: {},
id: timestamps.doc.data[1],
value: {
"op": "putAttachment",
"doc_id": "doc",
"timestamp": timestamps.doc.data[1]
}
},
{
doc: {},
id: timestamps.doc[2],
value: {
"op": "put",
"doc_id": "doc",
"timestamp": timestamps.doc[2]
}
},
{
doc: {},
id: timestamps.second_doc.data[0],
value: {
"op": "putAttachment",
"doc_id": "second_doc",
"timestamp": timestamps.second_doc.data[0]
}
},
{
doc: {},
id: timestamps.second_doc[0],
value: {
"op": "put",
"doc_id": "second_doc",
"timestamp": timestamps.second_doc[0]
}
},
{
doc: {},
id: timestamps.doc.data[0],
value: {
"op": "putAttachment",
"doc_id": "doc",
"timestamp": timestamps.doc.data[0]
}
},
{
doc: {},
id: timestamps.doc[1],
value: {
"op": "put",
"doc_id": "doc",
"timestamp": timestamps.doc[1]
}
},
{
doc: {},
id: timestamps.doc[0],
value: {
"op": "put",
"doc_id": "doc",
"timestamp": timestamps.doc[0]
}
}
], "All operations are logged correctly");
var promises = results.data.rows
.filter(function (doc) {
return (doc.value.op === "put");
})
.map(function (data) {
return not_history.get(data.id);
});
return RSVP.all(promises)
.then(function (results) {
return results.map(function (docum) {
return docum.doc;
});
});
})
.push(function (results) {
deepEqual(results,
[
{
"date": 4,
"type": "bar2",
"title": "second_doc"
},
{
"date": 4,
"type": "foo2",
"title": "doc"
},
{
"date": 2,
"type": "bar",
"title": "second_doc"
},
{
"date": 1,
"type": "foo",
"title": "doc"
},
{}
], "All versions of documents are stored correctly");
}) })
.push(function () { .push(function () {
return jio.allDocs({ return jio.allDocs({
query: "portal_type: Foo", query: "(_timestamp: >= " + timestamps.second_doc[0] +
select_list: ["modification_date", "__id", "__id"], " OR _timestamp: <= " + timestamps.doc[1] +
sort_on: [["modification_date", "descending"], ") AND NOT (date: = 2)",
["timestamp", "descending"], select_list: ["date", "non-existent-key", "type", "title"],
["timestamp", "descending"] sort_on: [["date", "descending"],
["non-existent-key", "ascending"],
["_timestamp", "ascending"]
] ]
}); });
}) })
.push(function (results) { .push(function (results) {
equal(results.data.rows.length, 1); deepEqual(results.data.rows, [
deepEqual(results.data.rows[0], { {
doc: { doc: {},
"modification_date": "a", id: "doc",
"portal_type": "Foo", value: {
"title": "foo_module/1" date: 4,
title: "doc",
type: "foo2"
}
}, },
id: "foo_module/1", {
value: {modification_date: "a"} doc: {},
}); id: "second_doc",
value: {
date: 4,
title: "second_doc",
type: "bar2"
}
},
{
doc: {},
id: "doc",
value: {
date: 1,
title: "doc",
type: "foo"
}
},
{
doc: {},
id: "doc",
value: {}
}
],
"Query gives correct results in correct order");
}) })
.fail(function (error) { .fail(function (error) {
//console.log(error); //console.log(error);
...@@ -1086,5 +1213,4 @@ ...@@ -1086,5 +1213,4 @@
}) })
.always(function () {start(); }); .always(function () {start(); });
}); });
}(jIO, RSVP, Blob, QUnit)); }(jIO, RSVP, Blob, 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