Commit 9cc2f37f authored by Tristan Cavelier's avatar Tristan Cavelier

indexstorage updated to JIO v2

parent 3a82fb20
......@@ -17,7 +17,7 @@
*/
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true, regexp: true */
/*global jIO, define, complex_queries */
/*global window, exports, require, define, jIO, RSVP, complex_queries */
/**
* JIO Index Storage.
......@@ -118,131 +118,23 @@
if (typeof define === 'function' && define.amd) {
return define(dependencies, module);
}
module(jIO, complex_queries);
}(['jio', 'complex_queries'], function (jIO, complex_queries) {
"use strict";
var error_dict = {
"Corrupted Index": {
"status": 24,
"statusText": "Corrupt",
"error": "corrupt",
"reason": "corrupted index database"
},
"Corrupted Metadata": {
"status": 24,
"statusText": "Corrupt",
"error": "corrupt",
"reason": "corrupted document"
},
"Not Found": {
"status": 404,
"statusText": "Not Found",
"error": "not_found",
"reason": "missing document"
},
"Conflict": {
"status": 409,
"statusText": "Conflicts",
"error": "conflicts",
"reason": "already exist"
},
"Different Index": {
"status": 40,
"statusText": "Check failed",
"error": "check_failed",
"reason": "incomplete database"
}
};
/**
* Generate a JIO Error Object
*
* @method generateErrorObject
* @param {String} name The error name
* @param {String} message The error message
* @param {String} [reason] The error reason
* @return {Object} A jIO error object
*/
function generateErrorObject(name, message, reason) {
if (!error_dict[name]) {
return {
"status": 0,
"statusText": "Unknown",
"error": "unknown",
"message": message,
"reason": reason || "unknown"
};
}
return {
"status": error_dict[name].status,
"statusText": error_dict[name].statusText,
"error": error_dict[name].error,
"message": message,
"reason": reason || error_dict[name].reason
};
}
/**
* Get the real type of an object
* @method type
* @param {Any} value The value to check
* @return {String} The value type
*/
function type(value) {
// returns "String", "Object", "Array", "RegExp", ...
return (/^\[object ([a-zA-Z]+)\]$/).exec(
Object.prototype.toString.call(value)
)[1];
}
/**
* Generate a new uuid
* @method generateUuid
* @return {string} The new uuid
*/
function generateUuid() {
var S4 = function () {
var i, string = Math.floor(
Math.random() * 0x10000 /* 65536 */
).toString(16);
for (i = string.length; i < 4; i += 1) {
string = "0" + string;
}
return string;
};
return S4() + S4() + "-" +
S4() + "-" +
S4() + "-" +
S4() + "-" +
S4() + S4() + S4();
}
/**
* Tool to get the date in W3C date format "2011-12-13T14:15:16+01:00"
*
* @param {Any} date The new Date() parameter
* @return {String} The date in W3C date format
*/
function w3cDate(date) {
var d = new Date(date), offset = -d.getTimezoneOffset();
return (
d.getFullYear() + "-" +
(d.getMonth() + 1) + "-" +
d.getDate() + "T" +
d.getHours() + ":" +
d.getMinutes() + ":" +
d.getSeconds() +
(offset < 0 ? "-" : "+") +
(offset / 60) + ":" +
(offset % 60)
).replace(/[0-9]+/g, function (found) {
if (found.length < 2) {
return '0' + found;
}
return found;
});
if (typeof exports === 'object') {
return module(
exports,
require('jio'),
require('rsvp'),
require('complex_queries')
);
}
window.index_storage = {};
module(window.index_storage, jIO, RSVP, complex_queries);
}([
'exports',
'jio',
'rsvp',
'complex_queries'
], function (exports, jIO, RSVP, complex_queries) {
"use strict";
/**
* A JSON Index manipulator
......@@ -314,7 +206,7 @@
*/
that.put = function (meta) {
var k, needed_meta = {}, ok = false;
if (typeof meta._id !== "string" && meta._id !== "") {
if (typeof meta._id !== "string" || meta._id === "") {
throw new TypeError("Corrupted Metadata");
}
for (k in meta) {
......@@ -359,7 +251,8 @@
throw new TypeError("Corrupted Metadata");
}
if (typeof that._location[meta._id] !== "number") {
throw new ReferenceError("Not Found");
// throw new ReferenceError("Not Found");
return;
}
that._database[that._location[meta._id]] = null;
that._free.push(that._location[meta._id]);
......@@ -388,7 +281,8 @@
for (id in that._location) {
if (that._location.hasOwnProperty(id)) {
database_meta = that._database[that._location[id]];
if (type(database_meta) !== "Object" ||
if (typeof database_meta !== 'object' ||
Object.getPrototypeOf(database_meta || []) !== Object.prototype ||
database_meta._id !== id) {
throw new TypeError("Corrupted Index");
}
......@@ -421,8 +315,11 @@
that.checkDocument = function (doc) {
var i, key, db_doc;
if (typeof that._location[doc._id] !== "number" ||
(db_doc = that._database(that._location[doc._id])._id) !== doc._id) {
if (typeof that._location[doc._id] !== "number") {
throw new TypeError("Different Index");
}
db_doc = that._database(that._location[doc._id])._id;
if (db_doc !== doc._id) {
throw new TypeError("Different Index");
}
for (i = 0; i < that._indexing.length; i += 1) {
......@@ -442,12 +339,13 @@
var i = 0, meta;
that._free = [];
that._location = {};
if (type(that._database) !== "Array") {
if (!Array.isArray(that._database)) {
that._database = [];
}
while (i < that._database.length) {
meta = that._database[i];
if (type(meta) === "Object" &&
if (typeof meta === 'object' &&
Object.getPrototypeOf(meta || []) === Object.prototype &&
typeof meta._id === "string" && meta._id !== "" &&
!that._location[meta._id]) {
that._location[meta._id] = i;
......@@ -461,10 +359,10 @@
/**
* Returns the serialized version of this object (not cloned)
*
* @method serialized
* @method toJSON
* @return {Object} The serialized version
*/
that.serialized = function () {
that.toJSON = function () {
return {
"indexing": that._indexing,
"free": that._free,
......@@ -480,46 +378,59 @@
});
}
/**
* The JIO index storage constructor
*/
function indexStorage(spec, my) {
var that, priv = {};
that = my.basicStorage(spec, my);
priv.indices = spec.indices;
priv.sub_storage = spec.sub_storage;
// Overrides
that.specToStore = function () {
return {
"indices": priv.indices,
"sub_storage": priv.sub_storage
};
};
/**
* Return the similarity percentage (1 >= p >= 0) between two index lists.
*
* @method similarityPercentage
* @param {Array} list_a An index list
* @param {Array} list_b Another index list
* @return {Number} The similarity percentage
*/
priv.similarityPercentage = function (list_a, list_b) {
function similarityPercentage(list_a, list_b) {
var ai, bi, count = 0;
for (ai = 0; ai < list_a.length; ai += 1) {
for (bi = 0; bi < list_b.length; bi += 1) {
if (list_a[ai] === list_b[bi]) {
count += 1;
break;
}
}
}
return count / (list_a.length > list_b.length ?
list_a.length : list_b.length);
};
}
/**
* The JIO index storage constructor
*
* @class IndexStorage
* @constructor
*/
function IndexStorage(spec) {
var i;
if (!Array.isArray(spec.indices)) {
throw new TypeError("IndexStorage 'indices' must be an array of " +
"objects.");
}
this._indices = spec.indices;
if (typeof spec.sub_storage !== 'object' ||
Object.getPrototypeOf(spec.sub_storage || []) !== Object.prototype) {
throw new TypeError("IndexStorage 'sub_storage' must be a storage " +
"description.");
}
// check indices IDs
for (i = 0; i < this._indices.length; i += 1) {
if (typeof this._indices[i].id !== "string" ||
this._indices[i].id === "") {
throw new TypeError("IndexStorage " +
"'indices[x].id' must be a non empty string");
}
if (!Array.isArray(this._indices[i].index)) {
throw new TypeError("IndexStorage " +
"'indices[x].index' must be a string array");
}
}
this._sub_storage = spec.sub_storage;
}
/**
* Select the good index to use according to a select list.
......@@ -528,11 +439,10 @@
* @param {Array} select_list An array of strings
* @return {Number} The index index
*/
priv.selectIndex = function (select_list) {
IndexStorage.prototype.selectIndex = function (select_list) {
var i, tmp, selector = {"index": 0, "similarity": 0};
for (i = 0; i < priv.indices.length; i += 1) {
tmp = priv.similarityPercentage(select_list,
priv.indices[i].index);
for (i = 0; i < this._indices.length; i += 1) {
tmp = similarityPercentage(select_list, this._indices[i].index);
if (tmp > selector.similarity) {
selector.index = i;
selector.similarity = tmp;
......@@ -541,293 +451,233 @@
return selector.index;
};
/**
* Get a database
*
* @method getIndexDatabase
* @param {Object} option The command option
* @param {Number} number The location in priv.indices
* @param {Function} callback The callback
*/
priv.getIndexDatabase = function (option, number, callback) {
that.addJob(
"getAttachment",
priv.indices[number].sub_storage || priv.sub_storage,
{
"_id": priv.indices[number].id,
"_attachment": priv.indices[number].attachment || "body"
},
option,
function (response) {
IndexStorage.prototype.getIndexDatabase = function (command, index) {
index = this._indices[index];
function makeNewIndex() {
return new JSONIndex({
"_id": index.id,
"_attachment": index.attachment || "body",
"indexing": index.index
});
}
return command.storage(
index.sub_storage || this._sub_storage
).getAttachment({
"_id": index.id,
"_attachment": index.attachment || "body"
}).then(function (response) {
return jIO.util.readBlobAsText(response.data);
}).then(function (e) {
try {
response = JSON.parse(response);
response._id = priv.indices[number].id;
response._attachment = priv.indices[number].attachment || "body";
callback(new JSONIndex(response));
} catch (e) {
return that.error(generateErrorObject(
e.message,
"Repair is necessary",
"corrupt"
));
}
},
function (err) {
e = JSON.parse(e.target.result);
e._id = index.id;
e._attachment = index.attachment || "body";
} catch (e1) {
return makeNewIndex();
}
return new JSONIndex(e);
}, function (err) {
if (err.status === 404) {
callback(new JSONIndex({
"_id": priv.indices[number].id,
"_attachment": priv.indices[number].attachment || "body",
"indexing": priv.indices[number].index
}));
return;
return makeNewIndex();
// go back to fulfillment channel
}
err.message = "Unable to get index database.";
that.error(err);
throw err;
// propagate err
});
};
IndexStorage.prototype.getIndexDatabases = function (command) {
var i, promises = [];
for (i = 0; i < this._indices.length; i += 1) {
promises[promises.length] = this.getIndexDatabase(command, i);
}
);
return RSVP.all(promises);
};
/**
* Gets a list containing all the databases set in the storage description.
*
* @method getIndexDatabaseList
* @param {Object} option The command option
* @param {Function} callback The result callback(database_list)
*/
priv.getIndexDatabaseList = function (option, callback) {
var i, count = 0, callbacks = {}, response_list = [];
callbacks.error = function (index) {
return function (err) {
IndexStorage.prototype.storeIndexDatabase = function (command, database,
index) {
var that = this;
index = this._indices[index];
function putAttachment() {
return command.storage(
index.sub_storage || that._sub_storage
).putAttachment({
"_id": index.id,
"_attachment": index.attachment || "body",
"_data": JSON.stringify(database),
"_content_type": "application/json"
});
}
function createDatabaseAndPutAttachmentIfPossible(err) {
if (err.status === 404) {
response_list[index] = new JSONIndex({
"_id": priv.indices[index].id,
"_attachment": priv.indices[index].attachment || "body",
"indexing": priv.indices[index].index
return command.storage(
index.sub_storage || that._sub_storage
).post({
"_id": index.id
// XXX add metadata to document if necessary
}).then(putAttachment, null, function () {
throw null; // stop post progress propagation
});
count += 1;
if (count === priv.indices.length) {
callback(response_list);
}
return;
throw err;
}
err.message = "Unable to get index database.";
that.error(err);
return putAttachment().
then(null, createDatabaseAndPutAttachmentIfPossible);
};
};
callbacks.success = function (index) {
return function (response) {
try {
response = JSON.parse(response);
response._id = priv.indices[index].id;
response._attachment = priv.indices[index].attachment || "body";
response_list[index] = new JSONIndex(response);
} catch (e) {
return that.error(generateErrorObject(
e.message,
"Repair is necessary",
"corrupt"
));
}
count += 1;
if (count === priv.indices.length) {
callback(response_list);
IndexStorage.prototype.storeIndexDatabases = function (command, databases) {
var i, promises = [];
for (i = 0; i < this._indices.length; i += 1) {
if (databases[i] !== undefined) {
promises[promises.length] =
this.storeIndexDatabase(command, databases[i], i);
}
};
};
for (i = 0; i < priv.indices.length; i += 1) {
that.addJob(
"getAttachment",
priv.indices[i].sub_storage || priv.sub_storage,
{
"_id": priv.indices[i].id,
"_attachment": priv.indices[i].attachment || "body"
},
option,
callbacks.success(i),
callbacks.error(i)
);
}
return RSVP.all(promises);
};
/**
* Saves all the databases to the remote(s).
* Generic method for 'post', 'put', 'get' and 'remove'. It delegates the
* command to the sub storage and update the databases.
*
* @method storeIndexDatabaseList
* @param {Array} database_list The database list
* @method genericCommand
* @param {String} method The method to use
* @param {Object} command The JIO command
* @param {Object} metadata The metadata to post
* @param {Object} option The command option
* @param {Function} callback The result callback(err, response)
*/
priv.storeIndexDatabaseList = function (database_list, option, callback) {
var i, count = 0, count_max = 0;
function onAttachmentResponse(response) {
count += 1;
if (count === count_max) {
callback({"ok": true});
}
}
function onAttachmentError(err) {
err.message = "Unable to store index database.";
that.error(err);
}
function putAttachment(i) {
that.addJob(
"putAttachment",
priv.indices[i].sub_storage || priv.sub_storage,
{
"_id": database_list[i]._id,
"_attachment": database_list[i]._attachment,
"_data": JSON.stringify(database_list[i].serialized()),
"_mimetype": "application/json"
},
option,
onAttachmentResponse,
onAttachmentError
);
}
function post(i) {
var doc = priv.indices[i].metadata || {};
doc._id = database_list[i]._id;
that.addJob(
"post", // with id
priv.indices[i].sub_storage || priv.sub_storage,
doc,
option,
function (response) {
putAttachment(i);
},
function (err) {
if (err.status === 409) {
return putAttachment(i);
}
err.message = "Unable to store index database.";
that.error(err);
IndexStorage.prototype.genericCommand = function (method, command,
metadata, option) {
var that = this, generic_response;
function updateAndStoreIndexDatabases(responses) {
var i, database_list = responses[0];
generic_response = responses[1];
if (method === 'get') {
jIO.util.dictUpdate(metadata, generic_response.data);
}
metadata._id = generic_response.id;
if (method === 'remove') {
for (i = 0; i < database_list.length; i += 1) {
database_list[i].remove(metadata);
}
);
} else {
for (i = 0; i < database_list.length; i += 1) {
database_list[i].put(metadata);
}
for (i = 0; i < priv.indices.length; i += 1) {
if (database_list[i] !== undefined) {
count_max += 1;
post(i);
}
return that.storeIndexDatabases(command, database_list);
}
};
/**
* A generic request method which delegates the request to the sub storage.
* On response, it will index the document from the request and update all
* the databases.
*
* @method genericRequest
* @param {Command} command The JIO command
* @param {Function} method The request method
*/
priv.genericRequest = function (command, method) {
var doc = command.cloneDoc(), option = command.cloneOption();
that.addJob(
method,
priv.sub_storage,
doc,
option,
function (response) {
switch (method) {
case "post":
case "put":
case "remove":
doc._id = response.id;
priv.getIndexDatabaseList(option, function (database_list) {
var i;
switch (method) {
case "post":
case "put":
for (i = 0; i < database_list.length; i += 1) {
database_list[i].put(doc);
function allProgress(progress) {
if (progress.index === 1) {
progress.value.percentage *= 0.7; // 0 to 70%
command.notify(progress.value);
}
break;
case "remove":
for (i = 0; i < database_list.length; i += 1) {
database_list[i].remove(doc);
throw null; // stop propagation
}
break;
default:
break;
}
priv.storeIndexDatabaseList(database_list, option, function () {
that.success({"ok": true, "id": doc._id});
});
});
break;
default:
that.success(response);
break;
function success() {
command.success(generic_response);
}
},
function (err) {
return that.error(err);
function storeProgress(progress) {
progress.percentage = (0.3 * progress.percentage) + 70; // 70 to 100%
command.notify(progress);
}
);
RSVP.all([
this.getIndexDatabases(command),
command.storage(this._sub_storage)[method](metadata, option)
]).then(updateAndStoreIndexDatabases, null, allProgress).
then(success, command.error, storeProgress);
};
/**
* Post the document metadata and update the index
*
* @method post
* @param {object} command The JIO command
* @param {Object} command The JIO command
* @param {Object} metadata The metadata to post
* @param {Object} option The command option
*/
that.post = function (command) {
priv.genericRequest(command, 'post');
IndexStorage.prototype.post = function (command, metadata, option) {
this.genericCommand('post', command, metadata, option);
};
/**
* Update the document metadata and update the index
*
* @method put
* @param {object} command The JIO command
* @param {Object} command The JIO command
* @param {Object} metadata The metadata to put
* @param {Object} option The command option
*/
that.put = function (command) {
priv.genericRequest(command, 'put');
IndexStorage.prototype.put = function (command, metadata, option) {
this.genericCommand('put', command, metadata, option);
};
/**
* Add an attachment to a document (no index modification)
*
* @method putAttachment
* @param {object} command The JIO command
* @param {Object} command The JIO command
* @param {Object} param The command parameters
* @param {Object} option The command option
*/
that.putAttachment = function (command) {
priv.genericRequest(command, 'putAttachment');
IndexStorage.prototype.putAttachment = function (command, param, option) {
command.storage(this._sub_storage).putAttachment(param, option).
then(command.success, command.error, command.notify);
};
/**
* Get the document metadata
*
* @method get
* @param {object} command The JIO command
* @param {Object} command The JIO command
* @param {Object} param The command parameters
* @param {Object} option The command option
*/
that.get = function (command) {
priv.genericRequest(command, 'get');
IndexStorage.prototype.get = function (command, param, option) {
this.genericCommand('get', command, param, option);
};
/**
* Get the attachment.
*
* @method getAttachment
* @param {object} command The JIO command
* @param {Object} command The JIO command
* @param {Object} param The command parameters
* @param {Object} option The command option
*/
that.getAttachment = function (command) {
priv.genericRequest(command, 'getAttachment');
IndexStorage.prototype.getAttachment = function (command, param, option) {
command.storage(this._sub_storage).getAttachment(param, option).
then(command.success, command.error, command.notify);
};
/**
* Remove document - removing documents updates index!.
*
* @method remove
* @param {object} command The JIO command
* @param {Object} command The JIO command
* @param {Object} param The command parameters
* @param {Object} option The command option
*/
that.remove = function (command) {
priv.genericRequest(command, 'remove');
IndexStorage.prototype.remove = function (command, param, option) {
this.genericCommand('remove', command, param, option);
};
/**
* Remove attachment
*
* @method removeAttachment
* @param {object} command The JIO command
* @param {Object} command The JIO command
* @param {Object} param The command parameters
* @param {Object} option The command option
*/
that.removeAttachment = function (command) {
priv.genericRequest(command, 'removeAttachment');
IndexStorage.prototype.removeAttachment = function (command, param, option) {
command.storage(this._sub_storage).removeAttachment(param, option).
then(command.success, command.error, command.notify);
};
/**
......@@ -837,146 +687,172 @@
* @method allDocs
* @param {object} command The JIO command
*/
that.allDocs = function (command) {
var option = command.cloneOption(),
index = priv.selectIndex(option.select_list || []);
IndexStorage.prototype.allDocs = function (command, param, option) { // XXX
/*jslint unparam: true */
var index = this.selectIndex(option.select_list || []), delete_id;
// Include docs option is ignored, if you want to get all the document,
// don't use index storage!
option.select_list = option.select_list || [];
option.select_list = (
Array.isArray(option.select_list) ? option.select_list : []
);
if (option.select_list.indexOf("_id") === -1) {
option.select_list.push("_id");
priv.getIndexDatabase(option, index, function (db) {
delete_id = true;
}
this.getIndexDatabase(command, index).then(function (db) {
var i, id;
db = db._database;
complex_queries.QueryFactory.create(option.query || '').
exec(db, option);
for (i = 0; i < db.length; i += 1) {
id = db[i]._id;
if (delete_id) {
delete db[i]._id;
}
db[i] = {
"id": id,
"key": id,
"value": db[i],
};
}
that.success({"total_rows": db.length, "rows": db});
});
"value": db[i]
};
that.check = function (command) {
that.repair(command, true);
};
priv.repairIndexDatabase = function (command, index, just_check) {
var i, option = command.cloneOption();
that.addJob(
'allDocs',
priv.sub_storage,
{},
{'include_docs': true},
function (response) {
var db_list = [], db = new JSONIndex({
"_id": command.getDocId(),
"_attachment": priv.indices[index].attachment || "body",
"indexing": priv.indices[index].index
});
for (i = 0; i < response.rows.length; i += 1) {
db.put(response.rows[i].doc);
}
db_list[index] = db;
if (just_check) {
priv.getIndexDatabase(option, index, function (current_db) {
if (db.equals(current_db)) {
return that.success({"ok": true, "id": command.getDocId()});
}
return that.error(generateErrorObject(
"Different Index",
"Check failed",
"corrupt index database"
));
});
} else {
priv.storeIndexDatabaseList(db_list, {}, function () {
that.success({"ok": true, "id": command.getDocId()});
});
}
},
function (err) {
err.message = "Unable to repair the index database";
that.error(err);
}
);
};
priv.repairDocument = function (command, just_check) {
var i, option = command.cloneOption();
that.addJob(
"get",
priv.sub_storage,
command.cloneDoc(),
{},
function (response) {
response._id = command.getDocId();
priv.getIndexDatabaseList(option, function (database_list) {
if (just_check) {
for (i = 0; i < database_list.length; i += 1) {
try {
database_list[i].checkDocument(response);
} catch (e) {
return that.error(generateErrorObject(
e.message,
"Check failed",
"corrupt index database"
));
}
}
that.success({"_id": command.getDocId(), "ok": true});
} else {
for (i = 0; i < database_list.length; i += 1) {
database_list[i].put(response);
}
priv.storeIndexDatabaseList(database_list, option, function () {
that.success({"ok": true, "id": command.getDocId()});
});
command.success(200, {"data": {"total_rows": db.length, "rows": db}});
}, function (err) {
if (err.status === 404) {
return command.success(200, {"data": {"total_rows": 0, "rows": []}});
}
command.error(err);
});
},
function (err) {
err.message = "Unable to repair document";
return that.error(err);
}
);
};
that.repair = function (command, just_check) {
var database_index = -1, i;
for (i = 0; i < priv.indices.length; i += 1) {
if (priv.indices[i].id === command.getDocId()) {
database_index = i;
break;
}
}
that.addJob(
"repair",
priv.sub_storage,
command.cloneDoc(),
command.cloneOption(),
function (response) {
if (database_index !== -1) {
priv.repairIndexDatabase(command, database_index, just_check);
} else {
priv.repairDocument(command, just_check);
}
},
function (err) {
err.message = "Could not repair sub storage";
that.error(err);
}
);
// IndexStorage.prototype.check = function (command, param, option) { // XXX
// this.repair(command, true, param, option);
// };
// IndexStorage.prototype.repairIndexDatabase = function (
// command,
// index,
// just_check,
// param,
// option
// ) { // XXX
// var i, that = this;
// command.storage(this._sub_storage).allDocs({'include_docs': true}).then(
// function (response) {
// var db_list = [], db = new JSONIndex({
// "_id": param._id,
// "_attachment": that._indices[index].attachment || "body",
// "indexing": that._indices[index].index
// });
// for (i = 0; i < response.rows.length; i += 1) {
// db.put(response.rows[i].doc);
// }
// db_list[index] = db;
// if (just_check) {
// this.getIndexDatabase(command, option, index, function (current_db) {
// if (db.equals(current_db)) {
// return command.success({"ok": true, "id": param._id});
// }
// return command.error(
// "conflict",
// "corrupted",
// "Database is not up to date"
// );
// });
// } else {
// that.storeIndexDatabaseList(command, db_list, {}, function () {
// command.success({"ok": true, "id": param._id});
// });
// }
// },
// function (err) {
// err.message = "Unable to repair the index database";
// command.error(err);
// }
// );
// };
// IndexStorage.prototype.repairDocument = function (
// command,
// just_check,
// param,
// option
// ) { // XXX
// var i, that = this;
// command.storage(this._sub_storage).get(param, {}).then(
// function (response) {
// response._id = param._id;
// that.getIndexDatabaseList(command, option, function (database_list) {
// if (just_check) {
// for (i = 0; i < database_list.length; i += 1) {
// try {
// database_list[i].checkDocument(response);
// } catch (e) {
// return command.error(
// "conflict",
// e.message,
// "Corrupt index database"
// );
// }
// }
// command.success({"_id": param._id, "ok": true});
// } else {
// for (i = 0; i < database_list.length; i += 1) {
// database_list[i].put(response);
// }
// that.storeIndexDatabaseList(
// command,
// database_list,
// option,
// function () {
// command.success({"ok": true, "id": param._id});
// }
// );
// }
// });
// },
// function (err) {
// err.message = "Unable to repair document";
// return command.error(err);
// }
// );
// };
// IndexStorage.prototype.repair = function (command, just_check, param,
// option) { // XXX
// var database_index = -1, i, that = this;
// for (i = 0; i < this._indices.length; i += 1) {
// if (this._indices[i].id === param._id) {
// database_index = i;
// break;
// }
// }
// command.storage(this._sub_storage).repair(param, option).then(
// function () {
// if (database_index !== -1) {
// that.repairIndexDatabase(
// command,
// database_index,
// just_check,
// param,
// option
// );
// } else {
// that.repairDocument(command, just_check, param, option);
// }
// },
// function (err) {
// err.message = "Could not repair sub storage";
// command.error(err);
// }
// );
// };
jIO.addStorage("indexed", IndexStorage);
exports.createDescription = function () {
// XXX
return;
};
return that;
}
jIO.addStorageType("indexed", indexStorage);
}));
/*jslint indent: 2, maxlen: 80, nomen: true */
/*global define, jIO, jio_tests, test, ok, deepEqual, sinon */
/*global define, module, test_util, RSVP, jIO, local_storage, test, ok,
deepEqual, stop, start */
// define([module_name], [dependencies], module);
(function (dependencies, module) {
......@@ -7,1108 +8,621 @@
if (typeof define === 'function' && define.amd) {
return define(dependencies, module);
}
module(jIO, jio_tests);
}(['jio', 'jio_tests', 'localstorage', 'indexstorage'], function (jIO, util) {
module(test_util, RSVP, jIO, local_storage);
}([
'test_util',
'rsvp',
'jio',
'localstorage',
'indexstorage'
], function (util, RSVP, jIO, local_storage) {
"use strict";
function generateTools() {
return {
clock: sinon.useFakeTimers(),
spy: util.ospy,
tick: util.otick
};
function success(promise) {
return new RSVP.Promise(function (resolve, reject, notify) {
/*jslint unparam: true*/
promise.then(resolve, resolve, notify);
}, function () {
promise.cancel();
});
}
module("IndexStorage");
test("Post", function () {
var o = generateTools();
o.jio = jIO.newJio({
"type": "indexed",
"indices": [
{"id": "A", "index": ["title"]},
{"id": "B", "index": ["title", "year"]}
],
"sub_storage": {
"type": "local",
"username": "ipost",
"application_name": "ipost"
}
/**
* sequence(thens): Promise
*
* Executes a sequence of *then* callbacks. It acts like
* `smth().then(callback).then(callback)...`. The first callback is called
* with no parameter.
*
* Elements of `thens` array can be a function or an array contaning at most
* three *then* callbacks: *onFulfilled*, *onRejected*, *onNotified*.
*
* When `cancel()` is executed, each then promises are cancelled at the same
* time.
*
* @param {Array} thens An array of *then* callbacks
* @return {Promise} A new promise
*/
function sequence(thens) {
var promises = [];
return new RSVP.Promise(function (resolve, reject, notify) {
var i;
promises[0] = new RSVP.Promise(function (resolve) {
resolve();
});
o.getAttachmentCallback = function (err, response) {
if (response) {
try {
response = JSON.parse(response);
} catch (e) {
response = "PARSE ERROR " + response;
for (i = 0; i < thens.length; i += 1) {
if (Array.isArray(thens[i])) {
promises[i + 1] = promises[i].
then(thens[i][0], thens[i][1], thens[i][2]);
} else {
promises[i + 1] = promises[i].then(thens[i]);
}
}
o.f(err, response);
};
// post without id
o.spy(o, "jobstatus", "done", "Post without id");
o.jio.post({}, function (err, response) {
o.id = (response || {}).id;
o.f(err, response);
});
o.tick(o);
// post non empty document
o.doc = {"_id": "some_id", "title": "My Title",
"year": 2000, "hey": "def"};
o.spy(o, "value", {"ok": true, "id": "some_id"}, "Post document");
o.jio.post(o.doc, o.f);
o.tick(o);
// check document
o.fakeIndexA = {
"indexing": ["title"],
"free": [],
"location": {
"some_id": 0
},
"database": [
{"_id": "some_id", "title": "My Title"}
]
};
o.spy(o, "value", o.fakeIndexA, "Check index file");
o.jio.getAttachment({
"_id": "A",
"_attachment": "body"
}, o.getAttachmentCallback);
o.tick(o);
o.fakeIndexB = {
"indexing": ["title", "year"],
"free": [],
"location": {
"some_id": 0
},
"database": [
{"_id": "some_id", "title": "My Title", "year": 2000}
]
};
o.spy(o, "value", o.fakeIndexB, "Check index file");
o.jio.getAttachment({
"_id": "B",
"_attachment": "body"
}, o.getAttachmentCallback);
o.tick(o);
// post with escapable characters
o.doc = {
"_id": "other_id",
"title": "myPost2",
"findMeA": "keyword_*§$%&/()=?",
"findMeB": "keyword_|ð@ł¶đæðſæðæſ³"
};
o.spy(o, "value", {"ok": true, "id": "other_id"},
"Post with escapable characters");
o.jio.post(o.doc, o.f);
o.tick(o);
// post and document already exists
o.doc = {
"_id": "some_id",
"title": "myPost3",
"findMeA": "keyword_ghi",
"findMeB": "keyword_jkl"
};
o.spy(o, "status", 409, "Post and document already exists");
o.jio.post(o.doc, o.f);
o.tick(o);
util.closeAndcleanUpJio(o.jio);
});
test("Put", function () {
var o = generateTools();
o.jio = jIO.newJio({
"type": "indexed",
"indices": [
{"id": "A", "index": ["author"]},
{"id": "B", "index": ["year"]}
],
"sub_storage": {
"type": "local",
"username": "iput",
"application_name": "iput"
promises[i].then(resolve, reject, notify);
}, function () {
var i;
for (i = 0; i < promises.length; i += 1) {
promises[i].cancel();
}
});
o.getAttachmentCallback = function (err, response) {
if (response) {
try {
response = JSON.parse(response);
} catch (e) {
response = "PARSE ERROR " + response;
}
}
o.f(err, response);
};
// put without id
// error 20 -> document id required
o.spy(o, "status", 20, "Put without id");
o.jio.put({}, o.f);
o.tick(o);
// put non empty document
o.doc = {"_id": "put1", "title": "myPut1", "author": "John Doe"};
o.spy(o, "value", {"ok": true, "id": "put1"}, "Put-create document");
o.jio.put(o.doc, o.f);
o.tick(o);
// check index file
o.fakeIndexA = {
"indexing": ["author"],
"free": [],
"location": {
"put1": 0
},
"database": [{"_id": "put1", "author": "John Doe"}]
};
o.spy(o, "value", o.fakeIndexA, "Check index file");
o.jio.getAttachment({
"_id": "A",
"_attachment": "body"
}, o.getAttachmentCallback);
o.tick(o);
o.fakeIndexB = {
"indexing": ["year"],
"free": [],
"location": {},
"database": []
};
o.spy(o, "value", o.fakeIndexB, "Check index file");
o.jio.getAttachment({
"_id": "B",
"_attachment": "body"
}, o.getAttachmentCallback);
o.tick(o);
// modify document - modify keyword on index!
o.doc = {"_id": "put1", "title": "myPuttter1", "author": "Jane Doe"};
o.spy(o, "value", {"ok": true, "id": "put1"}, "Modify existing document");
o.jio.put(o.doc, o.f);
o.tick(o);
// check index file
o.fakeIndexA.database[0].author = "Jane Doe";
o.spy(o, "value", o.fakeIndexA, "Check index file");
o.jio.getAttachment({
"_id": "A",
"_attachment": "body"
}, o.getAttachmentCallback);
o.tick(o);
// add new document with same keyword!
o.doc = {"_id": "new_doc", "title": "myPut2", "author": "Jane Doe"};
o.spy(o, "value", {"ok": true, "id": "new_doc"},
"Add new document with same keyword");
o.jio.put(o.doc, o.f);
o.tick(o);
// check index file
o.fakeIndexA.location.new_doc = 1;
o.fakeIndexA.database.push({"_id": "new_doc", "author": "Jane Doe"});
o.spy(o, "value", o.fakeIndexA, "Check index file");
o.jio.getAttachment({
"_id": "A",
"_attachment": "body"
}, o.getAttachmentCallback);
o.tick(o);
// add second keyword to index file
o.doc = {"_id": "put1", "title": "myPut2", "author": "Jane Doe",
"year": "1912"};
o.spy(o, "value", {"ok": true, "id": "put1"},
"add second keyword to index file");
o.jio.put(o.doc, o.f);
o.tick(o);
// check index file
o.spy(o, "value", o.fakeIndexA, "Check index file");
o.jio.getAttachment({
"_id": "A",
"_attachment": "body"
}, o.getAttachmentCallback);
o.tick(o);
o.fakeIndexB.location.put1 = 0;
o.fakeIndexB.database.push({"_id": "put1", "year": "1912"});
o.spy(o, "value", o.fakeIndexB, "Check index file");
o.jio.getAttachment({
"_id": "B",
"_attachment": "body"
}, o.getAttachmentCallback);
o.tick(o);
// remove a keyword from an existing document
o.doc = {"_id": "new_doc", "title": "myPut2"};
o.spy(o, "value", {"ok": true, "id": "new_doc"},
"Remove keyword from existing document");
o.jio.put(o.doc, o.f);
o.tick(o);
// check index file
delete o.fakeIndexA.location.new_doc;
o.fakeIndexA.database[1] = null;
o.fakeIndexA.free.push(1);
o.spy(o, "value", o.fakeIndexA, "Check index file");
o.jio.getAttachment({
"_id": "A",
"_attachment": "body"
}, o.getAttachmentCallback);
o.tick(o);
util.closeAndcleanUpJio(o.jio);
});
module("IndexStorage");
test("Check & Repair", function () {
var o = generateTools(), i;
test("Scenario", function () {
o.jio = jIO.newJio({
var LOCAL_STORAGE_SPEC = local_storage.createDescription(
'indexstorage tests',
'scenario',
'memory'
), INDEX_STORAGE_SPEC = {
"type": "indexed",
"indices": [
{"id": "A", "index": ["director"]},
{"id": "B", "index": ["year"]}
{"id": "A", "index": ["contributor"]},
{"id": "B", "index": ["author"]},
{"id": "C", "index": ["title"]},
{"id": "D", "index": ["title", "year"]}
],
"sub_storage": {
"type": "local",
"username": "indexstoragerepair"
}
});
o.getAttachmentCallback = function (err, response) {
if (response) {
try {
response = JSON.parse(response);
} catch (e) {
response = "PARSE ERROR " + response;
}
delete response.location;
response.database.sort(function (a, b) {
return a._id < b._id ? -1 : a._id > b._id ? 1 : 0;
});
}
o.f(err, response);
};
"sub_storage": LOCAL_STORAGE_SPEC
}, option = {"workspace": {}}, shared = {}, jio_index, jio_local;
o.fakeIndexA = {
"indexing": ["director"],
"free": [],
"database": []
};
o.fakeIndexB = {
"indexing": ["year"],
"free": [],
"database": []
};
for (i = 0; i < 10; i += 1) {
o.jio.put({
"_id": "id" + i,
"director": "D" + i,
"year": i,
"title": "T" + i
});
jio_index = jIO.createJIO(INDEX_STORAGE_SPEC, option);
jio_local = jIO.createJIO(LOCAL_STORAGE_SPEC, option);
o.tmp = o.fakeIndexA.free.pop() || o.fakeIndexA.database.length;
o.fakeIndexA.database[o.tmp] = {"_id": "id" + i, "director": "D" + i};
o.tmp = o.fakeIndexB.free.pop() || o.fakeIndexB.database.length;
o.fakeIndexB.database[o.tmp] = {"_id": "id" + i, "year": i};
function postNewDocument() {
return jio_index.post({"title": "Unique ID"});
}
o.clock.tick(5000);
o.spy(o, "status", 40, "Check database");
o.jio.check({"_id": "A"}, o.f);
o.tick(o);
o.spy(o, "status", 40, "Check database");
o.jio.check({"_id": "B"}, o.f);
o.tick(o);
o.spy(o, "value", {"id": "A", "ok": true}, "Repair database");
o.jio.repair({"_id": "A"}, o.f);
o.tick(o);
o.spy(o, "value", {"id": "B", "ok": true}, "Repair database");
o.jio.repair({"_id": "B"}, o.f);
o.tick(o);
o.spy(o, "value", {"id": "A", "ok": true}, "Check database again");
o.jio.check({"_id": "A"}, o.f);
o.tick(o);
o.spy(o, "value", {"id": "B", "ok": true}, "Check database again");
o.jio.check({"_id": "B"}, o.f);
o.tick(o);
// check index file
o.spy(o, "value", o.fakeIndexA, "Manually check index file");
o.jio.getAttachment({
"_id": "A",
"_attachment": "body"
}, o.getAttachmentCallback);
o.tick(o);
o.spy(o, "value", o.fakeIndexB, "Manually check index file");
o.jio.getAttachment({
"_id": "B",
"_attachment": "body"
}, o.getAttachmentCallback);
o.tick(o);
o.jio2 = jIO.newJio({"type": "local", "username": "indexstoragerepair"});
o.jio2.put({"_id": "blah", "title": "t", "year": "y", "director": "d"});
o.clock.tick(1000);
util.closeAndcleanUpJio(o.jio2);
o.fakeIndexA.database.unshift({"_id": "blah", "director": "d"});
o.fakeIndexB.database.unshift({"_id": "blah", "year": "y"});
o.spy(o, "status", 40, "Check Document");
o.jio.check({"_id": "blah"}, o.f);
o.tick(o);
o.spy(o, "value", {"id": "blah", "ok": true}, "Repair Document");
o.jio.repair({"_id": "blah"}, o.f);
o.tick(o);
o.spy(o, "value", {"id": "blah", "ok": true}, "Check Document again");
o.jio.repair({"_id": "blah"}, o.f);
o.tick(o);
// check index file
o.spy(o, "value", o.fakeIndexA, "Manually check index file");
o.jio.getAttachment({
"_id": "A",
"_attachment": "body"
}, o.getAttachmentCallback);
o.tick(o);
o.spy(o, "value", o.fakeIndexB, "Manually check index file");
o.jio.getAttachment({
"_id": "B",
"_attachment": "body"
}, o.getAttachmentCallback);
o.tick(o);
util.closeAndcleanUpJio(o.jio);
});
test("PutAttachment", function () {
// not sure these need to be run, because the index does not change
// and only small modifications have been made to handle putAttachment
// tests are from localStorage putAttachment
var o = generateTools();
o.jio = jIO.newJio({
"type": "indexed",
"indices": [
{"id": "A", "index": ["author"]},
{"id": "B", "index": ["year"]}
],
"sub_storage": {
"type": "local",
"username": "iputatt",
"application_name": "iputatt"
function postNewDocumentTest(answer) {
var uuid = answer.id;
answer.id = "<uuid>";
deepEqual(answer, {
"id": "<uuid>",
"method": "post",
"result": "success",
"status": 201,
"statusText": "Created"
}, "Post a new document");
ok(util.isUuid(uuid), "New document id should look like " +
"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx : " + uuid);
shared.created_document_id = uuid;
}
});
// putAttachment without doc id
// error 20 -> document id required
o.spy(o, "status", 20, "PutAttachment without doc id");
o.jio.putAttachment({}, o.f);
o.tick(o);
// putAttachment without attachment id
// error 22 -> attachment id required
o.spy(o, "status", 22, "PutAttachment without attachment id");
o.jio.putAttachment({"_id": "putattmt1"}, o.f);
o.tick(o);
// putAttachment without document
// error 404 -> not found
o.spy(o, "status", 404, "PutAttachment without document");
o.jio.putAttachment({"_id": "putattmt1", "_attachment": "putattmt2"}, o.f);
o.tick(o);
// putAttachment with document
o.doc = {"_id": "putattmt1", "title": "myPutAttmt1"};
o.spy(o, "value", {"ok": true, "id": "putattmt1"},
"Put underlying document");
o.jio.put(o.doc, o.f);
o.tick(o);
o.spy(o, "value", {
"ok": true,
"id": "putattmt1",
"attachment": "putattmt2"
}, "PutAttachment with document, without data");
o.jio.putAttachment({"_id": "putattmt1", "_attachment": "putattmt2"}, o.f);
o.tick(o);
// check document
deepEqual(util.jsonlocalstorage.getItem(
"jio/localstorage/iputatt/iputatt/putattmt1"
), {
"_id": "putattmt1",
"title": "myPutAttmt1",
"_attachments": {
"putattmt2": {
"length": 0,
// md5("")
"digest": "md5-d41d8cd98f00b204e9800998ecf8427e"
}
function getCreatedDocument() {
return jio_index.get({"_id": shared.created_document_id});
}
}, "Check document");
// check attachment
deepEqual(util.jsonlocalstorage.getItem(
"jio/localstorage/iputatt/iputatt/putattmt1/putattmt2"
), "", "Check attachment");
// update attachment
o.spy(o, "value",
{"ok": true, "id": "putattmt1", "attachment": "putattmt2"},
"Update Attachment, with data");
o.jio.putAttachment({
"_id": "putattmt1",
"_attachment": "putattmt2",
"_data": "abc"
}, o.f);
o.tick(o);
// check document
deepEqual(util.jsonlocalstorage.getItem(
"jio/localstorage/iputatt/iputatt/putattmt1"
), {
"_id": "putattmt1",
"title": "myPutAttmt1",
"_attachments": {
"putattmt2": {
"length": 3,
// md5("abc")
"digest": "md5-900150983cd24fb0d6963f7d28e17f72"
function getCreatedDocumentTest(answer) {
deepEqual(answer, {
"data": {
"_id": shared.created_document_id,
"title": "Unique ID"
},
"id": shared.created_document_id,
"method": "get",
"result": "success",
"status": 200,
"statusText": "Ok"
}, "Get new document");
}
// function postSpecificDocuments() {
// return success(RSVP.all([
// jio_index.post({"_id": "b", "title": "Bee", "year": 2013}),
// jio_index.post({"_id": "ce", "contributor": "DCee"}),
// jio_index.post({"_id": "dee", "format": "text/plain"})
// ]));
// }
// function postSpecificDocumentsTest(answers) {
// deepEqual(answers[0], {
// "id": "b",
// "method": "post",
// "result": "success",
// "status": 201,
// "statusText": "Created"
// }, "Post specific document 'b'");
// deepEqual(answers[1], {
// "id": "ce",
// "method": "post",
// "result": "success",
// "status": 201,
// "statusText": "Created"
// }, "Post specific document 'ce'");
// deepEqual(answers[2], {
// "id": "dee",
// "method": "post",
// "result": "success",
// "status": 201,
// "statusText": "Created"
// }, "Post specific document 'dee'");
// }
// XXX the 2 following functions should be replaced by the 2 commented
// previous ones (which don't work yet)
function postSpecificDocuments() {
return sequence([function () {
return jio_index.post({"_id": "b", "title": "Bee", "year": 2013});
}, function () {
return jio_index.post({"_id": "ce", "contributor": "DCee"});
}, function () {
return jio_index.post({"_id": "dee", "format": "text/plain"});
}]);
}
function postSpecificDocumentsTest(last_answer) {
deepEqual(last_answer, {
"id": "dee",
"method": "post",
"result": "success",
"status": 201,
"statusText": "Created"
}, "Post documents: 'b', 'ce', 'dee' (testing 'dee' response only)");
}
function listDocumentsFromIndexContributor() {
return jio_index.allDocs({"select_list": ["contributor"]});
}
function listDocumentsFromIndexContributorTest(answer) {
if (answer && answer.data && Array.isArray(answer.data.rows)) {
answer.data.rows.sort(function (a, b) {
return a.id.length < b.id.length ? -1 : (
a.id.length > b.id.length ? 1 : 0
);
});
}
deepEqual(answer, {
"data": {
"total_rows": 1,
"rows": [{
"id": "ce",
"value": {"contributor": "DCee"}
}]
},
"method": "allDocs",
"result": "success",
"status": 200,
"statusText": "Ok"
}, "List 1 document from 'contributor'");
}
}, "Check document");
// check attachment
deepEqual(util.jsonlocalstorage.getItem(
"jio/localstorage/iputatt/iputatt/putattmt1/putattmt2"
), "abc", "Check attachment");
util.closeAndcleanUpJio(o.jio);
});
test("Get", function () {
// not sure these need to be run, because the index does not change
// and only small modifications have been made to handle putAttachment
// tests are from localStorage putAttachment
var o = generateTools();
o.jio = jIO.newJio({
"type": "indexed",
"indices": [
{"id": "A", "index": ["author"]},
{"id": "B", "index": ["year"]}
],
"sub_storage": {
"type": "local",
"username": "iget",
"application_name": "iget"
function listDocumentsFromIndexTitleYear() {
return jio_index.allDocs({"select_list": ["year", "title"]});
}
});
// get inexistent document
o.spy(o, "status", 404, "Get inexistent document");
o.jio.get({"_id": "get1"}, o.f);
o.tick(o);
// get inexistent attachment
o.spy(o, "status", 404, "Get inexistent attachment");
o.jio.getAttachment({"_id": "get1", "_attachment": "get2"}, o.f);
o.tick(o);
// adding a document
o.doc_get1 = {
"_id": "get1",
"title": "myGet1"
};
util.jsonlocalstorage.setItem(
"jio/localstorage/iget/iget/get1",
o.doc_get1
);
// get document
o.spy(o, "value", o.doc_get1, "Get document");
o.jio.get({"_id": "get1"}, o.f);
o.tick(o);
// get inexistent attachment (document exists)
o.spy(o, "status", 404, "Get inexistent attachment (document exists)");
o.jio.getAttachment({"_id": "get1", "_attachment": "get2"}, o.f);
o.tick(o);
// adding an attachment
o.doc_get1._attachments = {
"get2": {
"length": 2,
// md5("de")
"digest": "md5-5f02f0889301fd7be1ac972c11bf3e7d"
}
};
util.jsonlocalstorage.setItem(
"jio/localstorage/iget/iget/get1",
o.doc_get1
function listDocumentsFromIndexTitleYearTest(answer) {
if (answer && answer.data && Array.isArray(answer.data.rows)) {
answer.data.rows.sort(function (a, b) {
return a.id.length < b.id.length ? -1 : (
a.id.length > b.id.length ? 1 : 0
);
util.jsonlocalstorage.setItem("jio/localstorage/iget/iget/get1/get2", "de");
// get attachment
o.spy(o, "value", "de", "Get attachment");
o.jio.getAttachment({"_id": "get1", "_attachment": "get2"}, o.f);
o.tick(o);
util.closeAndcleanUpJio(o.jio);
});
test("Remove", function () {
// not sure these need to be run, because the index does not change
// and only small modifications have been made to handle putAttachment
// tests are from localStorage putAttachment
var o = generateTools();
o.jio = jIO.newJio({
"type": "indexed",
"indices": [
{"id": "A", "index": ["author"]},
{"id": "B", "index": ["year"]}
],
"sub_storage": {
"type": "local",
"username": "irem",
"application_name": "irem"
}
});
o.getAttachmentCallback = function (err, response) {
if (response) {
try {
response = JSON.parse(response);
} catch (e) {
response = "PARSE ERROR " + response;
}
deepEqual(answer, {
"data": {
"total_rows": 2,
"rows": [{
"id": "b",
"value": {"title": "Bee", "year": 2013}
}, {
"id": shared.created_document_id,
"value": {"title": "Unique ID"}
}]
},
"method": "allDocs",
"result": "success",
"status": 200,
"statusText": "Ok"
}, "List 2 documents from 'year' and 'title'");
}
o.f(err, response);
};
// remove inexistent document
o.spy(o, "status", 404, "Remove inexistent document");
o.jio.remove({"_id": "remove1"}, o.f);
o.tick(o);
// remove inexistent document/attachment
o.spy(o, "status", 404, "Remove inexistent attachment");
o.jio.removeAttachment({"_id": "remove1", "_attachment": "remove2"}, o.f);
o.tick(o);
// adding a document
o.jio.put({"_id": "remove1", "title": "myRemove1",
"author": "Mr. President", "year": "2525"
});
o.tick(o);
function listDocumentsFromIndexTitle() {
return jio_index.allDocs({"select_list": ["title"]});
}
// adding a 2nd document with same keywords
o.jio.put({"_id": "removeAlso", "title": "myRemove2",
"author": "Martin Mustermann", "year": "2525"
function listDocumentsFromIndexTitleTest(answer) {
if (answer && answer.data && Array.isArray(answer.data.rows)) {
answer.data.rows.sort(function (a, b) {
return a.id.length < b.id.length ? -1 : (
a.id.length > b.id.length ? 1 : 0
);
});
o.tick(o);
// remove document
o.spy(o, "value", {"ok": true, "id": "remove1"}, "Remove document");
o.jio.remove({"_id": "remove1"}, o.f);
o.tick(o);
// check index
o.fakeIndexA = {
"indexing": ["author"],
"free": [0],
"location": {
"removeAlso": 1
},
"database": [null, {"_id": "removeAlso", "author": "Martin Mustermann"}]
};
o.spy(o, "value", o.fakeIndexA, "Check index file");
o.jio.getAttachment({
"_id": "A",
"_attachment": "body"
}, o.getAttachmentCallback);
o.tick(o);
o.fakeIndexB = {
"indexing": ["year"],
"free": [0],
"location": {
"removeAlso": 1
}
deepEqual(answer, {
"data": {
"total_rows": 2,
"rows": [{
"id": "b",
"value": {"title": "Bee"}
}, {
"id": shared.created_document_id,
"value": {"title": "Unique ID"}
}]
},
"database": [null, {"_id": "removeAlso", "year": "2525"}]
};
o.spy(o, "value", o.fakeIndexB, "Check index file");
o.jio.getAttachment({
"_id": "B",
"_attachment": "body"
}, o.getAttachmentCallback);
o.tick(o);
// check document
o.spy(o, "status", 404, "Check if document has been removed");
o.jio.get({"_id": "remove1"}, o.f);
o.tick(o);
// adding a new document
o.jio.put({"_id": "remove3",
"title": "myRemove1",
"author": "Mrs Sunshine",
"year": "1234"
});
o.tick(o);
// adding an attachment
o.jio.putAttachment({
"_id": "remove3",
"_attachment": "removeAtt",
"_mimetype": "text/plain",
"_data": "hello"
});
o.tick(o);
// add another attachment
o.jio.putAttachment({
"_id": "remove3",
"_attachment": "removeAtt2",
"_mimetype": "text/plain",
"_data": "hello2"
});
o.tick(o);
// remove attachment
o.spy(o, "value", {"ok": true, "id": "remove3", "attachment": "removeAtt2"},
"Remove one of multiple attachment");
o.jio.removeAttachment({
"_id": "remove3",
"_attachment": "removeAtt2"
}, o.f);
o.tick(o);
// check index
o.fakeIndexA.free = [];
o.fakeIndexA.location.remove3 = 0;
o.fakeIndexA.database[0] = {"_id": "remove3", "author": "Mrs Sunshine"};
o.spy(o, "value", o.fakeIndexA, "Check index file");
o.jio.getAttachment({
"_id": "A",
"_attachment": "body"
}, o.getAttachmentCallback);
o.tick(o);
o.fakeIndexB.free = [];
o.fakeIndexB.location.remove3 = 0;
o.fakeIndexB.database[0] = {"_id": "remove3", "year": "1234"};
o.spy(o, "value", o.fakeIndexB, "Check index file");
o.jio.getAttachment({
"_id": "B",
"_attachment": "body"
}, o.getAttachmentCallback);
o.tick(o);
// remove document and attachment together
o.spy(o, "value", {"ok": true, "id": "remove3"},
"Remove one document and attachment together");
o.jio.remove({"_id": "remove3"}, o.f);
o.tick(o);
// check index
o.fakeIndexA.free = [0];
delete o.fakeIndexA.location.remove3;
o.fakeIndexA.database[0] = null;
o.spy(o, "value", o.fakeIndexA, "Check index file");
o.jio.getAttachment({
"_id": "A",
"_attachment": "body"
}, o.getAttachmentCallback);
o.tick(o);
o.fakeIndexB.free = [0];
delete o.fakeIndexB.location.remove3;
o.fakeIndexB.database[0] = null;
o.spy(o, "value", o.fakeIndexB, "Check index file");
o.jio.getAttachment({
"_id": "B",
"_attachment": "body"
}, o.getAttachmentCallback);
o.tick(o);
// check attachment
o.spy(o, "status", 404, "Check if attachment has been removed");
o.jio.getAttachment({"_id": "remove3", "_attachment": "removeAtt"}, o.f);
o.tick(o);
// check document
o.spy(o, "status", 404, "Check if document has been removed");
o.jio.get({"_id": "remove3"}, o.f);
o.tick(o);
util.closeAndcleanUpJio(o.jio);
});
test("AllDocs", function () {
var o = generateTools();
o.jio = jIO.newJio({
"type": "indexed",
"indices": [
{"id": "A", "index": ["author"]},
{"id": "B", "index": ["year"]}
],
"sub_storage": {
"type": "local",
"username": "iall",
"application_name": "iall"
"method": "allDocs",
"result": "success",
"status": 200,
"statusText": "Ok"
}, "List 2 documents from 'title'");
}
});
o.getAttachmentCallback = function (err, response) {
if (response) {
try {
response = JSON.parse(response);
} catch (e) {
response = "PARSE ERROR " + response;
}
function listDocumentsFromIndexAuthor() {
return jio_index.allDocs({"select_list": ["author"]});
}
o.f(err, response);
};
// adding documents
o.all1 = { "_id": "dragon.doc",
"title": "some title", "author": "Dr. No", "year": "1968"
};
o.spy(o, "value", {"ok": true, "id": "dragon.doc"}, "Put 1");
o.jio.put(o.all1, o.f);
o.tick(o);
o.all2 = {
"_id": "timemachine",
"title": "hello world",
"author": "Dr. Who",
"year": "1968"
};
o.spy(o, "value", {"ok": true, "id": "timemachine"}, "Put 2");
o.jio.put(o.all2, o.f);
o.tick(o);
o.all3 = {
"_id": "rocket.ppt",
"title": "sunshine.",
"author": "Dr. Snuggles",
"year": "1985"
};
o.spy(o, "value", {"ok": true, "id": "rocket.ppt"}, "Put 3");
o.jio.put(o.all3, o.f);
o.tick(o);
o.all4 = {
"_id": "stick.jpg",
"title": "clouds",
"author": "Dr. House",
"year": "2005"
};
o.spy(o, "value", {"ok": true, "id": "stick.jpg"}, "Put 4");
o.jio.put(o.all4, o.f);
o.tick(o);
// check index
o.fakeIndexA = {
"indexing": ["author"],
"free": [],
"location": {
"dragon.doc": 0,
"timemachine": 1,
"rocket.ppt": 2,
"stick.jpg": 3
},
"database": [
{"_id": "dragon.doc", "author": "Dr. No"},
{"_id": "timemachine", "author": "Dr. Who"},
{"_id": "rocket.ppt", "author": "Dr. Snuggles"},
{"_id": "stick.jpg", "author": "Dr. House"}
]
};
o.spy(o, "value", o.fakeIndexA, "Check index file");
o.jio.getAttachment({
"_id": "A",
"_attachment": "body"
}, o.getAttachmentCallback);
o.tick(o);
o.thisShouldBeTheAnswer = {
"rows": [
{"id": "dragon.doc", "key": "dragon.doc", "value": {} },
{"id": "timemachine", "key": "timemachine", "value": {} },
{"id": "rocket.ppt", "key": "rocket.ppt", "value": {} },
{"id": "stick.jpg", "key": "stick.jpg", "value": {} }
],
"total_rows": 4
};
o.spy(o, "value", o.thisShouldBeTheAnswer, "allDocs (served by index)");
o.jio.allDocs(o.f);
o.tick(o);
util.closeAndcleanUpJio(o.jio);
});
test("AllDocs Complex Queries", function () {
var o = generateTools(), i, m = 15;
o.jio = jIO.newJio({
"type": "indexed",
"indices": [
{"id": "A", "index": ["director"]},
{"id": "B", "index": ["title", "year"]}
],
"sub_storage": {
"type": "local",
"username": "icomplex",
"application_name": "acomplex"
}
function listDocumentsFromIndexAuthorTest(answer) {
if (answer && answer.data && Array.isArray(answer.data.rows)) {
answer.data.rows.sort(function (a, b) {
return a.id.length < b.id.length ? -1 : (
a.id.length > b.id.length ? 1 : 0
);
});
o.localpath = "jio/localstorage/icomplex/acomplex";
o.getAttachmentCallback = function (err, response) {
if (response) {
try {
response = JSON.parse(response);
} catch (e) {
response = "PARSE ERROR " + response;
}
deepEqual(answer, {
"data": {
"total_rows": 0,
"rows": []
},
"method": "allDocs",
"result": "success",
"status": 200,
"statusText": "Ok"
}, "List 0 document from 'author'");
}
o.f(err, response);
};
// sample data
o.titles = [
"Shawshank Redemption",
"Godfather",
"Godfather 2",
"Pulp Fiction",
"The Good, The Bad and The Ugly",
"12 Angry Men",
"The Dark Knight",
"Schindlers List",
"Lord of the Rings - Return of the King",
"Fight Club",
"Star Wars Episode V",
"Lord Of the Rings - Fellowship of the Ring",
"One flew over the Cuckoo's Nest",
"Inception", "Godfellas"
];
o.years = [
1994,
1972,
1974,
1994,
1966,
1957,
2008,
1993,
2003,
1999,
1980,
2001,
1975,
2010,
1990
];
o.director = [
"Frank Darabont",
"Francis Ford Coppola",
"Francis Ford Coppola",
"Quentin Tarantino",
"Sergio Leone",
"Sidney Lumet",
"Christopher Nolan",
"Steven Spielberg",
"Peter Jackson",
"David Fincher",
"Irvin Kershner",
"Peter Jackson",
"Milos Forman",
"Christopher Nolan",
" Martin Scorsese"
];
o.fakeIndexA = {
"indexing": ["director"],
"free": [],
"location": {},
"database": []
};
o.fakeIndexB = {
"indexing": ["title", "year"],
"free": [],
"location": {},
"database": []
};
for (i = 0; i < m; i += 1) {
o.jio.put({
"_id": i.toString(),
"director": o.director[i],
"year": o.years[i],
"title": o.titles[i]
});
o.tmp = o.fakeIndexA.free.pop() || o.fakeIndexA.database.length;
o.fakeIndexA.database[o.tmp] = {
"_id": i.toString(),
"director": o.director[i]
};
o.fakeIndexA.location[i] = o.tmp;
o.tmp = o.fakeIndexB.free.pop() || o.fakeIndexB.database.length;
o.fakeIndexB.database[o.tmp] = {
"_id": i.toString(),
"year": o.years[i],
"title": o.titles[i]
};
o.fakeIndexB.location[i] = o.tmp;
o.clock.tick(1000);
function listDocumentsFromNothing() {
return jio_index.allDocs();
}
// o.clock.tick(1000);
// check index file
o.spy(o, "value", o.fakeIndexA, "Check index file");
o.jio.getAttachment({
"_id": "A",
"_attachment": "body"
}, o.getAttachmentCallback);
o.tick(o);
o.spy(o, "value", o.fakeIndexB, "Check index file");
o.jio.getAttachment({
"_id": "B",
"_attachment": "body"
}, o.getAttachmentCallback);
o.tick(o);
// response
o.allDocsResponse = {};
o.allDocsResponse.rows = [];
o.allDocsResponse.total_rows = m;
for (i = 0; i < m; i += 1) {
o.allDocsResponse.rows.push({
"id": i.toString(),
"key": i.toString(),
"value": {},
"doc": {
"_id": i.toString(),
"title": o.titles[i],
"year": o.years[i],
"director": o.director[i]
}
function listDocumentsFromNothingTest(answer) {
if (answer && answer.data && Array.isArray(answer.data.rows)) {
answer.data.rows.sort(function (a, b) {
return a.id.length < b.id.length ? -1 : (
a.id.length > b.id.length ? 1 : 0
);
});
}
o.response = JSON.parse(JSON.stringify(o.allDocsResponse));
for (i = 0; i < o.response.rows.length; i += 1) {
delete o.response.rows[i].doc;
deepEqual(answer, {
"data": {
"total_rows": 1,
"rows": [{
"id": "ce",
"value": {}
}]
},
"method": "allDocs",
"result": "success",
"status": 200,
"statusText": "Ok"
}, "List 1 document from first index (`allDocs()`)");
}
// alldocs
o.spy(o, "value", o.response, "AllDocs response generated from index");
o.jio.allDocs(o.f);
o.tick(o, 1000);
// complex queries
o.response = JSON.parse(JSON.stringify(o.allDocsResponse));
i = 0;
while (i < o.response.rows.length) {
if (o.response.rows[i].year < 1980) {
o.response.rows.splice(i, 1);
} else {
o.response.rows[i].value = {
"year": o.response.rows[i].doc.year,
"title": o.response.rows[i].doc.title
};
delete o.response.rows[i].doc;
i += 1;
}
function listDocumentsFromLocal() {
return jio_local.allDocs();
}
o.response.rows.sort(function (a, b) {
return (a.value.year > b.value.year ? -1 :
a.value.year < b.value.year ? 1 : 0);
});
o.response.rows.length = 5;
o.response.total_rows = 5;
o.spy(o, "value", o.response,
"allDocs (complex queries year >= 1980, index used to do query)");
o.jio.allDocs({
// "query":'(year: >= "1980" AND year: < "2000")',
"query": '(year: >= "1980")',
"limit": [0, 5],
"sort_on": [['year', 'descending']],
"select_list": ['title', 'year']
}, o.f);
o.tick(o);
// complex queries
o.spy(o, "value", {"total_rows": 0, "rows": []},
"allDocs (complex queries year >= 1980, can't use index)");
o.jio.allDocs({
// "query":'(year: >= "1980" AND year: < "2000")',
"query": '(year: >= "1980")',
"limit": [0, 5],
"sort_on": [['year', 'descending']],
"select_list": ['director', 'year']
}, o.f);
o.tick(o);
// empty query returns all
o.response = JSON.parse(JSON.stringify(o.allDocsResponse));
i = 0;
while (i < o.response.rows.length) {
o.response.rows[i].value.title =
o.response.rows[i].doc.title;
delete o.response.rows[i].doc;
i += 1;
}
o.response.rows.sort(function (a, b) {
return (a.value.title > b.value.title ? -1 :
a.value.title < b.value.title ? 1 : 0);
function listDocumentsFromLocalTest(answer) {
if (answer && answer.data && Array.isArray(answer.data.rows)) {
answer.data.rows.sort(function (a, b) {
return a.id.length < b.id.length ? -1 : (
a.id.length > b.id.length ? 1 : (
a.id < b.id ? -1 : a.id > b.id ? 1 : 0
)
);
});
o.spy(o, "value", o.response,
"allDocs (empty query in complex query)");
o.jio.allDocs({
"sort_on": [['title', 'descending']],
"select_list": ['title']
}, o.f);
o.tick(o);
}
deepEqual(answer, {
"data": {
"total_rows": 8,
"rows": [{
"id": "A",
"key": "A",
"value": {}
}, {
"id": "B",
"key": "B",
"value": {}
}, {
"id": "C",
"key": "C",
"value": {}
}, {
"id": "D",
"key": "D",
"value": {}
}, {
"id": "b",
"key": "b",
"value": {}
}, {
"id": "ce",
"key": "ce",
"value": {}
}, {
"id": "dee",
"key": "dee",
"value": {}
}, {
"id": shared.created_document_id,
"key": shared.created_document_id,
"value": {}
}]
},
"method": "allDocs",
"result": "success",
"status": 200,
"statusText": "Ok"
}, "List 8 documents from local (4 document + 4 databases)");
}
// function removeCreatedDocuments() {
// return success(RSVP.all([
// jio_index.remove({"_id": shared.created_document_id}),
// jio_index.remove({"_id": "b"}),
// jio_index.remove({"_id": "ce"}),
// jio_index.remove({"_id": "dee"})
// ]));
// }
// function removeCreatedDocumentsTest(answers) {
// deepEqual(answers[0], {
// "id": shared.created_document_id,
// "method": "remove",
// "result": "success",
// "status": 204,
// "statusText": "No Content"
// }, "Remove first document");
// deepEqual(answers[1], {
// "id": "b",
// "method": "remove",
// "result": "success",
// "status": 204,
// "statusText": "No Content"
// }, "Remove document 'b'");
// deepEqual(answers[2], {
// "id": "ce",
// "method": "remove",
// "result": "success",
// "status": 204,
// "statusText": "No Content"
// }, "Remove document 'ce'");
// deepEqual(answers[3], {
// "id": "dee",
// "method": "remove",
// "result": "success",
// "status": 204,
// "statusText": "No Content"
// }, "Remove document 'dee'");
// }
// XXX the 2 following functions should be replaced by the 2 commented
// previous ones (which don't work yet)
function removeCreatedDocuments() {
return sequence([function () {
return jio_index.remove({"_id": shared.created_document_id});
}, function () {
return jio_index.remove({"_id": "b"});
}, function () {
return jio_index.remove({"_id": "ce"});
}, function () {
return jio_index.remove({"_id": "dee"});
}]);
}
function removeCreatedDocumentsTest(last_answer) {
deepEqual(last_answer, {
"id": "dee",
"method": "remove",
"result": "success",
"status": 204,
"statusText": "No Content"
}, "Remove first document, 'b', 'ce' and 'dee' (testing 'dee' only)");
}
function listEmptyIndexes() {
return RSVP.all([
success(jio_index.allDocs({"select_list": ["contributor"]})),
success(jio_index.allDocs({"select_list": ["title"]})),
success(jio_index.allDocs({"select_list": ["title", "year"]})),
success(jio_index.allDocs({"select_list": ["author"]})),
success(jio_index.allDocs())
]);
}
function listEmptyIndexesTest(answers) {
deepEqual(answers[0], {
"data": {
"total_rows": 7000,
"rows": []
},
"method": "allDocs",
"result": "success",
"status": 200,
"statusText": "Ok"
}, "List empty indexes 'contributor'");
deepEqual(answers[1], {
"data": {
"total_rows": 7000,
"rows": []
},
"method": "allDocs",
"result": "success",
"status": 204,
"statusText": "No Content"
}, "List empty indexes 'title'");
deepEqual(answers[2], {
"data": {
"total_rows": 7000,
"rows": []
},
"method": "allDocs",
"result": "success",
"status": 204,
"statusText": "No Content"
}, "List empty indexes 'title', 'year'");
deepEqual(answers[3], {
"data": {
"total_rows": 7000,
"rows": []
},
"method": "allDocs",
"result": "success",
"status": 204,
"statusText": "No Content"
}, "List empty indexes 'author'");
deepEqual(answers[4], {
"data": {
"total_rows": 7000,
"rows": []
},
"method": "allDocs",
"result": "success",
"status": 204,
"statusText": "No Content"
}, "List default empty indexes");
}
// // XXX the 2 following functions should be replaced by the 2 commented
// // previous ones (which don't work yet)
// function removeCreatedDocuments() {
// return sequence([function () {
// return jio_index.remove({"_id": shared.created_document_id});
// }, function () {
// return jio_index.remove({"_id": "b"});
// }, function () {
// return jio_index.remove({"_id": "ce"});
// }, function () {
// return jio_index.remove({"_id": "dee"});
// }]);
// }
// function removeCreatedDocumentsTest(last_answer) {
// deepEqual(last_answer, {
// "id": "dee",
// "method": "remove",
// "result": "success",
// "status": 204,
// "statusText": "No Content"
// }, "Remove first document, 'b', 'ce' and 'dee' (testing 'dee' only)");
// }
function unexpectedError(error) {
if (error instanceof Error) {
deepEqual([
error.name + ": " + error.message,
error
], "UNEXPECTED ERROR", "Unexpected error");
} else {
deepEqual(error, "UNEXPECTED ERROR", "Unexpected error");
}
}
stop();
// # Post new documents, list them and remove them
// post a 201
postNewDocument().then(postNewDocumentTest).
// get 200
then(getCreatedDocument).then(getCreatedDocumentTest).
// post b ce dee 201
then(postSpecificDocuments).then(postSpecificDocumentsTest).
// allD 200 1 documents from index contributor
then(listDocumentsFromIndexContributor).
then(listDocumentsFromIndexContributorTest).
// allD 200 2 documents from index title
then(listDocumentsFromIndexTitle).
then(listDocumentsFromIndexTitleTest).
// allD 200 2 documents from index title year
then(listDocumentsFromIndexTitleYear).
then(listDocumentsFromIndexTitleYearTest).
// allD 200 0 documents from index author
then(listDocumentsFromIndexAuthor).
then(listDocumentsFromIndexAuthorTest).
// allD 200 0 documents from nothing (no select_list option)
then(listDocumentsFromNothing).
then(listDocumentsFromNothingTest).
// allD 200 8 documents from local
then(listDocumentsFromLocal).then(listDocumentsFromLocalTest).
// remove a b ce dee 204
then(removeCreatedDocuments).then(removeCreatedDocumentsTest).
// allD 200 empty indexes
then(listEmptyIndexes).then(listEmptyIndexesTest).
// // # Create and update documents, and some attachment and remove them
// // put 201
// then(putNewDocument).then(putNewDocumentTest).
// // get 200
// then(getCreatedDocument2).then(getCreatedDocument2Test).
// // post 409
// then(postSameDocument).then(postSameDocumentTest).
// // putA a 204
// then(createAttachment).then(createAttachmentTest).
// // putA a 204
// then(updateAttachment).then(updateAttachmentTest).
// // putA b 204
// then(createAnotherAttachment).then(createAnotherAttachmentTest).
// // put 204
// then(updateLastDocument).then(updateLastDocumentTest).
// // getA a 200
// then(getFirstAttachment).then(getFirstAttachmentTest).
// // getA b 200
// then(getSecondAttachment).then(getSecondAttachmentTest).
// // get 200
// then(getLastDocument).then(getLastDocumentTest).
// // removeA b 204
// then(removeSecondAttachment).then(removeSecondAttachmentTest).
// // getA b 404
// then(getInexistentSecondAttachment).
// then(getInexistentSecondAttachmentTest).
// // get 200
// then(getOneAttachmentDocument).then(getOneAttachmentDocumentTest).
// // removeA b 404
//then(removeSecondAttachmentAgain).then(removeSecondAttachmentAgainTest).
// // remove 204
// then(removeDocument).then(removeDocumentTest).
// // getA a 404
//then(getInexistentFirstAttachment)
//.then(getInexistentFirstAttachmentTest).
// // get 404
// then(getInexistentDocument).then(getInexistentDocumentTest).
// // remove 404
// then(removeInexistentDocument).then(removeInexistentDocumentTest).
// // check 204
// //then(checkDocument).done(checkDocumentTest).
// //then(checkStorage).done(checkStorageTest).
fail(unexpectedError).
always(start);
util.closeAndcleanUpJio(o.jio);
});
}));
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