Commit 0dffe5c1 authored by Marco Mariani's avatar Marco Mariani

support for key_schema at the simple query and storage level

parent 2f75cd07
...@@ -142,6 +142,7 @@ ...@@ -142,6 +142,7 @@
this._database = localStorage; this._database = localStorage;
this._storage = localstorage; this._storage = localstorage;
this._mode = "localStorage"; this._mode = "localStorage";
this._key_schema = spec.key_schema;
break; break;
} }
} }
...@@ -445,7 +446,8 @@ ...@@ -445,7 +446,8 @@
document_object[meta._id] = meta; document_object[meta._id] = meta;
}); });
} }
complex_queries.QueryFactory.create(options.query || ""). complex_queries.QueryFactory.create(options.query || "",
this._key_schema).
exec(document_list, options); exec(document_list, options);
document_list = document_list.map(function (value) { document_list = document_list.map(function (value) {
var o = { var o = {
......
...@@ -35,7 +35,12 @@ function ComplexQuery(spec) { ...@@ -35,7 +35,12 @@ function ComplexQuery(spec) {
* @optional * @optional
*/ */
this.query_list = spec.query_list || []; this.query_list = spec.query_list || [];
this.query_list = this.query_list.map(QueryFactory.create); /*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); }
);
/*jslint unparam: false*/
} }
inherits(ComplexQuery, Query); inherits(ComplexQuery, Query);
......
...@@ -21,7 +21,7 @@ function QueryFactory() { ...@@ -21,7 +21,7 @@ function QueryFactory() {
* of a Query * of a Query
* @return {Query} A Query object * @return {Query} A Query object
*/ */
QueryFactory.create = function (object) { QueryFactory.create = function (object, key_schema) {
if (object === "") { if (object === "") {
return new Query(); return new Query();
} }
...@@ -30,7 +30,7 @@ QueryFactory.create = function (object) { ...@@ -30,7 +30,7 @@ QueryFactory.create = function (object) {
} }
if (typeof (object || {}).type === "string" && if (typeof (object || {}).type === "string" &&
query_class_dict[object.type]) { query_class_dict[object.type]) {
return new query_class_dict[object.type](object); return new query_class_dict[object.type](object, key_schema);
} }
throw new TypeError("QueryFactory.create(): " + throw new TypeError("QueryFactory.create(): " +
"Argument 1 is not a search text or a parsable object"); "Argument 1 is not a search text or a parsable object");
......
...@@ -12,9 +12,11 @@ ...@@ -12,9 +12,11 @@
* @param {String} spec.key The metadata key * @param {String} spec.key The metadata key
* @param {String} spec.value The value of the metadata to compare * @param {String} spec.value The value of the metadata to compare
*/ */
function SimpleQuery(spec) { function SimpleQuery(spec, key_schema) {
Query.call(this); Query.call(this);
this._key_schema = key_schema || {};
/** /**
* Operator to use to compare object values * Operator to use to compare object values
* *
...@@ -45,33 +47,40 @@ function SimpleQuery(spec) { ...@@ -45,33 +47,40 @@ function SimpleQuery(spec) {
} }
inherits(SimpleQuery, Query); inherits(SimpleQuery, Query);
/** /**
* #crossLink "Query/match:method" * #crossLink "Query/match:method"
*/ */
SimpleQuery.prototype.match = function (item, wildcard_character) { SimpleQuery.prototype.match = function (item, wildcard_character) {
var to_compare = null, matchMethod = null, value = null; var object_value = null, matchMethod = null, value = null, key = this.key;
matchMethod = this[this.operator]; matchMethod = this[this.operator];
if (typeof this.key === 'object') {
to_compare = item[this.key.readFrom]; if (this._key_schema[key] !== undefined) {
key = this._key_schema[key];
}
if (typeof key === 'object') {
object_value = item[key.readFrom];
// defaultMatch overrides the default '=' operator // defaultMatch overrides the default '=' operator
matchMethod = (this.key.defaultMatch || matchMethod); matchMethod = (key.defaultMatch || matchMethod);
// but an explicit operator: key overrides DefaultMatch // but an explicit operator: key overrides DefaultMatch
matchMethod = ((this._spec && this._spec.operator) ? if (this._spec && this._spec.operator) {
this[this.operator] : matchMethod); matchMethod = this[this.operator];
}
value = this.value; value = this.value;
if (this.key.castTo) { if (key.castTo) {
value = this.key.castTo(value); value = key.castTo(value);
to_compare = this.key.castTo(to_compare); object_value = key.castTo(object_value);
} }
} else { } else {
to_compare = item[this.key]; object_value = item[key];
value = this.value; value = this.value;
} }
return matchMethod(to_compare, value, wildcard_character); return matchMethod(object_value, value, wildcard_character);
}; };
/** /**
......
/*jslint indent: 2, maxlen: 100, nomen: true, vars: true */
/*global define, exports, require, module, complex_queries, window, test, ok,
deepEqual, sinon */
// define([module_name], [dependencies], module);
(function (dependencies, module) {
"use strict";
if (typeof define === 'function' && define.amd) {
return define(dependencies, module);
}
if (typeof exports === 'object') {
return module(require('complex_queries'));
}
module(complex_queries);
}(['complex_queries', 'qunit'], function (complex_queries) {
"use strict";
var dateType = function (obj) {
if (Object.prototype.toString.call(obj) === '[object Date]') {
// no need to clone
return obj;
}
return new Date(obj);
};
var sameDay = function (a, b) {
return (
(a.getFullYear() === b.getFullYear()) &&
(a.getMonth() === b.getMonth()) &&
(a.getDay() === b.getDay())
);
};
var sameMonth = function (a, b) {
return (
(a.getFullYear() === b.getFullYear()) &&
(a.getMonth() === b.getMonth())
);
};
var sameYear = function (a, b) {
return (a.getFullYear() === b.getFullYear());
};
var translationEqualityMatcher = function (data) {
return function (object_value, value) {
value = data[value];
return (object_value === value);
};
};
var equalState = translationEqualityMatcher({'ouvert': 'open'});
/*jslint unparam: true*/
var key_schema = {
case_insensitive_identifier: {
readFrom: 'identifier',
defaultMatch: function (object_value, value, wildcard_character) {
return (object_value.toLowerCase() === value.toLowerCase());
}
},
date_day: {
readFrom: 'date',
castTo: dateType,
defaultMatch: sameDay
},
date_month: {
readFrom: 'date',
castTo: dateType,
defaultMatch: sameMonth
},
date_year: {
readFrom: 'date',
castTo: dateType,
defaultMatch: sameYear
},
translated_state: {
readFrom: 'state',
defaultMatch: equalState
}
};
/*jslint unparam: false*/
module('Queries with Key Schema');
test('Keys defined in a Schema can be used like metadata', function () {
var doc_list, docList = function () {
return [
{'identifier': 'a'},
{'identifier': 'A'},
{'identifier': 'b'}
];
};
doc_list = docList();
complex_queries.QueryFactory.create({
type: 'simple',
key: 'case_insensitive_identifier',
value: 'A'
}, key_schema).exec(doc_list);
deepEqual(doc_list, [
{'identifier': 'a'},
{'identifier': 'A'}
], 'Key Schema: case_insensitive_identifier');
});
test('Standard date keys', function () {
var doc_list, docList = function () {
return [
{'identifier': 'a', 'date': '2013-01-01'},
{'identifier': 'b', 'date': '2013-02-01'},
{'identifier': 'bb', 'date': '2013-02-02'},
{'identifier': 'bbb', 'date': '2013-02-03'},
{'identifier': 'c', 'date': '2013-03-03'},
{'identifier': 'd', 'date': '2013-04-04'}
];
};
doc_list = docList();
complex_queries.QueryFactory.create({
type: 'simple',
key: 'date_day',
value: '2013-02-02'
}, key_schema).exec(doc_list);
deepEqual(doc_list, [
{'identifier': 'bb', 'date': '2013-02-02'}
], 'Key Schema: same_day');
doc_list = docList();
complex_queries.QueryFactory.create({
type: 'simple',
key: 'date_month',
value: '2013-02-10'
}, key_schema).exec(doc_list);
deepEqual(doc_list, [
{
'date': '2013-02-01',
'identifier': 'b'
},
{
'date': '2013-02-02',
'identifier': 'bb'
},
{
'date': '2013-02-03',
'identifier': 'bbb'
}
], 'Key Schema: date_month');
doc_list = docList();
complex_queries.QueryFactory.create({
type: 'simple',
key: 'date_year',
value: '2013-02-10'
}, key_schema).exec(doc_list);
deepEqual(doc_list.length, 6, 'Key Schema: date_year');
});
test('Key Schema with translation lookup', function () {
var doc_list, docList = function () {
return [
{'identifier': '1', 'state': 'open'},
{'identifier': '2', 'state': 'closed'}
];
};
doc_list = docList();
complex_queries.QueryFactory.create({
type: 'simple',
key: 'translated_state',
value: 'ouvert'
}, key_schema).exec(doc_list);
deepEqual(doc_list, [
{'identifier': '1', 'state': 'open'}
], 'Key Schema: It should be possible to look for a translated string');
doc_list = docList();
complex_queries.QueryFactory.create({
type: 'simple',
key: 'translated_state',
operator: '=',
value: 'ouvert'
}, key_schema).exec(doc_list);
deepEqual(doc_list, [
{'identifier': '1', 'state': 'open'}
], 'Key Schema: It should be possible to look for a translated string with operator =');
// doc_list = docList();
// complex_queries.QueryFactory.create({
// type: 'simple',
// key: 'translated_state',
// operator: '!=',
// value: 'ouvert'
// }).exec(doc_list);
// deepEqual(doc_list, [
// {'identifier': '2', 'state': 'closed'}
// ], 'Key Schema: It should be possible to look for a translated string with operator !=');
});
}));
...@@ -31,8 +31,8 @@ ...@@ -31,8 +31,8 @@
}, },
case_insensitive_identifier: { case_insensitive_identifier: {
readFrom: 'identifier', readFrom: 'identifier',
defaultMatch: function (to_compare, value, wildcard_character) { defaultMatch: function (object_value, value, wildcard_character) {
return (to_compare.toLowerCase() === value.toLowerCase()); return (object_value.toLowerCase() === value.toLowerCase());
} }
} }
}; };
...@@ -294,10 +294,58 @@ ...@@ -294,10 +294,58 @@
}); });
var intType = function (value) {
if (typeof value === 'string') {
return parseInt(value, 10);
}
return value;
};
test('Test overriding operators', function () {
var doc_list, docList = function () {
return [
{'identifier': '10', 'number': '10'},
{'identifier': '19', 'number': '19'},
{'identifier': '100', 'number': '100'}
];
};
doc_list = docList();
complex_queries.QueryFactory.create({
type: 'simple',
key: {
readFrom: 'number',
castTo: intType
},
operator: '>',
value: '19'
}).exec(doc_list);
deepEqual(doc_list, [
{'identifier': '100', 'number': '100'}
], 'Key Schema: Numbers are correctly compared (>) after casting');
doc_list = docList();
complex_queries.QueryFactory.create({
type: 'simple',
key: {
readFrom: 'number',
castTo: intType
},
operator: '<',
value: '19'
}).exec(doc_list);
deepEqual(doc_list, [
{'identifier': '10', 'number': '10'}
], 'Key Schema: Numbers are correctly compared (<) after casting');
});
var translationEqualityMatcher = function (data) { var translationEqualityMatcher = function (data) {
return function (to_compare, value) { return function (object_value, value) {
value = data[value]; value = data[value];
return (to_compare === value); return (object_value === value);
}; };
}; };
...@@ -329,25 +377,27 @@ ...@@ -329,25 +377,27 @@
], 'It should be possible to look for a translated string with a custom match function'); ], 'It should be possible to look for a translated string with a custom match function');
// doc_list = docList(); doc_list = docList();
// complex_queries.QueryFactory.create({ complex_queries.QueryFactory.create({
// type: 'simple', type: 'simple',
// key: keys.translated_state, key: keys.translated_state,
// value: 'ouvert' operator: '=',
// }).exec(doc_list); value: 'ouvert'
// deepEqual(doc_list, [ }).exec(doc_list);
// {'identifier': '1', 'state': 'open'}, deepEqual(doc_list, [
// ], 'It should be possible to look for a translated string with operator ='); {'identifier': '1', 'state': 'open'}
], 'It should be possible to look for a translated string with operator =');
// doc_list = docList(); // doc_list = docList();
// complex_queries.QueryFactory.create({ // complex_queries.QueryFactory.create({
// type: 'simple', // type: 'simple',
// key: keys.translated_state, // key: keys.translated_state,
// operator: '!=',
// value: 'ouvert' // value: 'ouvert'
// }).exec(doc_list); // }).exec(doc_list);
// deepEqual(doc_list, [ // deepEqual(doc_list, [
// {'identifier': '2', 'state': 'closed'}, // {'identifier': '2', 'state': 'closed'}
// ], 'It should be possible to look for a translated string with operator !='); // ], 'It should be possible to look for a translated string with operator !=');
......
/*jslint indent: 2, maxlen: 100, nomen: true */
/*global window, define, module, test_util, RSVP, jIO, local_storage, test, ok,
deepEqual, sinon, expect, stop, start, Blob, console */
// define([module_name], [dependencies], module);
(function (dependencies, module) {
"use strict";
if (typeof define === 'function' && define.amd) {
return define(dependencies, module);
}
module(RSVP, jIO, local_storage);
}([
'rsvp',
'jio',
'localstorage',
'qunit'
], function (RSVP, jIO, local_storage) {
"use strict";
module("LocalStorage");
local_storage.clear();
/**
* all(promises): Promise
*
* Produces a promise that is resolved when all the given promises are
* fulfilled. The resolved value is an array of each of the answers of the
* given promises.
*
* @param {Array} promises The promises to use
* @return {Promise} A new promise
*/
function all(promises) {
var results = [], i, count = 0;
function cancel() {
var j;
for (j = 0; j < promises.length; j += 1) {
if (typeof promises[j].cancel === 'function') {
promises[j].cancel();
}
}
}
return new RSVP.Promise(function (resolve, reject, notify) {
/*jslint unparam: true */
function succeed(j) {
return function (answer) {
results[j] = answer;
count += 1;
if (count !== promises.length) {
return;
}
resolve(results);
};
}
function notified(j) {
return function (answer) {
notify({
"promise": promises[j],
"index": j,
"notified": answer
});
};
}
for (i = 0; i < promises.length; i += 1) {
promises[i].then(succeed(i), succeed(i), notified(i));
}
}, cancel);
}
var dateType = function (obj) {
if (Object.prototype.toString.call(obj) === '[object Date]') {
// no need to clone
return obj;
}
return new Date(obj);
}, key_schema = {
mydate: {
readFrom: 'date',
castTo: dateType
}
};
test("AllDocs", function () {
expect(3);
var o = {}, jio = jIO.createJIO({
"type": "local",
"username": "ualldocs",
"application_name": "aalldocs",
"key_schema": key_schema
}, {
"workspace": {}
});
stop();
o.date_a = new Date(0);
o.date_b = new Date();
// put some document before listing them
all([
jio.put({
"_id": "a",
"title": "one",
"date": o.date_a
}).then(function () {
return jio.putAttachment({
"_id": "a",
"_attachment": "aa",
"_data": "aaa"
});
}),
jio.put({"_id": "b", "title": "two", "date": o.date_a}),
jio.put({"_id": "c", "title": "one", "date": o.date_b}),
jio.put({"_id": "d", "title": "two", "date": o.date_b})
]).then(function () {
// get a list of documents
return jio.allDocs();
}).always(function (answer) {
// sort answer rows for comparison
if (answer.data && answer.data.rows) {
answer.data.rows.sort(function (a, b) {
return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
});
}
deepEqual(answer, {
"data": {
"rows": [{
"id": "a",
"key": "a",
"value": {}
}, {
"id": "b",
"key": "b",
"value": {}
}, {
"id": "c",
"key": "c",
"value": {}
}, {
"id": "d",
"key": "d",
"value": {}
}],
"total_rows": 4
},
"method": "allDocs",
"result": "success",
"status": 200,
"statusText": "Ok"
}, "AllDocs");
}).then(function () {
// get a list of documents
return jio.allDocs({
"include_docs": true,
"sort_on": [['title', 'ascending'], ['date', 'descending']],
"select_list": ['title', 'date'],
"limit": [1] // ==> equal [1, 3] in this case
});
}).always(function (answer) {
deepEqual(answer, {
"data": {
"rows": [{
"doc": {
"_attachments": {
"aa": {
"content_type": "",
"digest": "sha256-9834876dcfb05cb167a5c24953eba58c4" +
"ac89b1adf57f28f2f9d09af107ee8f0",
"length": 3
}
},
"_id": "a",
"date": o.date_a.toJSON(),
"title": "one"
},
"id": "a",
"key": "a",
"value": {
"date": o.date_a.toJSON(),
"title": "one"
}
}, {
"doc": {
"_id": "d",
"date": o.date_b.toJSON(),
"title": "two"
},
"id": "d",
"key": "d",
"value": {
"date": o.date_b.toJSON(),
"title": "two"
}
}, {
"doc": {
"_id": "b",
"date": o.date_a.toJSON(),
"title": "two"
},
"id": "b",
"key": "b",
"value": {
"date": o.date_a.toJSON(),
"title": "two"
}
}],
"total_rows": 3
},
"method": "allDocs",
"result": "success",
"status": 200,
"statusText": "Ok"
}, "AllDocs include docs + sort on + limit + select_list");
}).then(function () {
// use a query
return jio.allDocs({'query': {
type: 'simple',
key: 'mydate',
operator: '=',
value: o.date_a.toString()
}});
}).always(function (answer) {
deepEqual(answer, {
"data": {
"rows": [{
"id": "a",
"key": "a",
"value": {}
}, {
"id": "b",
"key": "b",
"value": {}
}],
"total_rows": 2
},
"method": "allDocs",
"result": "success",
"status": 200,
"statusText": "Ok"
}, "AllDocs sort on + query");
}).always(start);
});
}));
...@@ -20,9 +20,11 @@ ...@@ -20,9 +20,11 @@
<script src="../complex_queries.js"></script> <script src="../complex_queries.js"></script>
<script src="queries/keys.tests.js"></script> <script src="queries/keys.tests.js"></script>
<script src="queries/key-schema.tests.js"></script>
<script src="queries/tests.js"></script> <script src="queries/tests.js"></script>
<script src="../src/jio.storage/localstorage.js"></script> <script src="../src/jio.storage/localstorage.js"></script>
<script src="queries/localstorage-keys.tests.js"></script>
<script src="jio.storage/localstorage.tests.js"></script> <script src="jio.storage/localstorage.tests.js"></script>
<script src="../src/jio.storage/davstorage.js"></script> <script src="../src/jio.storage/davstorage.js"></script>
......
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