Commit c6db8c50 authored by Bryan Kaperick's avatar Bryan Kaperick

Made changes to simplify buildQuery revision queries allowing any property names possible.

parent 4306f29f
......@@ -4,7 +4,7 @@
"use strict";
// Used to distinguish between operations done within the same millisecond
var unique_timestamp = function (time) {
function generateUniqueTimestamp(time) {
// XXX: replace this with UUIDStorage function call to S4() when it becomes
// publicly accessible
......@@ -13,15 +13,28 @@
//timestamp = Date.now().toString();
timestamp = time.toString();
return timestamp + "-" + uuid;
},
looks_like_timestamp = function (id) {
//1529928772623-02e6
//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 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 + "'",
404
);
}
function throwRemovedError(id) {
throw new jIO.util.jIOError(
"HistoryStorage: cannot find object '" + id + "' (removed)",
404
);
}
/**
* The jIO HistoryStorage extension
......@@ -31,11 +44,28 @@
*/
function HistoryStorage(spec) {
this._sub_storage = jIO.createJIO(spec.sub_storage);
this._timestamps = {};
}
HistoryStorage.prototype.get = function (id_in) {
if (isTimestamp(id_in)) {
// 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);
}, function (error) {
if (error.status_code === 404 &&
error instanceof jIO.util.jIOError) {
throwRemovedError(id_in);
}
throw error;
});
}
// Query to get the last edit made to this document
var substorage = this._sub_storage,
......@@ -66,65 +96,23 @@
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(
"HistoryStorage: cannot find object '" + id_in + "' (removed)",
404
);
throwRemovedError(id_in);
}
// Try again by treating id_in as a timestamp instead of a name
return substorage.get(id_in)
.push(function (result) {
if (result.op === "put") {
return result.doc;
}
throw new jIO.util.jIOError(
"HistoryStorage: cannot find object '" + id_in +
"' (removed)",
404
);
}, function (error) {
if (error.status_code === 400 &&
error instanceof jIO.util.jIOError) {
throw new jIO.util.jIOError(
"HistoryStorage: cannot find object '" + id_in + "'",
404
);
}
});
throwCantFindError(id_in);
});
};
HistoryStorage.prototype.put = function (id, data) {
if (data.hasOwnProperty("_timestamp")) {
throw new jIO.util.jIOError(
"Document cannot have metadata attribute '_timestamp'",
422
);
}
if (data.hasOwnProperty("_doc_id")) {
throw new jIO.util.jIOError(
"Document cannot have metadata attribute '_doc_id'",
422
);
}
if (looks_like_timestamp(id)) {
if (isTimestamp(id)) {
throw new jIO.util.jIOError(
"Document cannot have id of the same form as a timestamp",
422
);
}
var timestamp = unique_timestamp(Date.now()),
var timestamp = generateUniqueTimestamp(Date.now()),
metadata = {
// XXX: remove this attribute once query can sort_on id
timestamp: timestamp,
......@@ -132,60 +120,102 @@
doc: data,
op: "put"
};
if (this._timestamps.hasOwnProperty(id)) {
this._timestamps[id].push(timestamp);
} else {
this._timestamps[id] = [timestamp];
}
return this._sub_storage.put(timestamp, metadata);
};
HistoryStorage.prototype.remove = function (id) {
var timestamp = unique_timestamp(Date.now() - 1),
var timestamp = generateUniqueTimestamp(Date.now() - 1),
metadata = {
// XXX: remove this attribute once query can sort_on id
timestamp: timestamp,
doc_id: id,
op: "remove"
};
this._timestamps[id].push(timestamp);
return this._sub_storage.put(timestamp, metadata);
};
HistoryStorage.prototype.allAttachments = function (id) {
// XXX: If instead you passed a timestamp in as `id`, we could retrieve all
// the attachments of the document at that point in time. Not sure if this
// would be useful.
// XXX: allAttachments with timestamp:
// should return all non-removed attachments at this point in time
var substorage = this._sub_storage,
// Include id as value in query object for safety (as opposed to string
// concatenation)
query_obj = new ComplexQuery({
operator: "AND",
query_list: [
new SimpleQuery({key: "doc_id", value: id}),
new ComplexQuery({
operator: "OR",
query_list: [
new SimpleQuery({key: "op", value: "putAttachment"}),
new SimpleQuery({key: "op", value: "removeAttachment"})
]
})
]
}),
query_obj,
query_removed_check,
options,
query_doc_id,
options_remcheck;
// Only query for attachment edits
options = {
query: query_obj,
sort_on: [["timestamp", "descending"]],
select_list: ["op", "timestamp", "name"]
};
return this._sub_storage.allDocs(options)
if (isTimestamp(id)) {
query_doc_id = new SimpleQuery({
operator: "<=",
key: "timestamp",
value: id
});
} else {
query_doc_id = new SimpleQuery({key: "doc_id", value: id});
}
query_removed_check = new ComplexQuery({
operator: "AND",
query_list: [
query_doc_id,
new ComplexQuery({
operator: "OR",
query_list: [
new SimpleQuery({key: "op", value: "put"}),
new SimpleQuery({key: "op", value: "remove"})
]
})
]
});
query_obj = new ComplexQuery({
operator: "AND",
query_list: [
query_doc_id,
new ComplexQuery({
operator: "OR",
query_list: [
new SimpleQuery({key: "op", value: "putAttachment"}),
new SimpleQuery({key: "op", value: "removeAttachment"})
]
})
]
});
options_remcheck = {
query: query_removed_check,
select_list: ["op", "timestamp"],
//limit: [0, 1],
sort_on: [["timestamp", "descending"]]
};
options = {
query: query_obj,
sort_on: [["timestamp", "descending"]],
select_list: ["op", "name"]
};
return this._sub_storage.allDocs(options_remcheck)
.push(function (results) {
if (results.data.total_rows > 0) {
if (results.data.rows[0].value.op === "remove") {
throwRemovedError(id);
}
} else {
throwCantFindError(id);
}
})
.push(function () {
return substorage.allDocs(options);
})
.push(function (results) {
var seen = {},
attachments = [],
attachment_promises = [],
ind,
entry;
// Only return attachments if:
// (it is the most recent revision) AND (it is a putAttachment)
attachments = results.data.rows.filter(function (docum) {
if (!seen.hasOwnProperty(docum.value.name)) {
var output = (docum.value.op === "putAttachment");
......@@ -193,17 +223,19 @@
return output;
}
});
// Assembles object of attachment_name: attachment_object
for (ind = 0; ind < attachments.length; ind += 1) {
entry = attachments[ind];
attachment_promises[entry.value.name] =
substorage.getAttachment(entry.id, entry.value.name);
}
return RSVP.hash(attachment_promises);
});
};
HistoryStorage.prototype.putAttachment = function (id, name, blob) {
var timestamp = unique_timestamp(Date.now()),
var timestamp = generateUniqueTimestamp(Date.now()),
metadata = {
// XXX: remove this attribute once query can sort_on id
timestamp: timestamp,
......@@ -212,11 +244,6 @@
op: "putAttachment"
},
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)
.push(function () {
return substorage.putAttachment(timestamp, name, blob);
......@@ -225,6 +252,17 @@
HistoryStorage.prototype.getAttachment = function (id, name) {
if (isTimestamp(id)) {
return this._sub_storage.getAttachment(id, name)
.push(undefined, function (error) {
if (error.status_code === 404 &&
error instanceof jIO.util.jIOError) {
throwCantFindError(id);
}
throw error;
});
}
// Query to get the last edit made to this document
var substorage = this._sub_storage,
......@@ -268,39 +306,16 @@
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(
"HistoryStorage: cannot find object '" + id + "' (removed)",
404
);
throwRemovedError(id);
}
return substorage.getAttachment(results.data.rows[0].id, name)
.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;
});
return substorage.getAttachment(results.data.rows[0].id, name);
}
return substorage.getAttachment(id, name)
.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;
});
throwCantFindError(id);
});
};
HistoryStorage.prototype.removeAttachment = function (id, name) {
var timestamp = unique_timestamp(Date.now()),
var timestamp = generateUniqueTimestamp(Date.now()),
metadata = {
// XXX: remove this attribute once query can sort_on id
timestamp: timestamp,
......@@ -308,7 +323,6 @@
name: name,
op: "removeAttachment"
};
this._timestamps[id][name].push(timestamp);
return this._sub_storage.put(timestamp, metadata);
};
HistoryStorage.prototype.repair = function () {
......@@ -319,49 +333,27 @@
};
HistoryStorage.prototype.buildQuery = function (options) {
// XXX: if include_revisions, we should also include the document results
// for different edits of attachments
// Set default values
if (options === undefined) {
options = {};
}
if (options.query === undefined) {
options.query = "";
}
if (options.sort_on === undefined) {
options.sort_on = [];
}
if (options.select_list === undefined) {
options.select_list = [];
if (options === undefined) {options = {}; }
if (options.query === undefined) {options.query = ""; }
if (options.sort_on === undefined) {options.sort_on = []; }
if (options.select_list === undefined) {options.select_list = []; }
if (options.include_revisions === undefined) {
options.include_revisions = false;
}
options.sort_on.push(["timestamp", "descending"]);
options.query = jIO.QueryFactory.create(options.query);
var meta_options,
substorage = this._sub_storage,
// Check if query involved _REVISION. If not, we will later place a
// (*) AND (_REVISION: =0) as the default handling of revisions
rev_query = false,
query_obj = options.query,
query_stack = [],
ind;
if (query_obj instanceof ComplexQuery) {
query_stack.push(query_obj);
} else {
rev_query = (query_obj.key === "_timestamp");
}
// Traverse through query tree to find mentions of _timestamp
// and stop as soon as it is found once
while (query_stack.length > 0 && (!rev_query)) {
query_obj = query_stack.pop();
for (ind = 0; ind < query_obj.query_list.length; ind += 1) {
if (query_obj.query_list[ind].hasOwnProperty("query_list")) {
query_stack.push(query_obj.query_list[ind]);
} else if (query_obj.query_list[ind].key === "_timestamp") {
rev_query = true;
break;
}
}
}
// Check if query involved _timestamp.
// If not, use default behavior and only query on latest revisions
rev_query = options.include_revisions,
doc_id_name,
timestamp_name;
// Query for all edits putting or removing documents (and nothing about
// attachments)
......@@ -382,7 +374,8 @@
.push(function (results) {
var seen = {},
query_matches,
docs_to_query;
docs_to_query,
i;
// If !rev_query, then by default only consider latest revisions of
// documents
results = results.filter(function (docum) {
......@@ -395,29 +388,47 @@
}
return false;
});
// If any documents have property _doc_id, __doc_id, etc, then set
// doc_id_name to the first string which is not a property of any
// of the documents
doc_id_name = "_doc_id";
timestamp_name = "_timestamp";
for (i = 0; i < results.length; i += 1) {
while (results[i].doc.hasOwnProperty(doc_id_name)) {
doc_id_name = "_" + doc_id_name;
}
while (results[i].doc.hasOwnProperty(timestamp_name)) {
timestamp_name = "_" + timestamp_name;
}
}
docs_to_query = results.map(function (docum) {
// If it's a "remove" operation
// If it's a "remove" operation then it has no doc property
if (!docum.hasOwnProperty("doc")) {
docum.doc = {};
}
docum.doc._doc_id = docum.doc_id;
docum.doc._timestamp = docum.timestamp;
docum.doc[doc_id_name] = docum.doc_id;
docum.doc[timestamp_name] = docum.timestamp;
return docum.doc;
});
options.select_list.push("_doc_id");
options.select_list.push(doc_id_name);
options.select_list.push(timestamp_name);
query_matches = options.query.exec(docs_to_query, options);
return query_matches;
})
// Format the results of the query, and return
.push(function (query_matches) {
return query_matches.map(function (docum) {
var doc_id = docum._doc_id;
delete docum._timestamp;
delete docum._doc_id;
var doc_id = docum[doc_id_name],
time = docum[timestamp_name];
delete docum[timestamp_name];
delete docum[doc_id_name];
return {
doc: {},
value: docum,
id: doc_id
id: doc_id,
timestamp: time
};
});
});
......
......@@ -64,36 +64,47 @@
test("Testing proper adding/removing attachments",
function () {
stop();
expect(9);
expect(10);
var jio = this.jio,
timestamps = this.jio.__storage._timestamps,
not_history = this.not_history,
timestamps,
blob2 = this.blob2,
blob1 = this.blob1,
other_blob = this.other_blob,
otherother_blob = new Blob(['abcabc']);
jio.put("doc", {title: "foo0"})
jio.put("doc", {title: "foo0"}) // 0
.push(function () {
return jio.put("doc2", {key: "val"});
return jio.put("doc2", {key: "val"}); // 1
})
.push(function () {
return jio.putAttachment("doc", "attacheddata", blob1);
return jio.putAttachment("doc", "attacheddata", blob1); // 2
})
.push(function () {
return jio.putAttachment("doc", "attacheddata", blob2);
return jio.putAttachment("doc", "attacheddata", blob2); // 3
})
.push(function () {
return jio.putAttachment("doc", "other_attacheddata", other_blob);
return jio.putAttachment("doc", "other_attacheddata", other_blob);// 4
})
.push(function () {
return jio.putAttachment(
return jio.putAttachment( // 5
"doc",
"otherother_attacheddata",
otherother_blob
);
})
.push(function () {
return jio.removeAttachment("doc", "otherother_attacheddata");
return jio.removeAttachment("doc", "otherother_attacheddata"); // 6
})
.push(function () {
return not_history.allDocs({
sort_on: [["timestamp", "ascending"]]
});
})
.push(function (results) {
timestamps = results.data.rows.map(function (d) {
return d.id;
});
})
.push(function () {
return jio.get("doc");
......@@ -110,7 +121,7 @@
"Return the attachment information with getAttachment"
);
return jio.getAttachment(
timestamps.doc.attacheddata[1],
timestamps[3],
"attacheddata"
);
})
......@@ -121,7 +132,7 @@
"current revision"
);
return jio.getAttachment(
timestamps.doc.attacheddata[0],
timestamps[2],
"attacheddata"
);
}, function (error) {
......@@ -134,7 +145,7 @@
"Return the attachment information with getAttachment for " +
"previous revision"
);
return jio.getAttachment(timestamps.doc[0], "attached");
return jio.getAttachment(timestamps[0], "attached");
}, function (error) {
ok(false, error);
})
......@@ -146,6 +157,9 @@
deepEqual(error.status_code,
404,
"Error if you try to go back to a nonexistent timestamp");
deepEqual(error.message,
"HistoryStorage: cannot find object '" + timestamps[0] + "'",
"Error caught by history storage correctly");
return jio.getAttachment("doc", "other_attacheddata");
})
.push(function (result) {
......@@ -208,10 +222,10 @@
.always(function () {start(); });
});
test("Correctness of allAttachments method",
test("Correctness of allAttachments method on current attachments",
function () {
stop();
expect(11);
expect(14);
var jio = this.jio,
not_history = this.not_history,
blob1 = this.blob1,
......@@ -317,7 +331,7 @@
}));
})
.push(function (results) {
equal(results.length, 7, "Seven document revisions in storage (17)");
equal(results.length, 7, "Seven document revisions in storage");
return jio.remove("doc");
})
.push(function () {
......@@ -332,6 +346,132 @@
404,
"Cannot get the attachment of a removed document");
})
.push(function () {
return jio.allAttachments("doc");
})
.push(function () {
ok(false, "This query should have thrown a 404 error");
},
function (error) {
ok(error instanceof jIO.util.jIOError, "throws a jio error");
deepEqual(error.status_code,
404,
"allAttachments of a removed document throws a 404 error");
deepEqual(error.message,
"HistoryStorage: cannot find object 'doc' (removed)",
"Error is handled by Historystorage.");
})
.fail(function (error) {
//console.log(error);
ok(false, error);
})
.always(function () {start(); });
});
test("Correctness of allAttachments method on older revisions",
function () {
stop();
expect(8);
var jio = this.jio,
not_history = this.not_history,
blob1 = new Blob(['a']),
blob11 = new Blob(['ab']),
blob2 = new Blob(['abc']),
blob22 = new Blob(['abcd']),
timestamps;
jio.put("doc", {title: "foo0"}) // 0
.push(function () {
return jio.putAttachment("doc", "data", blob1);
})
.push(function () {
return jio.putAttachment("doc", "data2", blob2);
})
.push(function () {
return jio.put("doc", {title: "foo1"}); // 1
})
.push(function () {
return jio.removeAttachment("doc", "data2");
})
.push(function () {
return jio.put("doc", {title: "foo2"}); // 2
})
.push(function () {
return jio.putAttachment("doc", "data", blob11);
})
.push(function () {
return jio.remove("doc"); // 3
})
.push(function () {
return jio.put("doc", {title: "foo3"}); // 4
})
.push(function () {
return jio.putAttachment("doc", "data2", blob22);
})
.push(function () {
return not_history.allDocs({
query: "op: put OR op: remove",
sort_on: [["timestamp", "ascending"]],
select_list: ["timestamp"]
});
})
.push(function (results) {
timestamps = results.data.rows.map(function (d) {
return d.value.timestamp;
});
})
.push(function () {
return jio.allAttachments("doc");
})
.push(function (results) {
deepEqual(results, {
"data": blob11,
"data2": blob22
},
"Current state of document is correct");
return jio.allAttachments(timestamps[0]);
})
.push(function (results) {
deepEqual(results, {}, "First version of document has 0 attachments");
return jio.allAttachments(timestamps[1]);
})
.push(function (results) {
deepEqual(results, {
data: blob1,
data2: blob2
}, "Both attachments are included in allAttachments");
return jio.allAttachments(timestamps[2]);
})
.push(function (results) {
deepEqual(results, {
data: blob1
}, "Removed attachment does not show up in allAttachments");
return jio.allAttachments(timestamps[3]);
})
.push(function () {
ok(false, "This query should have thrown a 404 error");
},
function (error) {
ok(error instanceof jIO.util.jIOError, "throws a jio error");
deepEqual(error.status_code,
404,
"allAttachments of a removed document throws a 404 error");
deepEqual(error.message,
"HistoryStorage: cannot find object '" + timestamps[3] +
"' (removed)",
"Error is handled by Historystorage.");
})
.push(function () {
return jio.allAttachments(timestamps[4]);
})
.push(function (results) {
deepEqual(results, {
data: blob11
});
})
.fail(function (error) {
//console.log(error);
ok(false, error);
......@@ -377,49 +517,48 @@
test("Handling bad input",
function () {
stop();
expect(6);
expect(2);
var jio = this.jio,
BADINPUT_ERRCODE = 422;
jio.put("doc", {
"_timestamp": 3,
"other_attr": "other_val"
})
jio.put("1234567891123-ab7d", {})
.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,
BADINPUT_ERRCODE,
"Can't save a document with a reserved keyword"
"Can't save a document with a timestamp-formatted id"
);
})
.push(function () {
return jio.put("doc", {
"_doc_id": 3,
"other_attr": "other_val"
});
})
.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,
BADINPUT_ERRCODE,
"Can't save a document with a reserved keyword"
);
.fail(function (error) {
//console.log(error);
ok(false, error);
})
.always(function () {start(); });
});
test("Getting a non-existent document",
function () {
stop();
expect(3);
var jio = this.jio;
jio.put("not_doc", {})
.push(function () {
return jio.put("1234567891123-ab7d", {});
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);
......@@ -431,31 +570,39 @@
test("Creating a document with put and retrieving it with get",
function () {
stop();
expect(4);
expect(7);
var jio = this.jio,
not_history = this.not_history,
timestamps = jio.__storage._timestamps;
timestamps;
jio.put("doc", {title: "version0"})
.push(function () {
ok(timestamps.hasOwnProperty("doc"),
"jio._timestamps is updated with new document.");
equal(timestamps.doc.length,
return not_history.allDocs({
select_list: ["timestamp"]
});
})
.push(function (results) {
timestamps = results.data.rows.map(function (d) {
return d.value.timestamp;
});
})
.push(function () {
equal(timestamps.length,
1,
"One revision is logged in jio._timestamps"
"One revision is saved in storage"
);
return jio.get(timestamps.doc[0]);
return jio.get(timestamps[0]);
})
.push(function (result) {
deepEqual(result, {
title: "version0"
}, "Get document from history storage");
return not_history.get(
timestamps.doc[0]
timestamps[0]
);
})
.push(function (result) {
deepEqual(result, {
timestamp: timestamps.doc[0],
timestamp: timestamps[0],
op: "put",
doc_id: "doc",
doc: {
......@@ -463,6 +610,31 @@
}
}, "Get document from non-history storage");
})
.push(function () {
return jio.get("non-existent-doc");
})
.push(function () {
ok(false, "This should have thrown an error");
}, function (error) {
//console.log(error);
ok(error instanceof jIO.util.jIOError, "Correct type of error");
deepEqual(error.status_code,
404,
"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);
......@@ -475,7 +647,8 @@
stop();
expect(7);
var jio = this.jio,
timestamps = this.jio.__storage._timestamps;
not_history = this.not_history,
timestamps;
return jio.put("doc", {title: "t0", subtitle: "s0"})
.push(function () {
......@@ -490,6 +663,17 @@
.push(function () {
return jio.put("doc", {title: "t3", subtitle: "s3"});
})
.push(function () {
return not_history.allDocs({
select_list: ["timestamp"],
sort_on: [["timestamp", "ascending"]]
});
})
.push(function (results) {
timestamps = results.data.rows.map(function (d) {
return d.value.timestamp;
});
})
.push(function () {
return jio.get("doc");
})
......@@ -498,7 +682,7 @@
title: "t3",
subtitle: "s3"
}, "Get returns latest revision");
return jio.get(timestamps.doc[0]);
return jio.get(timestamps[0]);
}, function (err) {
ok(false, err);
})
......@@ -507,14 +691,14 @@
title: "t0",
subtitle: "s0"
}, "Get returns first version");
return jio.get(timestamps.doc[1]);
return jio.get(timestamps[1]);
})
.push(function (result) {
deepEqual(result, {
title: "t1",
subtitle: "s1"
}, "Get returns second version");
return jio.get(timestamps.doc[2]);
return jio.get(timestamps[2]);
}, function (err) {
ok(false, err);
})
......@@ -523,20 +707,20 @@
title: "t2",
subtitle: "s2"
}, "Get returns third version");
return jio.get(timestamps.doc[3]);
return jio.get(timestamps[3]);
}, function (err) {
ok(false, err);
})
.push(function () {
ok(false, "This should have thrown a 404 error");
return jio.get(timestamps.doc[4]);
return jio.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.doc[4]);
return jio.get(timestamps[4]);
})
.push(function (result) {
deepEqual(result, {
......@@ -674,8 +858,18 @@
stop();
expect(7);
var jio = this.jio,
not_history = this.not_history;
not_history = this.not_history,
timestamp;
jio.put("doc", {title: "version0"})
.push(function () {
return not_history.allDocs({
query: "doc_id: doc",
select_list: ["timestamp"]
});
})
.push(function (results) {
timestamp = results.data.rows[0].value.timestamp;
})
.push(function () {
return RSVP.all([
jio.allDocs(),
......@@ -701,7 +895,8 @@
deepEqual(results.data.rows[0], {
doc: {},
value: {},
id: "doc"
id: "doc",
timestamp: timestamp
},
"Correct document format is returned."
);
......@@ -715,7 +910,7 @@
})
.push(function (result) {
deepEqual(result, {
timestamp: jio.__storage._timestamps.doc[0],
timestamp: timestamp,
doc_id: "doc",
doc: {
title: "version0"
......@@ -733,14 +928,69 @@
.always(function () {start(); });
});
test("Putting doc with _doc_id and _timestamp properties" +
"and retrieving them with allDocs",
function () {
stop();
expect(1);
var jio = this.jio,
not_history = this.not_history,
timestamp;
jio.put("doc", {
title: "version0",
_doc_id: "bar",
__doc_id: "bar2",
___doc_id: "bar3",
_timestamp: "foo",
____timestamp: "foo2"
})
.push(function () {
return not_history.allDocs({
query: "doc_id: doc",
select_list: ["timestamp"]
});
})
.push(function (results) {
timestamp = results.data.rows[0].value.timestamp;
})
.push(function () {
return jio.allDocs({
query: "title: version0 AND _timestamp: >= 0",
select_list: ["title", "_doc_id", "__doc_id", "___doc_id",
"_timestamp", "____timestamp"]
});
})
.push(function (results) {
deepEqual(results.data.rows, [
{
doc: {},
id: "doc",
value: {
title: "version0",
_doc_id: "bar",
__doc_id: "bar2",
___doc_id: "bar3",
_timestamp: "foo",
____timestamp: "foo2"
},
timestamp: timestamp
}],
"_doc_id properties are not overwritten in allDocs call");
})
.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(14);
expect(10);
var jio = this.jio,
not_history = this.not_history,
timestamps = this.jio.__storage._timestamps;
timestamps;
jio.put("doc", {
title: "version0",
subtitle: "subvers0"
......@@ -757,6 +1007,17 @@
subtitle: "subvers2"
});
})
.push(function () {
return not_history.allDocs({
select_list: ["timestamp"],
sort_on: [["timestamp", "ascending"]]
});
})
.push(function (results) {
timestamps = results.data.rows.map(function (d) {
return d.value.timestamp;
});
})
.push(function () {
return RSVP.all([
jio.allDocs({select_list: ["title", "subtitle"]}),
......@@ -803,73 +1064,52 @@
subtitle: "subvers2"
},
doc: {},
id: "doc"
id: "doc",
timestamp: timestamps[2]
},
"Correct document format is returned."
);
})
.push(function () {
// These are all equivalent queries in that they should return the
// same documents in the correct order
return RSVP.all([
jio.allDocs({
query: "_timestamp: " + timestamps.doc[1],
select_list: ["title", "subtitle"]
}),
jio.allDocs({
query: "_timestamp: =" + timestamps.doc[1],
select_list: ["title", "subtitle"]
}),
jio.allDocs({
query: "_timestamp: >" + timestamps.doc[0] +
" AND title: version1",
select_list: ["title", "subtitle"]
}),
jio.allDocs({
query: "_timestamp: > " + timestamps.doc[0] +
" AND _timestamp: < " + timestamps.doc[2],
select_list: ["title", "subtitle"]
})
]);
})
.push(function (results) {
equal(results[0].data.rows.length,
1,
"Querying a specific timestamp retrieves one document");
var ind = 0;
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 (results) {
deepEqual(results.data.rows[0], {
id: "doc",
value: {
title: "version1",
subtitle: "subvers1"
},
doc: {}
});
return jio.allDocs({
query: "_timestamp: " + timestamps.doc[0],
select_list: ["title", "subtitle"]
query: "",
select_list: ["title", "subtitle"],
include_revisions: true
});
})
.push(function (results) {
equal(results.data.rows.length,
3,
"Querying with include_revisions retrieves all versions");
deepEqual(results.data.rows, [
{
id: "doc",
value: {
title: "version2",
subtitle: "subvers2"
},
doc: {},
timestamp: timestamps[2]
},
{
id: "doc",
value: {
title: "version1",
subtitle: "subvers1"
},
doc: {},
timestamp: timestamps[1]
},
{
id: "doc",
value: {
title: "version0",
subtitle: "subvers0"
},
doc: {},
id: "doc"
timestamp: timestamps[0]
}
], "Query requesting one timestamp works.");
], "Full version history is included.");
return not_history.allDocs({
sort_on: [["title", "ascending"]]
......@@ -883,7 +1123,7 @@
.push(function (results) {
deepEqual(results, [
{
timestamp: timestamps.doc[0],
timestamp: timestamps[0],
op: "put",
doc_id: "doc",
doc: {
......@@ -892,7 +1132,7 @@
}
},
{
timestamp: timestamps.doc[1],
timestamp: timestamps[1],
op: "put",
doc_id: "doc",
doc: {
......@@ -901,7 +1141,7 @@
}
},
{
timestamp: timestamps.doc[2],
timestamp: timestamps[2],
op: "put",
doc_id: "doc",
doc: {
......@@ -924,43 +1164,54 @@
"Putting and removing documents, latest revisions and no removed documents",
function () {
stop();
expect(5);
var history = this.jio,
timestamps = this.jio.__storage._timestamps;
expect(3);
var jio = this.jio,
not_history = this.not_history,
timestamps;
history.put("doc_a", {
jio.put("doc_a", {
title_a: "rev0",
subtitle_a: "subrev0"
})
.push(function () {
return history.put("doc_a", {
return jio.put("doc_a", {
title_a: "rev1",
subtitle_a: "subrev1"
});
})
.push(function () {
return history.put("doc_b", {
return jio.put("doc_b", {
title_b: "rev0",
subtitle_b: "subrev0"
});
})
.push(function () {
return history.remove("doc_b");
return jio.remove("doc_b");
})
.push(function () {
return history.put("doc_c", {
return jio.put("doc_c", {
title_c: "rev0",
subtitle_c: "subrev0"
});
})
.push(function () {
return history.put("doc_c", {
return jio.put("doc_c", {
title_c: "rev1",
subtitle_c: "subrev1"
});
})
.push(function () {
return history.allDocs({sort_on: [["timestamp", "descending"]]});
return not_history.allDocs({
sort_on: [["timestamp", "ascending"]]
});
})
.push(function (results) {
timestamps = results.data.rows.map(function (d) {
return d.id;
});
})
.push(function () {
return jio.allDocs({sort_on: [["timestamp", "descending"]]});
})
.push(function (results) {
equal(results.data.rows.length,
......@@ -971,24 +1222,20 @@
{
id: "doc_c",
value: {},
doc: {}
doc: {},
timestamp: timestamps[5]
},
{
id: "doc_a",
value: {},
doc: {}
doc: {},
timestamp: timestamps[1]
}
],
"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");
equal(timestamps.length,
6,
"Correct number of revisions logged");
})
.fail(function (error) {
//console.log(error);
......@@ -1006,7 +1253,9 @@
function () {
stop();
expect(2);
var history = this.jio,
var jio = this.jio,
not_history = this.not_history,
timestamps,
docs = [
{
"date": 1,
......@@ -1029,19 +1278,29 @@
new Blob(['bcd']),
new Blob(['eeee'])
];
history.put("doc", {})
jio.put("doc", {}) // 0
.push(function () {
return putFullDoc(history, "doc", docs[0], "data", blobs[0]);
return putFullDoc(jio, "doc", docs[0], "data", blobs[0]); // 1,2
})
.push(function () {
return putFullDoc(history, "second_doc", docs[1], "data", blobs[1]);
return putFullDoc(jio, "second_doc", docs[1], "data", blobs[1]);// 3,4
})
.push(function () {
return putFullDoc(history, "third_doc", docs[2], "data", blobs[2]);
return putFullDoc(jio, "third_doc", docs[2], "data", blobs[2]); // 5,6
})
.push(function () {
return history.allDocs({
query: "(date: <= 2)",
return not_history.allDocs({
sort_on: [["timestamp", "ascending"]]
});
})
.push(function (results) {
timestamps = results.data.rows.map(function (d) {
return d.id;
});
})
.push(function () {
return jio.allDocs({
query: "NOT (date: > 2)",
select_list: ["date", "non-existent-key"],
sort_on: [["date", "ascending"],
["non-existent-key", "ascending"]
......@@ -1054,17 +1313,20 @@
{
doc: {},
id: "doc",
value: {date: 1}
value: {date: 1},
timestamp: timestamps[1]
},
{
doc: {},
id: "third_doc",
value: {date: 2}
value: {date: 2},
timestamp: timestamps[5]
},
{
doc: {},
id: "second_doc",
value: {date: 2}
value: {date: 2},
timestamp: timestamps[3]
}
],
"Query gives correct results in correct order");
......@@ -1086,7 +1348,7 @@
expect(3);
var jio = this.jio,
not_history = this.not_history,
timestamps = this.jio.__storage._timestamps,
timestamps,
docs = [
{
"date": 1,
......@@ -1106,11 +1368,11 @@
new Blob(['bcd2']),
new Blob(['a3'])
];
jio.put("doc", {})
.push(function () {
jio.put("doc", {})// 0
.push(function () {// 1,2
return putFullDoc(jio, "doc", docs[0], "data", blobs[0]);
})
.push(function () {
.push(function () {// 3,4
return putFullDoc(jio, "second_doc", docs[1], "data", blobs[1]);
})
.push(function () {
......@@ -1119,15 +1381,25 @@
docs[1].date = 4;
docs[1].type = "bar2";
})
.push(function () {
.push(function () {// 5,6
return putFullDoc(jio, "doc", docs[0], "data", blobs[2]);
})
.push(function () {
.push(function () {// 7
return jio.remove("second_doc");
})
.push(function () {
.push(function () {// 8,9
return putFullDoc(jio, "second_doc", docs[1], "data", blobs[3]);
})
.push(function () {
return not_history.allDocs({
sort_on: [["timestamp", "ascending"]]
});
})
.push(function (results) {
timestamps = results.data.rows.map(function (d) {
return d.id;
});
})
.push(function () {
return not_history.allDocs({
sort_on: [["timestamp", "descending"]],
......@@ -1138,92 +1410,92 @@
deepEqual(results.data.rows, [
{
doc: {},
id: timestamps.second_doc.data[1],
id: timestamps[9],
value: {
"op": "putAttachment",
"doc_id": "second_doc",
"timestamp": timestamps.second_doc.data[1]
"timestamp": timestamps[9]
}
},
{
doc: {},
id: timestamps.second_doc[2],
id: timestamps[8],
value: {
"op": "put",
"doc_id": "second_doc",
"timestamp": timestamps.second_doc[2]
"timestamp": timestamps[8]
}
},
{
doc: {},
id: timestamps.second_doc[1],
id: timestamps[7],
value: {
"op": "remove",
"doc_id": "second_doc",
"timestamp": timestamps.second_doc[1]
"timestamp": timestamps[7]
}
},
{
doc: {},
id: timestamps.doc.data[1],
id: timestamps[6],
value: {
"op": "putAttachment",
"doc_id": "doc",
"timestamp": timestamps.doc.data[1]
"timestamp": timestamps[6]
}
},
{
doc: {},
id: timestamps.doc[2],
id: timestamps[5],
value: {
"op": "put",
"doc_id": "doc",
"timestamp": timestamps.doc[2]
"timestamp": timestamps[5]
}
},
{
doc: {},
id: timestamps.second_doc.data[0],
id: timestamps[4],
value: {
"op": "putAttachment",
"doc_id": "second_doc",
"timestamp": timestamps.second_doc.data[0]
"timestamp": timestamps[4]
}
},
{
doc: {},
id: timestamps.second_doc[0],
id: timestamps[3],
value: {
"op": "put",
"doc_id": "second_doc",
"timestamp": timestamps.second_doc[0]
"timestamp": timestamps[3]
}
},
{
doc: {},
id: timestamps.doc.data[0],
id: timestamps[2],
value: {
"op": "putAttachment",
"doc_id": "doc",
"timestamp": timestamps.doc.data[0]
"timestamp": timestamps[2]
}
},
{
doc: {},
id: timestamps.doc[1],
id: timestamps[1],
value: {
"op": "put",
"doc_id": "doc",
"timestamp": timestamps.doc[1]
"timestamp": timestamps[1]
}
},
{
doc: {},
id: timestamps.doc[0],
id: timestamps[0],
value: {
"op": "put",
"doc_id": "doc",
"timestamp": timestamps.doc[0]
"timestamp": timestamps[0]
}
}
], "All operations are logged correctly");
......@@ -1269,14 +1541,13 @@
})
.push(function () {
return jio.allDocs({
query: "(_timestamp: >= " + timestamps.second_doc[0] +
" OR _timestamp: <= " + timestamps.doc[1] +
") AND NOT (date: = 2)",
query: "NOT (date: >= 2 AND date: <= 3)",
select_list: ["date", "non-existent-key", "type", "title"],
sort_on: [["date", "descending"],
["non-existent-key", "ascending"],
["_timestamp", "ascending"]
]
],
include_revisions: true
});
})
.push(function (results) {
......@@ -1288,7 +1559,8 @@
date: 4,
title: "doc",
type: "foo2"
}
},
timestamp: timestamps[5]
},
{
doc: {},
......@@ -1297,7 +1569,8 @@
date: 4,
title: "second_doc",
type: "bar2"
}
},
timestamp: timestamps[8]
},
{
doc: {},
......@@ -1306,12 +1579,14 @@
date: 1,
title: "doc",
type: "foo"
}
},
timestamp: timestamps[1]
},
{
doc: {},
id: "doc",
value: {}
value: {},
timestamp: timestamps[0]
}
],
"Query gives correct results in correct order");
......
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