Commit bf46a5d5 authored by Tristan Cavelier's avatar Tristan Cavelier

Queries are now asynchronous

parent f8c1966d
This diff is collapsed.
......@@ -719,28 +719,29 @@
}
}
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;
}
if (option.include_docs) {
db[i] = {
"id": id,
"value": db[i],
"doc": db[i]["_" + now]
};
delete db[i].doc["_" + now];
delete db[i].value["_" + now];
} else {
db[i] = {
"id": id,
"value": db[i]
};
}
}
command.success(200, {"data": {"total_rows": db.length, "rows": db}});
exec(db, option).then(function () {
for (i = 0; i < db.length; i += 1) {
id = db[i]._id;
if (delete_id) {
delete db[i]._id;
}
if (option.include_docs) {
db[i] = {
"id": id,
"value": db[i],
"doc": db[i]["_" + now]
};
delete db[i].doc["_" + now];
delete db[i].value["_" + now];
} else {
db[i] = {
"id": id,
"value": db[i]
};
}
}
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": []}});
......
......@@ -446,26 +446,27 @@
});
}
complex_queries.QueryFactory.create(options.query || "").
exec(document_list, options);
document_list = document_list.map(function (value) {
var o = {
"id": value._id,
"key": value._id
};
if (options.include_docs === true) {
o.doc = document_object[value._id];
delete document_object[value._id];
}
if (delete_id) {
delete value._id;
}
o.value = value;
return o;
});
command.success({"data": {
"total_rows": document_list.length,
"rows": document_list
}});
exec(document_list, options).then(function () {
document_list = document_list.map(function (value) {
var o = {
"id": value._id,
"key": value._id
};
if (options.include_docs === true) {
o.doc = document_object[value._id];
delete document_object[value._id];
}
if (delete_id) {
delete value._id;
}
o.value = value;
return o;
});
command.success({"data": {
"total_rows": document_list.length,
"rows": document_list
}});
});
}
};
......
......@@ -19,8 +19,8 @@
return module(exports);
}
window.complex_queries = {};
module(window.complex_queries);
}(['exports'], function (to_export) {
module(window.complex_queries, RSVP);
}(['exports'], function (to_export, RSVP) {
"use strict";
/**
......
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global Query: true, query_class_dict: true, inherits: true,
_export: true, QueryFactory: true */
_export, QueryFactory, RSVP, sequence */
/**
* The ComplexQuery inherits from Query, and compares one or several metadata
......@@ -56,8 +56,7 @@ ComplexQuery.prototype.toString = function () {
str_list.push(query.toString());
str_list.push(this_operator);
});
str_list.pop(); // remove last operator
str_list.push(")");
str_list[str_list.length - 1] = ")"; // replace last operator
return str_list.join(" ");
};
......@@ -86,13 +85,41 @@ ComplexQuery.prototype.serialized = function () {
* @return {Boolean} true if all match, false otherwise
*/
ComplexQuery.prototype.AND = function (item, wildcard_character) {
var i;
for (i = 0; i < this.query_list.length; i += 1) {
if (!this.query_list[i].match(item, wildcard_character)) {
return false;
var j, promises = [];
for (j = 0; j < this.query_list.length; j += 1) {
promises.push(this.query_list[j].match(item, wildcard_character));
}
function cancel() {
var i;
for (i = 0; i < promises.length; i += 1) {
if (typeof promises.cancel === 'function') {
promises.cancel();
}
}
}
return true;
return new RSVP.Promise(function (resolve, reject) {
var i, count = 0;
function resolver(value) {
if (!value) {
resolve(false);
}
count += 1;
if (count === promises.length) {
resolve(true);
}
}
function rejecter(err) {
reject(err);
cancel();
}
for (i = 0; i < promises.length; i += 1) {
promises[i].then(resolver, rejecter);
}
}, cancel);
};
/**
......@@ -105,13 +132,41 @@ ComplexQuery.prototype.AND = function (item, wildcard_character) {
* @return {Boolean} true if one match, false otherwise
*/
ComplexQuery.prototype.OR = function (item, wildcard_character) {
var i;
for (i = 0; i < this.query_list.length; i += 1) {
if (this.query_list[i].match(item, wildcard_character)) {
return true;
var j, promises = [];
for (j = 0; j < this.query_list.length; j += 1) {
promises.push(this.query_list[j].match(item, wildcard_character));
}
function cancel() {
var i;
for (i = 0; i < promises.length; i += 1) {
if (typeof promises.cancel === 'function') {
promises.cancel();
}
}
}
return false;
return new RSVP.Promise(function (resolve, reject) {
var i, count = 0;
function resolver(value) {
if (value) {
resolve(true);
}
count += 1;
if (count === promises.length) {
resolve(false);
}
}
function rejecter(err) {
reject(err);
cancel();
}
for (i = 0; i < promises.length; i += 1) {
promises[i].then(resolver, rejecter);
}
}, cancel);
};
/**
......@@ -124,7 +179,11 @@ ComplexQuery.prototype.OR = function (item, wildcard_character) {
* @return {Boolean} true if one match, false otherwise
*/
ComplexQuery.prototype.NOT = function (item, wildcard_character) {
return !this.query_list[0].match(item, wildcard_character);
return sequence([function () {
return this.query_list[0].match(item, wildcard_character);
}, function (answer) {
return !answer;
}]);
};
query_class_dict.complex = ComplexQuery;
......
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global parseStringToObject: true, emptyFunction: true, sortOn: true, limit:
true, select: true, _export: true, stringEscapeRegexpCharacters: true,
deepClone: true */
deepClone, RSVP, sequence */
/**
* The query to use to filter a list of objects.
......@@ -64,7 +64,7 @@ function Query() {
* second is the length.
*/
Query.prototype.exec = function (item_list, option) {
var i = 0;
var i, promises = [];
if (!Array.isArray(item_list)) {
throw new TypeError("Query().exec(): Argument 1 is not of type 'array'");
}
......@@ -78,20 +78,34 @@ Query.prototype.exec = function (item_list, option) {
if (option.wildcard_character === undefined) {
option.wildcard_character = '%';
}
while (i < item_list.length) {
if (!item_list[i] || !this.match(item_list[i], option.wildcard_character)) {
item_list.splice(i, 1);
for (i = 0; i < item_list.length; i += 1) {
if (!item_list[i]) {
promises.push(RSVP.resolve(false));
} else {
i += 1;
promises.push(this.match(item_list[i], option.wildcard_character));
}
}
if (option.sort_on) {
sortOn(option.sort_on, item_list);
}
if (option.limit) {
limit(option.limit, item_list);
}
select(option.select_list || [], item_list);
return sequence([function () {
return RSVP.all(promises);
}, function (answers) {
var j;
for (j = answers.length - 1; j >= 0; j -= 1) {
if (!answers[j]) {
item_list.splice(j, 1);
}
}
if (option.sort_on) {
return sortOn(option.sort_on, item_list);
}
}, function () {
if (option.limit) {
return limit(option.limit, item_list);
}
}, function () {
return select(option.select_list || [], item_list);
}, function () {
return item_list;
}]);
};
/**
......@@ -103,7 +117,7 @@ Query.prototype.exec = function (item_list, option) {
* @return {Boolean} true if match, false otherwise
*/
Query.prototype.match = function () {
return true;
return RSVP.resolve(true);
};
......@@ -129,24 +143,39 @@ Query.prototype.parse = function (option) {
* @return {Any} The parser result
*/
function recParse(object, option) {
var i, query = object.parsed;
var query = object.parsed;
if (query.type === "complex") {
for (i = 0; i < query.query_list.length; i += 1) {
object.parsed = query.query_list[i];
recParse(object, option);
query.query_list[i] = object.parsed;
}
object.parsed = query;
that.onParseComplexQuery(object, option);
} else if (query.type === "simple") {
that.onParseSimpleQuery(object, option);
return sequence([function () {
return sequence(query.query_list.map(function (v, i) {
/*jslint unparam: true */
return function () {
sequence([function () {
object.parsed = query.query_list[i];
return recParse(object, option);
}, function () {
query.query_list[i] = object.parsed;
}]);
};
}));
}, function () {
object.parsed = query;
return that.onParseComplexQuery(object, option);
}]);
}
if (query.type === "simple") {
return that.onParseSimpleQuery(object, option);
}
}
object = {"parsed": JSON.parse(JSON.stringify(that.serialized()))};
that.onParseStart(object, option);
recParse(object, option);
that.onParseEnd(object, option);
return object.parsed;
return sequence([function () {
return that.onParseStart(object, option);
}, function () {
return recParse(object, option);
}, function () {
return that.onParseEnd(object, option);
}, function () {
return object.parsed;
}]);
};
/**
......
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global Query: true, inherits: true, query_class_dict: true, _export: true,
convertStringToRegExp: true */
convertStringToRegExp, RSVP */
/**
* The SimpleQuery inherits from Query, and compares one metadata value
......@@ -81,7 +81,7 @@ SimpleQuery.prototype.serialized = function () {
* @return {Boolean} true if match, false otherwise
*/
SimpleQuery.prototype["="] = function (object_value, comparison_value,
wildcard_character) {
wildcard_character) {
var value, i;
if (!Array.isArray(object_value)) {
object_value = [object_value];
......@@ -93,12 +93,12 @@ SimpleQuery.prototype["="] = function (object_value, comparison_value,
}
if (comparison_value === undefined) {
if (value === undefined) {
return true;
return RSVP.resolve(true);
}
return false;
return RSVP.resolve(false);
}
if (value === undefined) {
return false;
return RSVP.resolve(false);
}
if (
convertStringToRegExp(
......@@ -106,10 +106,10 @@ SimpleQuery.prototype["="] = function (object_value, comparison_value,
wildcard_character
).test(value.toString())
) {
return true;
return RSVP.resolve(true);
}
}
return false;
return RSVP.resolve(false);
};
/**
......@@ -122,7 +122,7 @@ SimpleQuery.prototype["="] = function (object_value, comparison_value,
* @return {Boolean} true if not match, false otherwise
*/
SimpleQuery.prototype["!="] = function (object_value, comparison_value,
wildcard_character) {
wildcard_character) {
var value, i;
if (!Array.isArray(object_value)) {
object_value = [object_value];
......@@ -134,12 +134,12 @@ SimpleQuery.prototype["!="] = function (object_value, comparison_value,
}
if (comparison_value === undefined) {
if (value === undefined) {
return false;
return RSVP.resolve(false);
}
return true;
return RSVP.resolve(true);
}
if (value === undefined) {
return true;
return RSVP.resolve(true);
}
if (
convertStringToRegExp(
......@@ -147,10 +147,10 @@ SimpleQuery.prototype["!="] = function (object_value, comparison_value,
wildcard_character
).test(value.toString())
) {
return false;
return RSVP.resolve(false);
}
}
return true;
return RSVP.resolve(true);
};
/**
......@@ -170,7 +170,7 @@ SimpleQuery.prototype["<"] = function (object_value, comparison_value) {
if (typeof value === 'object') {
value = value.content;
}
return value < comparison_value;
return RSVP.resolve(value < comparison_value);
};
/**
......@@ -191,7 +191,7 @@ SimpleQuery.prototype["<="] = function (object_value, comparison_value) {
if (typeof value === 'object') {
value = value.content;
}
return value <= comparison_value;
return RSVP.resolve(value <= comparison_value);
};
/**
......@@ -212,7 +212,7 @@ SimpleQuery.prototype[">"] = function (object_value, comparison_value) {
if (typeof value === 'object') {
value = value.content;
}
return value > comparison_value;
return RSVP.resolve(value > comparison_value);
};
/**
......@@ -233,7 +233,7 @@ SimpleQuery.prototype[">="] = function (object_value, comparison_value) {
if (typeof value === 'object') {
value = value.content;
}
return value >= comparison_value;
return RSVP.resolve(value >= comparison_value);
};
query_class_dict.simple = SimpleQuery;
......
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global _export: true */
/*global _export, RSVP */
/**
* Escapes regexp special chars from a string.
......@@ -287,3 +287,43 @@ function convertStringToRegExp(string, wildcard_character) {
}
_export('convertStringToRegExp', convertStringToRegExp);
/**
* 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();
});
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]);
}
}
promises[i].then(resolve, reject, notify);
}, function () {
var i;
for (i = 0; i < promises.length; i += 1) {
promises[i].cancel();
}
});
}
/*jslint indent: 2, maxlen: 80, nomen: true */
/*global define, exports, require, module, complex_queries, window, test, ok,
deepEqual, sinon */
deepEqual, stop, start */
// define([module_name], [dependencies], module);
(function (dependencies, module) {
......@@ -23,11 +23,14 @@
{"identifier": "a"},
{"identifier": ["b", "c"]}
];
complex_queries.QueryFactory.create('').exec(doc_list);
deepEqual(doc_list, [
{"identifier": "a"},
{"identifier": ["b", "c"]}
], 'Nothing done on the list');
stop();
complex_queries.QueryFactory.create('').exec(doc_list).
then(function (doc_list) {
deepEqual(doc_list, [
{"identifier": "a"},
{"identifier": ["b", "c"]}
], 'Nothing done on the list');
}).always(start);
});
test('Simple Query', function () {
......@@ -35,20 +38,26 @@
{"identifier": "a"},
{"identifier": ["b", "c"]}
];
complex_queries.QueryFactory.create('identifier: "a"').exec(doc_list);
deepEqual(doc_list, [
{"identifier": "a"}
], 'Document with several identifier should be removed');
stop();
complex_queries.QueryFactory.create('identifier: "a"').exec(doc_list).
then(function (doc_list) {
deepEqual(doc_list, [
{"identifier": "a"}
], 'Document with several identifier should be removed');
doc_list = [
{"identifier": "a"},
{"identifier": ["a", "b"]}
];
complex_queries.QueryFactory.create('identifier: "a"').exec(doc_list);
deepEqual(doc_list, [
{"identifier": "a"},
{"identifier": ["a", "b"]}
], 'Document with several identifier should be kept');
doc_list = [
{"identifier": "a"},
{"identifier": ["a", "b"]}
];
return complex_queries.QueryFactory.create('identifier: "a"').
exec(doc_list);
}).then(function (doc_list) {
deepEqual(doc_list, [
{"identifier": "a"},
{"identifier": ["a", "b"]}
], 'Document with several identifier should be kept');
}).always(start);
});
test('Complex Query', function () {
......@@ -56,35 +65,40 @@
{"identifier": "a"},
{"identifier": ["b", "c"]}
];
stop();
complex_queries.QueryFactory.create(
'identifier: "b" AND identifier: "c"'
).exec(doc_list);
deepEqual(doc_list, [
{"identifier": ["b", "c"]}
], 'Document with only one identifier should be removed');
).exec(doc_list).then(function (doc_list) {
deepEqual(doc_list, [
{"identifier": ["b", "c"]}
], 'Document with only one identifier should be removed');
doc_list = [
{"identifier": "a"},
{"identifier": ["b", "c"]}
];
complex_queries.QueryFactory.create(
'identifier: "a" OR identifier: "c"'
).exec(doc_list);
deepEqual(doc_list, [
{"identifier": "a"},
{"identifier": ["b", "c"]}
], 'All document matches');
doc_list = [
{"identifier": "a"},
{"identifier": ["b", "c"]}
];
return complex_queries.QueryFactory.create(
'identifier: "a" OR identifier: "c"'
).exec(doc_list);
}).then(function (doc_list) {
deepEqual(doc_list, [
{"identifier": "a"},
{"identifier": ["b", "c"]}
], 'All document matches');
doc_list = [
{"identifier": "a", "title": "o"},
{"identifier": ["b", "c"]}
];
complex_queries.QueryFactory.create(
'(identifier: "a" OR identifier: "b") AND title: "o"'
).exec(doc_list);
deepEqual(doc_list, [
{"identifier": "a", "title": "o"}
], 'Only first document should be kept');
doc_list = [
{"identifier": "a", "title": "o"},
{"identifier": ["b", "c"]}
];
return complex_queries.QueryFactory.create(
'(identifier: "a" OR identifier: "b") AND title: "o"'
).exec(doc_list);
}).then(function (doc_list) {
deepEqual(doc_list, [
{"identifier": "a", "title": "o"}
], 'Only first document should be kept');
}).always(start);
});
test('Wildcard Character', function () {
......@@ -93,40 +107,43 @@
{"identifier": "a%"},
{"identifier": ["ab", "b"]}
];
stop();
complex_queries.QueryFactory.create('identifier: "a%"').exec(doc_list, {
// "wildcard_character": "%" // default
});
deepEqual(doc_list, [
{"identifier": "a"},
{"identifier": "a%"},
{"identifier": ["ab", "b"]}
], 'All documents should be kept');
}).then(function (doc_list) {
deepEqual(doc_list, [
{"identifier": "a"},
{"identifier": "a%"},
{"identifier": ["ab", "b"]}
], 'All documents should be kept');
doc_list = [
{"identifier": "a"},
{"identifier": "a%"},
{"identifier": ["ab", "b"]}
];
complex_queries.QueryFactory.create('identifier: "a%"').exec(doc_list, {
"wildcard_character": null
});
deepEqual(doc_list, [
{"identifier": "a%"}
], 'Document "a%" should be kept');
doc_list = [
{"identifier": "a"},
{"identifier": "a%"},
{"identifier": ["ab", "b"]}
];
doc_list = [
{"identifier": "a"},
{"identifier": "a%"},
{"identifier": ["ab", "b"]}
];
complex_queries.QueryFactory.create('identifier: "b"').exec(doc_list, {
"wildcard_character": "b"
});
deepEqual(doc_list, [
{"identifier": "a"},
{"identifier": "a%"},
{"identifier": ["ab", "b"]}
], 'All documents should be kept');
return complex_queries.QueryFactory.create('identifier: "a%"').
exec(doc_list, {"wildcard_character": null});
}).then(function (doc_list) {
deepEqual(doc_list, [
{"identifier": "a%"}
], 'Document "a%" should be kept');
doc_list = [
{"identifier": "a"},
{"identifier": "a%"},
{"identifier": ["ab", "b"]}
];
return complex_queries.QueryFactory.create('identifier: "b"').
exec(doc_list, {"wildcard_character": "b"});
}).then(function (doc_list) {
deepEqual(doc_list, [
{"identifier": "a"},
{"identifier": "a%"},
{"identifier": ["ab", "b"]}
], 'All documents should be kept');
}).always(start);
});
test("Additional Filters", function () {
......@@ -135,14 +152,16 @@
{"identifier": "a", "title": "f"},
{"identifier": "b", "title": "d"}
];
stop();
complex_queries.QueryFactory.create('').exec(doc_list, {
"select_list": ["title"],
"limit": [2, 1],
"sort_on": [["identifier", "ascending"], ["title", "descending"]]
});
deepEqual(doc_list, [
{"title": "d"}
], 'The first document should be kept');
}).then(function (doc_list) {
deepEqual(doc_list, [
{"title": "d"}
], 'The first document should be kept');
}).always(start);
});
}));
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