Commit e6c460b4 authored by Romain Courteaud's avatar Romain Courteaud

Release version 3.6.0

Include new google drive storage and performance improvement for QueryStorage's allDocs.
parent d005d845
......@@ -3889,229 +3889,6 @@ if (typeof define === 'function' && define.amd) {
makeGlobal();
}
}).call(this);
;/*jslint indent: 2, maxlen: 80, sloppy: true */
var query_class_dict = {};
;/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global parseStringToObject: true, emptyFunction: true, sortOn: true, limit:
true, select: true, window, stringEscapeRegexpCharacters: true,
deepClone, RSVP*/
/**
* The query to use to filter a list of objects.
* This is an abstract class.
*
* @class Query
* @constructor
*/
function Query() {
/**
* Called before parsing the query. Must be overridden!
*
* @method onParseStart
* @param {Object} object The object shared in the parse process
* @param {Object} option Some option gave in parse()
*/
this.onParseStart = emptyFunction;
/**
* Called when parsing a simple query. Must be overridden!
*
* @method onParseSimpleQuery
* @param {Object} object The object shared in the parse process
* @param {Object} option Some option gave in parse()
*/
this.onParseSimpleQuery = emptyFunction;
/**
* Called when parsing a complex query. Must be overridden!
*
* @method onParseComplexQuery
* @param {Object} object The object shared in the parse process
* @param {Object} option Some option gave in parse()
*/
this.onParseComplexQuery = emptyFunction;
/**
* Called after parsing the query. Must be overridden!
*
* @method onParseEnd
* @param {Object} object The object shared in the parse process
* @param {Object} option Some option gave in parse()
*/
this.onParseEnd = emptyFunction;
}
/**
* Filter the item list with matching item only
*
* @method exec
* @param {Array} item_list The list of object
* @param {Object} [option] Some operation option
* @param {Array} [option.select_list] A object keys to retrieve
* @param {Array} [option.sort_on] Couples of object keys and "ascending"
* or "descending"
* @param {Array} [option.limit] Couple of integer, first is an index and
* second is the length.
*/
Query.prototype.exec = function (item_list, option) {
var i, promises = [];
if (!Array.isArray(item_list)) {
throw new TypeError("Query().exec(): Argument 1 is not of type 'array'");
}
if (option === undefined) {
option = {};
}
if (typeof option !== 'object') {
throw new TypeError("Query().exec(): " +
"Optional argument 2 is not of type 'object'");
}
for (i = 0; i < item_list.length; i += 1) {
if (!item_list[i]) {
promises.push(RSVP.resolve(false));
} else {
promises.push(this.match(item_list[i]));
}
}
return new RSVP.Queue()
.push(function () {
return RSVP.all(promises);
})
.push(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);
}
})
.push(function () {
if (option.limit) {
return limit(option.limit, item_list);
}
})
.push(function () {
return select(option.select_list || [], item_list);
})
.push(function () {
return item_list;
});
};
/**
* Test if an item matches this query
*
* @method match
* @param {Object} item The object to test
* @return {Boolean} true if match, false otherwise
*/
Query.prototype.match = function () {
return RSVP.resolve(true);
};
/**
* Browse the Query in deep calling parser method in each step.
*
* `onParseStart` is called first, on end `onParseEnd` is called.
* It starts from the simple queries at the bottom of the tree calling the
* parser method `onParseSimpleQuery`, and go up calling the
* `onParseComplexQuery` method.
*
* @method parse
* @param {Object} option Any options you want (except 'parsed')
* @return {Any} The parse result
*/
Query.prototype.parse = function (option) {
var that = this,
object;
/**
* The recursive parser.
*
* @param {Object} object The object shared in the parse process
* @param {Object} options Some options usable in the parseMethods
* @return {Any} The parser result
*/
function recParse(object, option) {
var query = object.parsed,
queue = new RSVP.Queue(),
i;
function enqueue(j) {
queue
.push(function () {
object.parsed = query.query_list[j];
return recParse(object, option);
})
.push(function () {
query.query_list[j] = object.parsed;
});
}
if (query.type === "complex") {
for (i = 0; i < query.query_list.length; i += 1) {
enqueue(i);
}
return queue
.push(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()))
};
return new RSVP.Queue()
.push(function () {
return that.onParseStart(object, option);
})
.push(function () {
return recParse(object, option);
})
.push(function () {
return that.onParseEnd(object, option);
})
.push(function () {
return object.parsed;
});
};
/**
* Convert this query to a parsable string.
*
* @method toString
* @return {String} The string version of this query
*/
Query.prototype.toString = function () {
return "";
};
/**
* Convert this query to an jsonable object in order to be remake thanks to
* QueryFactory class.
*
* @method serialized
* @return {Object} The jsonable object
*/
Query.prototype.serialized = function () {
return undefined;
};
window.Query = Query;
;/**
* Parse a text request to a json query object tree
*
......@@ -4815,896 +4592,1010 @@ if ((error_count = __NODEJS_parse(string, error_offsets, error_lookaheads)) > 0)
; return result;
} // parseStringToObject
Query.parseStringToObject = parseStringToObject;
;/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global Query: true, query_class_dict: true, inherits: true,
window, QueryFactory, RSVP */
;/*global RSVP, window, parseStringToObject*/
/*jslint nomen: true, maxlen: 90*/
(function (RSVP, window, parseStringToObject) {
"use strict";
/**
* The ComplexQuery inherits from Query, and compares one or several metadata
* values.
*
* @class ComplexQuery
* @extends Query
* @param {Object} [spec={}] The specifications
* @param {String} [spec.operator="AND"] The compare method to use
* @param {String} spec.key The metadata key
* @param {String} spec.value The value of the metadata to compare
*/
function ComplexQuery(spec, key_schema) {
Query.call(this);
var query_class_dict = {},
regexp_escape = /[\-\[\]{}()*+?.,\\\^$|#\s]/g,
regexp_percent = /%/g,
regexp_underscore = /_/g,
regexp_operator = /^(?:AND|OR|NOT)$/i,
regexp_comparaison = /^(?:!?=|<=?|>=?)$/i;
/**
* Logical operator to use to compare object values
* Convert metadata values to array of strings. ex:
*
* "a" -> ["a"],
* {"content": "a"} -> ["a"]
*
* @attribute operator
* @type String
* @default "AND"
* @optional
* @param {Any} value The metadata value
* @return {Array} The value in string array format
*/
this.operator = spec.operator;
function metadataValueToStringArray(value) {
var i, new_value = [];
if (value === undefined) {
return undefined;
}
if (!Array.isArray(value)) {
value = [value];
}
for (i = 0; i < value.length; i += 1) {
if (typeof value[i] === 'object') {
new_value[i] = value[i].content;
} else {
new_value[i] = value[i];
}
}
return new_value;
}
/**
* The sub Query list which are used to query an item.
* A sort function to sort items by key
*
* @attribute query_list
* @type Array
* @default []
* @optional
* @param {String} key The key to sort on
* @param {String} [way="ascending"] 'ascending' or 'descending'
* @return {Function} The sort function
*/
this.query_list = spec.query_list || [];
/*jslint unparam: true*/
this.query_list = this.query_list.map(
// decorate the map to avoid sending the index as key_schema argument
function (o, i) { return QueryFactory.create(o, key_schema); }
);
/*jslint unparam: false*/
}
inherits(ComplexQuery, Query);
ComplexQuery.prototype.operator = "AND";
ComplexQuery.prototype.type = "complex";
/**
* #crossLink "Query/match:method"
*/
ComplexQuery.prototype.match = function (item) {
var operator = this.operator;
if (!(/^(?:AND|OR|NOT)$/i.test(operator))) {
operator = "AND";
function sortFunction(key, way) {
var result;
if (way === 'descending') {
result = 1;
} else if (way === 'ascending') {
result = -1;
} else {
throw new TypeError("Query.sortFunction(): " +
"Argument 2 must be 'ascending' or 'descending'");
}
return function (a, b) {
// this comparison is 5 times faster than json comparison
var i, l;
a = metadataValueToStringArray(a[key]) || [];
b = metadataValueToStringArray(b[key]) || [];
l = a.length > b.length ? a.length : b.length;
for (i = 0; i < l; i += 1) {
if (a[i] === undefined) {
return result;
}
if (b[i] === undefined) {
return -result;
}
if (a[i] > b[i]) {
return -result;
}
if (a[i] < b[i]) {
return result;
}
}
return 0;
};
}
return this[operator.toUpperCase()](item);
};
/**
* #crossLink "Query/toString:method"
*/
ComplexQuery.prototype.toString = function () {
var str_list = [], this_operator = this.operator;
if (this.operator === "NOT") {
str_list.push("NOT (");
str_list.push(this.query_list[0].toString());
str_list.push(")");
return str_list.join(" ");
/**
* Sort a list of items, according to keys and directions.
*
* @param {Array} sort_on_option List of couples [key, direction]
* @param {Array} list The item list to sort
* @return {Array} The filtered list
*/
function sortOn(sort_on_option, list) {
var sort_index;
if (!Array.isArray(sort_on_option)) {
throw new TypeError("jioquery.sortOn(): " +
"Argument 1 is not of type 'array'");
}
for (sort_index = sort_on_option.length - 1; sort_index >= 0;
sort_index -= 1) {
list.sort(sortFunction(
sort_on_option[sort_index][0],
sort_on_option[sort_index][1]
));
}
return list;
}
this.query_list.forEach(function (query) {
str_list.push("(");
str_list.push(query.toString());
str_list.push(")");
str_list.push(this_operator);
});
str_list.length -= 1;
return str_list.join(" ");
};
/**
* #crossLink "Query/serialized:method"
*/
ComplexQuery.prototype.serialized = function () {
var s = {
"type": "complex",
"operator": this.operator,
"query_list": []
};
this.query_list.forEach(function (query) {
s.query_list.push(
typeof query.toJSON === "function" ? query.toJSON() : query
);
});
return s;
};
ComplexQuery.prototype.toJSON = ComplexQuery.prototype.serialized;
/**
* Comparison operator, test if all sub queries match the
* item value
*
* @method AND
* @param {Object} item The item to match
* @return {Boolean} true if all match, false otherwise
*/
ComplexQuery.prototype.AND = function (item) {
var queue = new RSVP.Queue(),
context = this,
i = 0;
function executeNextIfNotFalse(result) {
if (result === false) {
// No need to evaluate the other elements, as one is false
return result;
/**
* Limit a list of items, according to index and length.
*
* @param {Array} limit_option A couple [from, length]
* @param {Array} list The item list to limit
* @return {Array} The filtered list
*/
function limit(limit_option, list) {
if (!Array.isArray(limit_option)) {
throw new TypeError("jioquery.limit(): " +
"Argument 1 is not of type 'array'");
}
if (context.query_list.length === i) {
// No new element to loop on
return true;
if (!Array.isArray(list)) {
throw new TypeError("jioquery.limit(): " +
"Argument 2 is not of type 'array'");
}
queue
.push(function () {
var sub_result = context.query_list[i].match(item);
i += 1;
return sub_result;
})
.push(executeNextIfNotFalse);
list.splice(0, limit_option[0]);
if (limit_option[1]) {
list.splice(limit_option[1]);
}
return list;
}
executeNextIfNotFalse(true);
return queue;
};
/**
* Comparison operator, test if one of the sub queries matches the
* item value
*
* @method OR
* @param {Object} item The item to match
* @return {Boolean} true if one match, false otherwise
*/
ComplexQuery.prototype.OR = function (item) {
var queue = new RSVP.Queue(),
context = this,
i = 0;
function executeNextIfNotTrue(result) {
if (result === true) {
// No need to evaluate the other elements, as one is true
return result;
}
if (context.query_list.length === i) {
// No new element to loop on
return false;
/**
* Filter a list of items, modifying them to select only wanted keys.
*
* @param {Array} select_option Key list to keep
* @param {Array} list The item list to filter
* @return {Array} The filtered list
*/
function select(select_option, list) {
var i, j, new_item;
if (!Array.isArray(select_option)) {
throw new TypeError("jioquery.select(): " +
"Argument 1 is not of type Array");
}
if (!Array.isArray(list)) {
throw new TypeError("jioquery.select(): " +
"Argument 2 is not of type Array");
}
for (i = 0; i < list.length; i += 1) {
new_item = {};
for (j = 0; j < select_option.length; j += 1) {
if (list[i].hasOwnProperty([select_option[j]])) {
new_item[select_option[j]] = list[i][select_option[j]];
}
}
for (j in new_item) {
if (new_item.hasOwnProperty(j)) {
list[i] = new_item;
break;
}
}
}
queue
.push(function () {
var sub_result = context.query_list[i].match(item);
i += 1;
return sub_result;
})
.push(executeNextIfNotTrue);
return list;
}
executeNextIfNotTrue(false);
return queue;
};
/**
* The query to use to filter a list of objects.
* This is an abstract class.
*
* @class Query
* @constructor
*/
function Query() {
/**
* Comparison operator, test if the sub query does not match the
* item value
*
* @method NOT
* @param {Object} item The item to match
* @return {Boolean} true if one match, false otherwise
*/
ComplexQuery.prototype.NOT = function (item) {
return new RSVP.Queue()
.push(function () {
return this.query_list[0].match(item);
})
.push(function (answer) {
return !answer;
});
};
/**
* Called before parsing the query. Must be overridden!
*
* @method onParseStart
* @param {Object} object The object shared in the parse process
* @param {Object} option Some option gave in parse()
*/
// this.onParseStart = emptyFunction;
query_class_dict.complex = ComplexQuery;
/**
* Called when parsing a simple query. Must be overridden!
*
* @method onParseSimpleQuery
* @param {Object} object The object shared in the parse process
* @param {Object} option Some option gave in parse()
*/
// this.onParseSimpleQuery = emptyFunction;
window.ComplexQuery = ComplexQuery;
;/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global window, ComplexQuery, SimpleQuery, Query, parseStringToObject,
query_class_dict */
/**
* Called when parsing a complex query. Must be overridden!
*
* @method onParseComplexQuery
* @param {Object} object The object shared in the parse process
* @param {Object} option Some option gave in parse()
*/
// this.onParseComplexQuery = emptyFunction;
/**
* Provides static methods to create Query object
*
* @class QueryFactory
*/
function QueryFactory() {
return;
}
/**
* Called after parsing the query. Must be overridden!
*
* @method onParseEnd
* @param {Object} object The object shared in the parse process
* @param {Object} option Some option gave in parse()
*/
// this.onParseEnd = emptyFunction;
/**
* Creates Query object from a search text string or a serialized version
* of a Query.
*
* @method create
* @static
* @param {Object,String} object The search text or the serialized version
* of a Query
* @return {Query} A Query object
*/
QueryFactory.create = function (object, key_schema) {
if (object === "") {
return new Query();
return;
}
if (typeof object === "string") {
object = parseStringToObject(object);
}
if (typeof (object || {}).type === "string" &&
query_class_dict[object.type]) {
return new query_class_dict[object.type](object, key_schema);
}
throw new TypeError("QueryFactory.create(): " +
"Argument 1 is not a search text or a parsable object");
};
window.QueryFactory = QueryFactory;
;/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global Query, exports */
function objectToSearchText(query) {
var str_list = [];
if (query.type === "complex") {
str_list.push("(");
(query.query_list || []).forEach(function (sub_query) {
str_list.push(objectToSearchText(sub_query));
str_list.push(query.operator);
});
str_list.length -= 1;
str_list.push(")");
return str_list.join(" ");
}
if (query.type === "simple") {
return (query.key ? query.key + ": " : "") +
(query.operator || "") + ' "' + query.value + '"';
}
throw new TypeError("This object is not a query");
}
Query.objectToSearchText = objectToSearchText;
;/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global Query, inherits, query_class_dict, window,
searchTextToRegExp, RSVP */
var checkKeySchema = function (key_schema) {
var prop;
if (key_schema !== undefined) {
if (typeof key_schema !== 'object') {
throw new TypeError("SimpleQuery().create(): " +
"key_schema is not of type 'object'");
}
// key_set is mandatory
if (key_schema.key_set === undefined) {
throw new TypeError("SimpleQuery().create(): " +
"key_schema has no 'key_set' property");
}
for (prop in key_schema) {
if (key_schema.hasOwnProperty(prop)) {
switch (prop) {
case 'key_set':
case 'cast_lookup':
case 'match_lookup':
break;
default:
throw new TypeError("SimpleQuery().create(): " +
"key_schema has unknown property '" + prop + "'");
}
/**
* Filter the item list with matching item only
*
* @method exec
* @param {Array} item_list The list of object
* @param {Object} [option] Some operation option
* @param {Array} [option.select_list] A object keys to retrieve
* @param {Array} [option.sort_on] Couples of object keys and "ascending"
* or "descending"
* @param {Array} [option.limit] Couple of integer, first is an index and
* second is the length.
*/
Query.prototype.exec = function (item_list, option) {
if (!Array.isArray(item_list)) {
throw new TypeError("Query().exec(): Argument 1 is not of type 'array'");
}
if (option === undefined) {
option = {};
}
if (typeof option !== 'object') {
throw new TypeError("Query().exec(): " +
"Optional argument 2 is not of type 'object'");
}
var context = this,
i;
for (i = item_list.length - 1; i >= 0; i -= 1) {
if (!context.match(item_list[i])) {
item_list.splice(i, 1);
}
}
}
};
if (option.sort_on) {
sortOn(option.sort_on, item_list);
}
/**
* The SimpleQuery inherits from Query, and compares one metadata value
*
* @class SimpleQuery
* @extends Query
* @param {Object} [spec={}] The specifications
* @param {String} [spec.operator="="] The compare method to use
* @param {String} spec.key The metadata key
* @param {String} spec.value The value of the metadata to compare
*/
function SimpleQuery(spec, key_schema) {
Query.call(this);
if (option.limit) {
limit(option.limit, item_list);
}
checkKeySchema(key_schema);
select(option.select_list || [], item_list);
this._key_schema = key_schema || {};
return new RSVP.Queue()
.push(function () {
return item_list;
});
};
/**
* Operator to use to compare object values
* Test if an item matches this query
*
* @attribute operator
* @type String
* @optional
* @method match
* @param {Object} item The object to test
* @return {Boolean} true if match, false otherwise
*/
this.operator = spec.operator;
Query.prototype.match = function () {
return true;
};
/**
* Key of the object which refers to the value to compare
* Browse the Query in deep calling parser method in each step.
*
* `onParseStart` is called first, on end `onParseEnd` is called.
* It starts from the simple queries at the bottom of the tree calling the
* parser method `onParseSimpleQuery`, and go up calling the
* `onParseComplexQuery` method.
*
* @attribute key
* @type String
* @method parse
* @param {Object} option Any options you want (except 'parsed')
* @return {Any} The parse result
*/
this.key = spec.key;
Query.prototype.parse = function (option) {
var that = this,
object;
/**
* The recursive parser.
*
* @param {Object} object The object shared in the parse process
* @param {Object} options Some options usable in the parseMethods
* @return {Any} The parser result
*/
function recParse(object, option) {
var query = object.parsed,
queue = new RSVP.Queue(),
i;
function enqueue(j) {
queue
.push(function () {
object.parsed = query.query_list[j];
return recParse(object, option);
})
.push(function () {
query.query_list[j] = object.parsed;
});
}
if (query.type === "complex") {
for (i = 0; i < query.query_list.length; i += 1) {
enqueue(i);
}
return queue
.push(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()))
};
return new RSVP.Queue()
.push(function () {
return that.onParseStart(object, option);
})
.push(function () {
return recParse(object, option);
})
.push(function () {
return that.onParseEnd(object, option);
})
.push(function () {
return object.parsed;
});
};
/**
* Value is used to do the comparison with the object value
* Convert this query to a parsable string.
*
* @attribute value
* @type String
* @method toString
* @return {String} The string version of this query
*/
this.value = spec.value;
}
inherits(SimpleQuery, Query);
Query.prototype.toString = function () {
return "";
};
SimpleQuery.prototype.type = "simple";
/**
* Convert this query to an jsonable object in order to be remake thanks to
* QueryFactory class.
*
* @method serialized
* @return {Object} The jsonable object
*/
Query.prototype.serialized = function () {
return undefined;
};
var checkKey = function (key) {
var prop;
/**
* Provides static methods to create Query object
*
* @class QueryFactory
*/
function QueryFactory() {
return;
}
if (key.read_from === undefined) {
throw new TypeError("Custom key is missing the read_from property");
/**
* Escapes regexp special chars from a string.
*
* @param {String} string The string to escape
* @return {String} The escaped string
*/
function stringEscapeRegexpCharacters(string) {
return string.replace(regexp_escape, "\\$&");
}
for (prop in key) {
if (key.hasOwnProperty(prop)) {
switch (prop) {
case 'read_from':
case 'cast_to':
case 'equal_match':
break;
default:
throw new TypeError("Custom key has unknown property '" +
prop + "'");
/**
* Inherits the prototype methods from one constructor into another. The
* prototype of `constructor` will be set to a new object created from
* `superConstructor`.
*
* @param {Function} constructor The constructor which inherits the super one
* @param {Function} superConstructor The super constructor
*/
function inherits(constructor, superConstructor) {
constructor.super_ = superConstructor;
constructor.prototype = Object.create(superConstructor.prototype, {
"constructor": {
"configurable": true,
"enumerable": false,
"writable": true,
"value": constructor
}
}
});
}
};
/**
* #crossLink "Query/match:method"
*/
SimpleQuery.prototype.match = function (item) {
var object_value = null,
equal_match = null,
cast_to = null,
matchMethod = null,
operator = this.operator,
value = null,
key = this.key;
/*jslint regexp: true */
if (!(/^(?:!?=|<=?|>=?)$/i.test(operator))) {
// `operator` is not correct, we have to change it to "like" or "="
if (/%/.test(this.value)) {
// `value` contains a non escaped `%`
operator = "like";
} else {
// `value` does not contain non escaped `%`
operator = "=";
/**
* Convert a search text to a regexp.
*
* @param {String} string The string to convert
* @param {Boolean} [use_wildcard_character=true] Use wildcard "%" and "_"
* @return {RegExp} The search text regexp
*/
function searchTextToRegExp(string, use_wildcard_characters) {
if (typeof string !== 'string') {
throw new TypeError("jioquery.searchTextToRegExp(): " +
"Argument 1 is not of type 'string'");
}
if (use_wildcard_characters === false) {
return new RegExp("^" + stringEscapeRegexpCharacters(string) + "$");
}
return new RegExp("^" + stringEscapeRegexpCharacters(string)
.replace(regexp_percent, '.*')
.replace(regexp_underscore, '.') + "$");
}
matchMethod = this[operator];
/**
* The ComplexQuery inherits from Query, and compares one or several metadata
* values.
*
* @class ComplexQuery
* @extends Query
* @param {Object} [spec={}] The specifications
* @param {String} [spec.operator="AND"] The compare method to use
* @param {String} spec.key The metadata key
* @param {String} spec.value The value of the metadata to compare
*/
function ComplexQuery(spec, key_schema) {
Query.call(this);
if (this._key_schema.key_set && this._key_schema.key_set[key] !== undefined) {
key = this._key_schema.key_set[key];
}
/**
* Logical operator to use to compare object values
*
* @attribute operator
* @type String
* @default "AND"
* @optional
*/
this.operator = spec.operator;
/**
* The sub Query list which are used to query an item.
*
* @attribute query_list
* @type Array
* @default []
* @optional
*/
this.query_list = spec.query_list || [];
this.query_list = this.query_list.map(
// decorate the map to avoid sending the index as key_schema argument
function (o) { return QueryFactory.create(o, key_schema); }
);
if (typeof key === 'object') {
checkKey(key);
object_value = item[key.read_from];
}
inherits(ComplexQuery, Query);
equal_match = key.equal_match;
ComplexQuery.prototype.operator = "AND";
ComplexQuery.prototype.type = "complex";
// equal_match can be a string
if (typeof equal_match === 'string') {
// XXX raise error if equal_match not in match_lookup
equal_match = this._key_schema.match_lookup[equal_match];
/**
* #crossLink "Query/match:method"
*/
ComplexQuery.prototype.match = function (item) {
var operator = this.operator;
if (!(regexp_operator.test(operator))) {
operator = "AND";
}
return this[operator.toUpperCase()](item);
};
/**
* #crossLink "Query/toString:method"
*/
ComplexQuery.prototype.toString = function () {
var str_list = [], this_operator = this.operator;
if (this.operator === "NOT") {
str_list.push("NOT (");
str_list.push(this.query_list[0].toString());
str_list.push(")");
return str_list.join(" ");
}
this.query_list.forEach(function (query) {
str_list.push("(");
str_list.push(query.toString());
str_list.push(")");
str_list.push(this_operator);
});
str_list.length -= 1;
return str_list.join(" ");
};
/**
* #crossLink "Query/serialized:method"
*/
ComplexQuery.prototype.serialized = function () {
var s = {
"type": "complex",
"operator": this.operator,
"query_list": []
};
this.query_list.forEach(function (query) {
s.query_list.push(
typeof query.toJSON === "function" ? query.toJSON() : query
);
});
return s;
};
ComplexQuery.prototype.toJSON = ComplexQuery.prototype.serialized;
// equal_match overrides the default '=' operator
if (equal_match !== undefined) {
matchMethod = (operator === "=" || operator === "like" ?
equal_match : matchMethod);
/**
* Comparison operator, test if all sub queries match the
* item value
*
* @method AND
* @param {Object} item The item to match
* @return {Boolean} true if all match, false otherwise
*/
ComplexQuery.prototype.AND = function (item) {
var result = true,
i = 0;
while (result && (i !== this.query_list.length)) {
result = this.query_list[i].match(item);
i += 1;
}
return result;
value = this.value;
cast_to = key.cast_to;
if (cast_to) {
// cast_to can be a string
if (typeof cast_to === 'string') {
// XXX raise error if cast_to not in cast_lookup
cast_to = this._key_schema.cast_lookup[cast_to];
}
};
try {
value = cast_to(value);
} catch (e) {
value = undefined;
}
/**
* Comparison operator, test if one of the sub queries matches the
* item value
*
* @method OR
* @param {Object} item The item to match
* @return {Boolean} true if one match, false otherwise
*/
ComplexQuery.prototype.OR = function (item) {
var result = false,
i = 0;
try {
object_value = cast_to(object_value);
} catch (e) {
object_value = undefined;
}
while ((!result) && (i !== this.query_list.length)) {
result = this.query_list[i].match(item);
i += 1;
}
} else {
object_value = item[key];
value = this.value;
}
if (object_value === undefined || value === undefined) {
return RSVP.resolve(false);
}
return matchMethod(object_value, value);
};
/**
* #crossLink "Query/toString:method"
*/
SimpleQuery.prototype.toString = function () {
return (this.key ? this.key + ":" : "") +
(this.operator ? " " + this.operator : "") + ' "' + this.value + '"';
};
return result;
};
/**
* #crossLink "Query/serialized:method"
*/
SimpleQuery.prototype.serialized = function () {
var object = {
"type": "simple",
"key": this.key,
"value": this.value
};
if (this.operator !== undefined) {
object.operator = this.operator;
}
return object;
};
SimpleQuery.prototype.toJSON = SimpleQuery.prototype.serialized;
/**
* Comparison operator, test if the sub query does not match the
* item value
*
* @method NOT
* @param {Object} item The item to match
* @return {Boolean} true if one match, false otherwise
*/
ComplexQuery.prototype.NOT = function (item) {
return !this.query_list[0].match(item);
};
/**
* Comparison operator, test if this query value matches the item value
*
* @method =
* @param {String} object_value The value to compare
* @param {String} comparison_value The comparison value
* @return {Boolean} true if match, false otherwise
*/
SimpleQuery.prototype["="] = function (object_value, comparison_value) {
var value, i;
if (!Array.isArray(object_value)) {
object_value = [object_value];
}
for (i = 0; i < object_value.length; i += 1) {
value = object_value[i];
if (typeof value === 'object' && value.hasOwnProperty('content')) {
value = value.content;
/**
* Creates Query object from a search text string or a serialized version
* of a Query.
*
* @method create
* @static
* @param {Object,String} object The search text or the serialized version
* of a Query
* @return {Query} A Query object
*/
QueryFactory.create = function (object, key_schema) {
if (object === "") {
return new Query();
}
if (typeof value.cmp === "function") {
return RSVP.resolve(value.cmp(comparison_value) === 0);
if (typeof object === "string") {
object = parseStringToObject(object);
}
if (
searchTextToRegExp(comparison_value.toString(), false).
test(value.toString())
) {
return RSVP.resolve(true);
if (typeof (object || {}).type === "string" &&
query_class_dict[object.type]) {
return new query_class_dict[object.type](object, key_schema);
}
}
return RSVP.resolve(false);
};
throw new TypeError("QueryFactory.create(): " +
"Argument 1 is not a search text or a parsable object");
};
/**
* Comparison operator, test if this query value matches the item value
*
* @method like
* @param {String} object_value The value to compare
* @param {String} comparison_value The comparison value
* @return {Boolean} true if match, false otherwise
*/
SimpleQuery.prototype.like = function (object_value, comparison_value) {
var value, i;
if (!Array.isArray(object_value)) {
object_value = [object_value];
}
for (i = 0; i < object_value.length; i += 1) {
value = object_value[i];
if (typeof value === 'object' && value.hasOwnProperty('content')) {
value = value.content;
function objectToSearchText(query) {
var str_list = [];
if (query.type === "complex") {
str_list.push("(");
(query.query_list || []).forEach(function (sub_query) {
str_list.push(objectToSearchText(sub_query));
str_list.push(query.operator);
});
str_list.length -= 1;
str_list.push(")");
return str_list.join(" ");
}
if (typeof value.cmp === "function") {
return RSVP.resolve(value.cmp(comparison_value) === 0);
if (query.type === "simple") {
return (query.key ? query.key + ": " : "") +
(query.operator || "") + ' "' + query.value + '"';
}
if (
searchTextToRegExp(comparison_value.toString()).test(value.toString())
) {
return RSVP.resolve(true);
throw new TypeError("This object is not a query");
}
function checkKeySchema(key_schema) {
var prop;
if (key_schema !== undefined) {
if (typeof key_schema !== 'object') {
throw new TypeError("SimpleQuery().create(): " +
"key_schema is not of type 'object'");
}
// key_set is mandatory
if (key_schema.key_set === undefined) {
throw new TypeError("SimpleQuery().create(): " +
"key_schema has no 'key_set' property");
}
for (prop in key_schema) {
if (key_schema.hasOwnProperty(prop)) {
switch (prop) {
case 'key_set':
case 'cast_lookup':
case 'match_lookup':
break;
default:
throw new TypeError("SimpleQuery().create(): " +
"key_schema has unknown property '" + prop + "'");
}
}
}
}
}
return RSVP.resolve(false);
};
/**
* Comparison operator, test if this query value does not match the item value
*
* @method !=
* @param {String} object_value The value to compare
* @param {String} comparison_value The comparison value
* @return {Boolean} true if not match, false otherwise
*/
SimpleQuery.prototype["!="] = function (object_value, comparison_value) {
var value, i;
if (!Array.isArray(object_value)) {
object_value = [object_value];
/**
* The SimpleQuery inherits from Query, and compares one metadata value
*
* @class SimpleQuery
* @extends Query
* @param {Object} [spec={}] The specifications
* @param {String} [spec.operator="="] The compare method to use
* @param {String} spec.key The metadata key
* @param {String} spec.value The value of the metadata to compare
*/
function SimpleQuery(spec, key_schema) {
Query.call(this);
checkKeySchema(key_schema);
this._key_schema = key_schema || {};
/**
* Operator to use to compare object values
*
* @attribute operator
* @type String
* @optional
*/
this.operator = spec.operator;
/**
* Key of the object which refers to the value to compare
*
* @attribute key
* @type String
*/
this.key = spec.key;
/**
* Value is used to do the comparison with the object value
*
* @attribute value
* @type String
*/
this.value = spec.value;
}
for (i = 0; i < object_value.length; i += 1) {
value = object_value[i];
if (typeof value === 'object' && value.hasOwnProperty('content')) {
value = value.content;
}
if (typeof value.cmp === "function") {
return RSVP.resolve(value.cmp(comparison_value) !== 0);
inherits(SimpleQuery, Query);
SimpleQuery.prototype.type = "simple";
function checkKey(key) {
var prop;
if (key.read_from === undefined) {
throw new TypeError("Custom key is missing the read_from property");
}
if (
searchTextToRegExp(comparison_value.toString(), false).
test(value.toString())
) {
return RSVP.resolve(false);
for (prop in key) {
if (key.hasOwnProperty(prop)) {
switch (prop) {
case 'read_from':
case 'cast_to':
case 'equal_match':
break;
default:
throw new TypeError("Custom key has unknown property '" +
prop + "'");
}
}
}
}
return RSVP.resolve(true);
};
/**
* Comparison operator, test if this query value is lower than the item value
*
* @method <
* @param {Number, String} object_value The value to compare
* @param {Number, String} comparison_value The comparison value
* @return {Boolean} true if lower, false otherwise
*/
SimpleQuery.prototype["<"] = function (object_value, comparison_value) {
var value;
if (!Array.isArray(object_value)) {
object_value = [object_value];
}
value = object_value[0];
if (typeof value === 'object' && value.hasOwnProperty('content')) {
value = value.content;
}
if (typeof value.cmp === "function") {
return RSVP.resolve(value.cmp(comparison_value) < 0);
}
return RSVP.resolve(value < comparison_value);
};
/**
* #crossLink "Query/match:method"
*/
SimpleQuery.prototype.match = function (item) {
var object_value = null,
equal_match = null,
cast_to = null,
matchMethod = null,
operator = this.operator,
value = null,
key = this.key;
if (!(regexp_comparaison.test(operator))) {
// `operator` is not correct, we have to change it to "like" or "="
if (regexp_percent.test(this.value)) {
// `value` contains a non escaped `%`
operator = "like";
} else {
// `value` does not contain non escaped `%`
operator = "=";
}
}
/**
* Comparison operator, test if this query value is equal or lower than the
* item value
*
* @method <=
* @param {Number, String} object_value The value to compare
* @param {Number, String} comparison_value The comparison value
* @return {Boolean} true if equal or lower, false otherwise
*/
SimpleQuery.prototype["<="] = function (object_value, comparison_value) {
var value;
if (!Array.isArray(object_value)) {
object_value = [object_value];
}
value = object_value[0];
if (typeof value === 'object' && value.hasOwnProperty('content')) {
value = value.content;
}
if (typeof value.cmp === "function") {
return RSVP.resolve(value.cmp(comparison_value) <= 0);
}
return RSVP.resolve(value <= comparison_value);
};
matchMethod = this[operator];
/**
* Comparison operator, test if this query value is greater than the item
* value
*
* @method >
* @param {Number, String} object_value The value to compare
* @param {Number, String} comparison_value The comparison value
* @return {Boolean} true if greater, false otherwise
*/
SimpleQuery.prototype[">"] = function (object_value, comparison_value) {
var value;
if (!Array.isArray(object_value)) {
object_value = [object_value];
}
value = object_value[0];
if (typeof value === 'object' && value.hasOwnProperty('content')) {
value = value.content;
}
if (typeof value.cmp === "function") {
return RSVP.resolve(value.cmp(comparison_value) > 0);
}
return RSVP.resolve(value > comparison_value);
};
if (this._key_schema.key_set && this._key_schema.key_set[key] !== undefined) {
key = this._key_schema.key_set[key];
}
/**
* Comparison operator, test if this query value is equal or greater than the
* item value
*
* @method >=
* @param {Number, String} object_value The value to compare
* @param {Number, String} comparison_value The comparison value
* @return {Boolean} true if equal or greater, false otherwise
*/
SimpleQuery.prototype[">="] = function (object_value, comparison_value) {
var value;
if (!Array.isArray(object_value)) {
object_value = [object_value];
}
value = object_value[0];
if (typeof value === 'object' && value.hasOwnProperty('content')) {
value = value.content;
}
if (typeof value.cmp === "function") {
return RSVP.resolve(value.cmp(comparison_value) >= 0);
}
return RSVP.resolve(value >= comparison_value);
};
if (typeof key === 'object') {
checkKey(key);
object_value = item[key.read_from];
query_class_dict.simple = SimpleQuery;
equal_match = key.equal_match;
window.SimpleQuery = SimpleQuery;
;/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global Query, RSVP, deepClone */
// equal_match can be a string
if (typeof equal_match === 'string') {
// XXX raise error if equal_match not in match_lookup
equal_match = this._key_schema.match_lookup[equal_match];
}
/**
* Escapes regexp special chars from a string.
*
* @param {String} string The string to escape
* @return {String} The escaped string
*/
function stringEscapeRegexpCharacters(string) {
if (typeof string === "string") {
return string.replace(/([\\\.\$\[\]\(\)\{\}\^\?\*\+\-])/g, "\\$1");
}
throw new TypeError("Query.stringEscapeRegexpCharacters(): " +
"Argument no 1 is not of type 'string'");
}
// equal_match overrides the default '=' operator
if (equal_match !== undefined) {
matchMethod = (operator === "=" || operator === "like" ?
equal_match : matchMethod);
}
Query.stringEscapeRegexpCharacters = stringEscapeRegexpCharacters;
value = this.value;
cast_to = key.cast_to;
if (cast_to) {
// cast_to can be a string
if (typeof cast_to === 'string') {
// XXX raise error if cast_to not in cast_lookup
cast_to = this._key_schema.cast_lookup[cast_to];
}
/**
* Convert metadata values to array of strings. ex:
*
* "a" -> ["a"],
* {"content": "a"} -> ["a"]
*
* @param {Any} value The metadata value
* @return {Array} The value in string array format
*/
function metadataValueToStringArray(value) {
var i, new_value = [];
if (value === undefined) {
return undefined;
}
if (!Array.isArray(value)) {
value = [value];
}
for (i = 0; i < value.length; i += 1) {
if (typeof value[i] === 'object') {
new_value[i] = value[i].content;
try {
value = cast_to(value);
} catch (e) {
value = undefined;
}
try {
object_value = cast_to(object_value);
} catch (e) {
object_value = undefined;
}
}
} else {
new_value[i] = value[i];
object_value = item[key];
value = this.value;
}
if (object_value === undefined || value === undefined) {
return false;
}
return matchMethod(object_value, value);
};
/**
* #crossLink "Query/toString:method"
*/
SimpleQuery.prototype.toString = function () {
return (this.key ? this.key + ":" : "") +
(this.operator ? " " + this.operator : "") + ' "' + this.value + '"';
};
/**
* #crossLink "Query/serialized:method"
*/
SimpleQuery.prototype.serialized = function () {
var object = {
"type": "simple",
"key": this.key,
"value": this.value
};
if (this.operator !== undefined) {
object.operator = this.operator;
}
return object;
};
SimpleQuery.prototype.toJSON = SimpleQuery.prototype.serialized;
/**
* Comparison operator, test if this query value matches the item value
*
* @method =
* @param {String} object_value The value to compare
* @param {String} comparison_value The comparison value
* @return {Boolean} true if match, false otherwise
*/
SimpleQuery.prototype["="] = function (object_value, comparison_value) {
var value, i;
if (!Array.isArray(object_value)) {
object_value = [object_value];
}
for (i = 0; i < object_value.length; i += 1) {
value = object_value[i];
if (typeof value === 'object' && value.hasOwnProperty('content')) {
value = value.content;
}
if (typeof value.cmp === "function") {
return (value.cmp(comparison_value) === 0);
}
if (comparison_value.toString() === value.toString()) {
return true;
}
}
}
return new_value;
}
return false;
};
/**
* A sort function to sort items by key
*
* @param {String} key The key to sort on
* @param {String} [way="ascending"] 'ascending' or 'descending'
* @return {Function} The sort function
*/
function sortFunction(key, way) {
var result;
if (way === 'descending') {
result = 1;
} else if (way === 'ascending') {
result = -1;
} else {
throw new TypeError("Query.sortFunction(): " +
"Argument 2 must be 'ascending' or 'descending'");
}
return function (a, b) {
// this comparison is 5 times faster than json comparison
var i, l;
a = metadataValueToStringArray(a[key]) || [];
b = metadataValueToStringArray(b[key]) || [];
l = a.length > b.length ? a.length : b.length;
for (i = 0; i < l; i += 1) {
if (a[i] === undefined) {
return result;
}
if (b[i] === undefined) {
return -result;
/**
* Comparison operator, test if this query value matches the item value
*
* @method like
* @param {String} object_value The value to compare
* @param {String} comparison_value The comparison value
* @return {Boolean} true if match, false otherwise
*/
SimpleQuery.prototype.like = function (object_value, comparison_value) {
var value, i;
if (!Array.isArray(object_value)) {
object_value = [object_value];
}
for (i = 0; i < object_value.length; i += 1) {
value = object_value[i];
if (typeof value === 'object' && value.hasOwnProperty('content')) {
value = value.content;
}
if (a[i] > b[i]) {
return -result;
if (typeof value.cmp === "function") {
return (value.cmp(comparison_value) === 0);
}
if (a[i] < b[i]) {
return result;
if (
searchTextToRegExp(comparison_value.toString()).test(value.toString())
) {
return true;
}
}
return 0;
return false;
};
}
/**
* Inherits the prototype methods from one constructor into another. The
* prototype of `constructor` will be set to a new object created from
* `superConstructor`.
*
* @param {Function} constructor The constructor which inherits the super one
* @param {Function} superConstructor The super constructor
*/
function inherits(constructor, superConstructor) {
constructor.super_ = superConstructor;
constructor.prototype = Object.create(superConstructor.prototype, {
"constructor": {
"configurable": true,
"enumerable": false,
"writable": true,
"value": constructor
/**
* Comparison operator, test if this query value does not match the item value
*
* @method !=
* @param {String} object_value The value to compare
* @param {String} comparison_value The comparison value
* @return {Boolean} true if not match, false otherwise
*/
SimpleQuery.prototype["!="] = function (object_value, comparison_value) {
var value, i;
if (!Array.isArray(object_value)) {
object_value = [object_value];
}
for (i = 0; i < object_value.length; i += 1) {
value = object_value[i];
if (typeof value === 'object' && value.hasOwnProperty('content')) {
value = value.content;
}
if (typeof value.cmp === "function") {
return (value.cmp(comparison_value) !== 0);
}
if (comparison_value.toString() === value.toString()) {
return false;
}
}
});
}
/**
* Does nothing
*/
function emptyFunction() {
return;
}
return true;
};
/**
* Filter a list of items, modifying them to select only wanted keys. If
* `clone` is true, then the method will act on a cloned list.
*
* @param {Array} select_option Key list to keep
* @param {Array} list The item list to filter
* @param {Boolean} [clone=false] If true, modifies a clone of the list
* @return {Array} The filtered list
*/
function select(select_option, list, clone) {
var i, j, new_item;
if (!Array.isArray(select_option)) {
throw new TypeError("jioquery.select(): " +
"Argument 1 is not of type Array");
}
if (!Array.isArray(list)) {
throw new TypeError("jioquery.select(): " +
"Argument 2 is not of type Array");
}
if (clone === true) {
list = deepClone(list);
}
for (i = 0; i < list.length; i += 1) {
new_item = {};
for (j = 0; j < select_option.length; j += 1) {
if (list[i].hasOwnProperty([select_option[j]])) {
new_item[select_option[j]] = list[i][select_option[j]];
}
/**
* Comparison operator, test if this query value is lower than the item value
*
* @method <
* @param {Number, String} object_value The value to compare
* @param {Number, String} comparison_value The comparison value
* @return {Boolean} true if lower, false otherwise
*/
SimpleQuery.prototype["<"] = function (object_value, comparison_value) {
var value;
if (!Array.isArray(object_value)) {
object_value = [object_value];
}
for (j in new_item) {
if (new_item.hasOwnProperty(j)) {
list[i] = new_item;
break;
}
value = object_value[0];
if (typeof value === 'object' && value.hasOwnProperty('content')) {
value = value.content;
}
}
return list;
}
if (typeof value.cmp === "function") {
return (value.cmp(comparison_value) < 0);
}
return (value < comparison_value);
};
Query.select = select;
/**
* Comparison operator, test if this query value is equal or lower than the
* item value
*
* @method <=
* @param {Number, String} object_value The value to compare
* @param {Number, String} comparison_value The comparison value
* @return {Boolean} true if equal or lower, false otherwise
*/
SimpleQuery.prototype["<="] = function (object_value, comparison_value) {
var value;
if (!Array.isArray(object_value)) {
object_value = [object_value];
}
value = object_value[0];
if (typeof value === 'object' && value.hasOwnProperty('content')) {
value = value.content;
}
if (typeof value.cmp === "function") {
return (value.cmp(comparison_value) <= 0);
}
return (value <= comparison_value);
};
/**
* Sort a list of items, according to keys and directions. If `clone` is true,
* then the method will act on a cloned list.
*
* @param {Array} sort_on_option List of couples [key, direction]
* @param {Array} list The item list to sort
* @param {Boolean} [clone=false] If true, modifies a clone of the list
* @return {Array} The filtered list
*/
function sortOn(sort_on_option, list, clone) {
var sort_index;
if (!Array.isArray(sort_on_option)) {
throw new TypeError("jioquery.sortOn(): " +
"Argument 1 is not of type 'array'");
}
if (clone) {
list = deepClone(list);
}
for (sort_index = sort_on_option.length - 1; sort_index >= 0;
sort_index -= 1) {
list.sort(sortFunction(
sort_on_option[sort_index][0],
sort_on_option[sort_index][1]
));
}
return list;
}
/**
* Comparison operator, test if this query value is greater than the item
* value
*
* @method >
* @param {Number, String} object_value The value to compare
* @param {Number, String} comparison_value The comparison value
* @return {Boolean} true if greater, false otherwise
*/
SimpleQuery.prototype[">"] = function (object_value, comparison_value) {
var value;
if (!Array.isArray(object_value)) {
object_value = [object_value];
}
value = object_value[0];
if (typeof value === 'object' && value.hasOwnProperty('content')) {
value = value.content;
}
if (typeof value.cmp === "function") {
return (value.cmp(comparison_value) > 0);
}
return (value > comparison_value);
};
Query.sortOn = sortOn;
/**
* Comparison operator, test if this query value is equal or greater than the
* item value
*
* @method >=
* @param {Number, String} object_value The value to compare
* @param {Number, String} comparison_value The comparison value
* @return {Boolean} true if equal or greater, false otherwise
*/
SimpleQuery.prototype[">="] = function (object_value, comparison_value) {
var value;
if (!Array.isArray(object_value)) {
object_value = [object_value];
}
value = object_value[0];
if (typeof value === 'object' && value.hasOwnProperty('content')) {
value = value.content;
}
if (typeof value.cmp === "function") {
return (value.cmp(comparison_value) >= 0);
}
return (value >= comparison_value);
};
/**
* Limit a list of items, according to index and length. If `clone` is true,
* then the method will act on a cloned list.
*
* @param {Array} limit_option A couple [from, length]
* @param {Array} list The item list to limit
* @param {Boolean} [clone=false] If true, modifies a clone of the list
* @return {Array} The filtered list
*/
function limit(limit_option, list, clone) {
if (!Array.isArray(limit_option)) {
throw new TypeError("jioquery.limit(): " +
"Argument 1 is not of type 'array'");
}
if (!Array.isArray(list)) {
throw new TypeError("jioquery.limit(): " +
"Argument 2 is not of type 'array'");
}
if (clone) {
list = deepClone(list);
}
list.splice(0, limit_option[0]);
if (limit_option[1]) {
list.splice(limit_option[1]);
}
return list;
}
query_class_dict.simple = SimpleQuery;
query_class_dict.complex = ComplexQuery;
Query.limit = limit;
Query.parseStringToObject = parseStringToObject;
Query.objectToSearchText = objectToSearchText;
/**
* Convert a search text to a regexp.
*
* @param {String} string The string to convert
* @param {Boolean} [use_wildcard_character=true] Use wildcard "%" and "_"
* @return {RegExp} The search text regexp
*/
function searchTextToRegExp(string, use_wildcard_characters) {
if (typeof string !== 'string') {
throw new TypeError("jioquery.searchTextToRegExp(): " +
"Argument 1 is not of type 'string'");
}
if (use_wildcard_characters === false) {
return new RegExp("^" + stringEscapeRegexpCharacters(string) + "$");
}
return new RegExp("^" + stringEscapeRegexpCharacters(string).replace(
/%/g,
".*"
).replace(
/_/g,
"."
) + "$");
}
window.Query = Query;
window.SimpleQuery = SimpleQuery;
window.ComplexQuery = ComplexQuery;
window.QueryFactory = QueryFactory;
Query.searchTextToRegExp = searchTextToRegExp;
}(RSVP, window, parseStringToObject));
;/*global window, moment */
/*jslint nomen: true, maxlen: 200*/
(function (window, moment) {
......@@ -5950,70 +5841,6 @@ Query.searchTextToRegExp = searchTextToRegExp;
}
util.ajax = ajax;
/**
* Clones all native object in deep. Managed types: Object, Array, String,
* Number, Boolean, Function, null.
*
* It can also clone object which are serializable, like Date.
*
* To make a class serializable, you need to implement the `toJSON` function
* which returns a JSON representation of the object. The returned value is
* used as first parameter of the object constructor.
*
* @param {A} object The object to clone
* @return {A} The cloned object
*/
function deepClone(object) {
var i, cloned;
if (Array.isArray(object)) {
cloned = [];
for (i = 0; i < object.length; i += 1) {
cloned[i] = deepClone(object[i]);
}
return cloned;
}
if (object === null) {
return null;
}
if (typeof object === 'object') {
if (Object.getPrototypeOf(object) === Object.prototype) {
cloned = {};
for (i in object) {
if (object.hasOwnProperty(i)) {
cloned[i] = deepClone(object[i]);
}
}
return cloned;
}
if (object instanceof Date) {
// XXX this block is to enable phantomjs and browsers compatibility with
// Date.prototype.toJSON when it is an invalid date. In phantomjs, it
// returns `"Invalid Date"` but in browsers it returns `null`. In
// browsers, giving `null` as parameter to `new Date()` doesn't return
// an invalid date.
// Cloning a date with `return new Date(object)` has problems on
// Firefox.
// I don't know why... (Tested on Firefox 23)
if (isFinite(object.getTime())) {
return new Date(object.toJSON());
}
return new Date("Invalid Date");
}
// clone serializable objects
if (typeof object.toJSON === 'function') {
return new (Object.getPrototypeOf(object).constructor)(object.toJSON());
}
// cannot clone
return object;
}
return object;
}
util.deepClone = deepClone;
function readBlobAsText(blob, encoding) {
var fr = new FileReader();
return new RSVP.Promise(function (resolve, reject, notify) {
......@@ -7525,13 +7352,13 @@ Query.searchTextToRegExp = searchTextToRegExp;
attachments: {}
};
}
this._database[id].doc = JSON.stringify(metadata);
this._database[id].doc = metadata;
return id;
};
MemoryStorage.prototype.get = function (id) {
try {
return JSON.parse(this._database[id].doc);
return this._database[id].doc;
} catch (error) {
if (error instanceof TypeError) {
throw new jIO.util.jIOError(
......@@ -7619,18 +7446,26 @@ Query.searchTextToRegExp = searchTextToRegExp;
MemoryStorage.prototype.hasCapacity = function (name) {
return (name === "list");
return ((name === "list") || (name === "include"));
};
MemoryStorage.prototype.buildQuery = function () {
MemoryStorage.prototype.buildQuery = function (options) {
var rows = [],
i;
for (i in this._database) {
if (this._database.hasOwnProperty(i)) {
rows.push({
id: i,
value: {}
});
if (options.include_docs === true) {
rows.push({
id: i,
value: {},
doc: this._database[i]
});
} else {
rows.push({
id: i,
value: {}
});
}
}
}
......@@ -8622,6 +8457,250 @@ Query.searchTextToRegExp = searchTextToRegExp;
jIO.addStorage('dav', DavStorage);
}(jIO, RSVP, DOMParser, Blob));
;/*
* Copyright 2015, Nexedi SA
* Released under the LGPL license.
* http://www.gnu.org/licenses/lgpl.html
*/
/**
* JIO Google Drive Storage. Type = "gdrive".
* Google Drive "database" storage.
*/
/*global jIO, Blob, RSVP, UriTemplate, JSON*/
/*jslint nomen: true*/
(function (jIO, Blob, RSVP, UriTemplate, JSON) {
"use strict";
var UPLOAD_URL = "https://www.googleapis.com{/upload}/drive/v2/files{/id}" +
"{?uploadType,access_token}",
upload_template = UriTemplate.parse(UPLOAD_URL),
REMOVE_URL = "https://www.googleapis.com/drive/v2/" +
"files{/id,trash}{?access_token}",
remove_template = UriTemplate.parse(REMOVE_URL),
LIST_URL = "https://www.googleapis.com/drive/v2/files" +
"?prettyPrint=false{&pageToken}&q=trashed=false" +
"&fields=nextPageToken,items(id){&access_token}",
list_template = UriTemplate.parse(LIST_URL),
GET_URL = "https://www.googleapis.com/drive/v2/files{/id}{?alt}",
get_template = UriTemplate.parse(GET_URL);
function handleError(error, id) {
if (error.target.status === 404) {
throw new jIO.util.jIOError(
"Cannot find document: " + id,
404
);
}
throw error;
}
function listPage(result, token) {
var i,
obj;
return new RSVP.Queue()
.push(function () {
return jIO.util.ajax({
"type": "GET",
"url": list_template.expand({
pageToken : (result.nextPageToken || ""),
access_token: token
})
});
})
.push(function (data) {
obj = JSON.parse(data.target.response || data.target.responseText);
for (i = 0; i < obj.items.length; i += 1) {
obj.items[i].value = {};
result.push(obj.items[i]);
}
result.nextPageToken = obj.nextPageToken;
return result;
}, handleError);
}
function checkName(name) {
if (name !== "enclosure") {
throw new jIO.util.jIOError("Only support 'enclosure' attachment", 400);
}
}
/**
* The JIO Google Drive Storage extension
*
* @class GdriveStorage
* @constructor
*/
function GdriveStorage(spec) {
if (spec === undefined || spec.access_token === undefined ||
typeof spec.access_token !== 'string') {
throw new TypeError("Access Token must be a string " +
"which contains more than one character.");
}
if (spec.trashing !== undefined &&
(spec.trashing !== true && spec.trashing !== false)) {
throw new TypeError("trashing parameter" +
" must be a boolean (true or false)");
}
this._trashing = spec.trashing || true;
this._access_token = spec.access_token;
return;
}
function recursiveAllDocs(result, accessToken) {
return new RSVP.Queue()
.push(function () {
return listPage(result, accessToken);
})
.push(function () {
if (result.nextPageToken) {
return recursiveAllDocs(result, accessToken);
}
return result;
});
}
GdriveStorage.prototype.hasCapacity = function (name) {
return (name === "list");
};
GdriveStorage.prototype.buildQuery = function () {
return recursiveAllDocs([], this._access_token);
};
function sendMetaData(id, param, token) {
var boundary = "-------314159265358979323846";
return new RSVP.Queue()
.push(function () {
return jIO.util.ajax({
"type": id ? "PUT" : "POST",
"url": upload_template.expand({
access_token: token,
id: id || [],
upload: id ? [] : "upload",
uploadType: "multipart"
}),
headers: {
"Content-Type" : 'multipart/related; boundary="' + boundary + '"'
},
data: '--' + boundary + '\n' +
'Content-Type: application/json; charset=UTF-8\n\n' +
JSON.stringify(param) + '\n\n--' + boundary + "--"
});
})
.push(function (result) {
var obj = JSON.parse(result.target.responseText);
return obj.id;
},
function (error) {handleError(error, id); });
}
GdriveStorage.prototype.put = function (id, param) {
return sendMetaData(id, param, this._access_token);
};
GdriveStorage.prototype.post = function (param) {
return sendMetaData(undefined, param, this._access_token);
};
function sendData(id, blob, token) {
return new RSVP.Queue()
.push(function () {
return jIO.util.ajax({
"type": "PUT",
"url": upload_template.expand({
access_token: token,
upload: "upload",
id: id,
uploadType: "media"
}),
data: blob
});
})
.push(function (data) {
data = JSON.parse(data.target.responseText);
if (data.mimeType === "application/vnd.google-apps.folder") {
throw new jIO.util.jIOError("cannot put attachments to folder", 400);
}
return data;
}, function (error) {handleError(error, id); });
}
GdriveStorage.prototype.putAttachment = function (id, name, blob) {
checkName(name);
return sendData(id, blob, this._access_token);
};
GdriveStorage.prototype.remove = function (id) {
var that = this;
return new RSVP.Queue()
.push(function () {
return jIO.util.ajax({
type: that._trashing ? "POST" : "DELETE",
url: remove_template.expand({
id : id,
access_token : that._access_token,
trash : that._trashing ? "trash" : []
})
});
})
.push(undefined, function (error) {handleError(error, id); });
};
function getData(id, attach, token) {
return new RSVP.Queue()
.push(function () {
return jIO.util.ajax({
type: "GET",
dataType: attach ? "blob" : "json",
url: get_template.expand({
id: id,
alt: attach ? "media" : [],
access_token: token
}),
headers: {
"Authorization" : "Bearer " + token
}
});
})
.push(function (evt) {
return evt.target.response ||
(attach ? new Blob([evt.target.responseText],
{"type" :
evt.target.responseHeaders["Content-Type"]}) :
JSON.parse(evt.target.responseText));
}, function (error) {handleError(error, id); });
}
GdriveStorage.prototype.get = function (id) {
return getData(id, false, this._access_token);
};
GdriveStorage.prototype.getAttachment = function (id, name) {
checkName(name);
return getData(id, true, this._access_token);
};
GdriveStorage.prototype.allAttachments = function (id) {
var token = this._access_token;
return new RSVP.Queue()
.push(function () {
return getData(id, false, token);
})
.push(function (data) {
if (data.mimeType === "application/vnd.google-apps.folder") {
return {};
}
return {"enclosure": {}};
});
};
jIO.addStorage('gdrive', GdriveStorage);
}(jIO, Blob, RSVP, UriTemplate, JSON));
;/*jslint nomen: true */
/*global RSVP*/
......@@ -8880,10 +8959,10 @@ Query.searchTextToRegExp = searchTextToRegExp;
// }
/*jslint nomen: true, unparam: true */
/*global jIO, UriTemplate, FormData, RSVP, URI, Blob, objectToSearchText,
/*global jIO, UriTemplate, FormData, RSVP, URI, Blob,
SimpleQuery, ComplexQuery*/
(function (jIO, UriTemplate, FormData, RSVP, URI, Blob, objectToSearchText,
(function (jIO, UriTemplate, FormData, RSVP, URI, Blob,
SimpleQuery, ComplexQuery) {
"use strict";
......@@ -9340,14 +9419,14 @@ Query.searchTextToRegExp = searchTextToRegExp;
if (result_list) {
local_roles = result_list;
parsed_query.query_list.splice(i, 1);
query = objectToSearchText(parsed_query);
query = jIO.Query.objectToSearchText(parsed_query);
i = parsed_query.query_list.length;
} else {
result_list = isMultipleLocalRoles(sub_query);
if (result_list) {
local_roles = result_list;
parsed_query.query_list.splice(i, 1);
query = objectToSearchText(parsed_query);
query = jIO.Query.objectToSearchText(parsed_query);
i = parsed_query.query_list.length;
}
}
......@@ -9398,7 +9477,7 @@ Query.searchTextToRegExp = searchTextToRegExp;
jIO.addStorage("erp5", ERP5Storage);
}(jIO, UriTemplate, FormData, RSVP, URI, Blob, objectToSearchText,
}(jIO, UriTemplate, FormData, RSVP, URI, Blob,
SimpleQuery, ComplexQuery));
;/*jslint nomen: true*/
/*global RSVP*/
......
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
{
"name": "jio",
"version": "v3.5.0",
"version": "v3.6.0",
"license": "LGPLv3",
"author": "Nexedi SA",
"contributors": [
......
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