Commit 4a6e56c2 authored by Tristan Cavelier's avatar Tristan Cavelier

Merge branch 'queries'

parents 7ca3f513 ffc30121
...@@ -11,6 +11,8 @@ COMPLEX_MIN = complex_queries.min.js ...@@ -11,6 +11,8 @@ COMPLEX_MIN = complex_queries.min.js
PARSER_PAR = $(QUERIES_DIR)/parser.par PARSER_PAR = $(QUERIES_DIR)/parser.par
PARSER_OUT = $(QUERIES_DIR)/parser.js PARSER_OUT = $(QUERIES_DIR)/parser.js
## install npm package system wide -> npm -g install <package>
## js/cc using rhino ## js/cc using rhino
#JSCC_CMD = rhino ~/modules/jscc/jscc.js -t ~/modules/jscc/driver_web.js_ #JSCC_CMD = rhino ~/modules/jscc/jscc.js -t ~/modules/jscc/driver_web.js_
# sh -c 'cd ; npm install jscc-node' # sh -c 'cd ; npm install jscc-node'
...@@ -19,6 +21,8 @@ JSCC_CMD = node ~/node_modules/jscc-node/jscc.js -t ~/node_modules/jscc-node/ ...@@ -19,6 +21,8 @@ JSCC_CMD = node ~/node_modules/jscc-node/jscc.js -t ~/node_modules/jscc-node/
LINT_CMD = $(shell which jslint || echo node ~/node_modules/jslint/bin/jslint.js) --terse LINT_CMD = $(shell which jslint || echo node ~/node_modules/jslint/bin/jslint.js) --terse
# sh -c 'cd ; npm install uglify-js' # sh -c 'cd ; npm install uglify-js'
UGLIFY_CMD = $(shell which uglifyjs || echo node ~/node_modules/uglify-js/bin/uglifyjs) UGLIFY_CMD = $(shell which uglifyjs || echo node ~/node_modules/uglify-js/bin/uglifyjs)
# sh -c 'cd ; npm install phantomjs'
PHANTOM_CMD = $(shell which phantomjs || echo ~/node_modules/phantomjs/bin/phantomjs)
auto: compile build lint auto: compile build lint
build: concat uglify build: concat uglify
...@@ -26,7 +30,7 @@ build: concat uglify ...@@ -26,7 +30,7 @@ build: concat uglify
# The order is important! # The order is important!
CONCAT_JIO_NAMES = intro exceptions jio.intro storages/* commands/* jobs/status/* jobs/job announcements/announcement activityUpdater announcements/announcer jobs/jobIdHandler jobs/jobManager jobs/jobRules jio.core jio.outro jioNamespace outro CONCAT_JIO_NAMES = intro exceptions jio.intro storages/* commands/* jobs/status/* jobs/job announcements/announcement activityUpdater announcements/announcer jobs/jobIdHandler jobs/jobManager jobs/jobRules jio.core jio.outro jioNamespace outro
CONCAT_STORAGE_NAMES = * CONCAT_STORAGE_NAMES = *
CONCAT_QUERIES_NAMES = begin parser-begin parser parser-end serializer query end CONCAT_QUERIES_NAMES = begin parser-begin parser parser-end tool queryfactory query simplequery complexquery end
LINT_NAMES = exceptions storages/* commands/* jobs/status/* jobs/* announcements/* activityUpdater jio.core jioNamespace LINT_NAMES = exceptions storages/* commands/* jobs/status/* jobs/* announcements/* activityUpdater jio.core jioNamespace
CONCAT_QUERIES_FILES = $(CONCAT_QUERIES_NAMES:%=$(QUERIES_DIR)/%.js) CONCAT_QUERIES_FILES = $(CONCAT_QUERIES_NAMES:%=$(QUERIES_DIR)/%.js)
...@@ -57,7 +61,7 @@ lint: ...@@ -57,7 +61,7 @@ lint:
$(LINT_CMD) $(LINT_FILES) $(LINT_CMD) $(LINT_FILES)
phantom: phantom:
~/node_modules/phantomjs/bin/phantomjs test/run-qunit.js test/jiotests_withoutrequirejs.html | awk 'BEGIN {print "<!DOCTYPE html><html>"} /^<head>$$/, /^<\/body>$$/ {print} END {print "</html>"}' | sed -e 's,^ *<\(/\|\)script.*>$$,,g' > test/unit_test_result.html $(PHANTOM_CMD) test/run-qunit.js test/jiotests_withoutrequirejs.html | awk 'BEGIN {print "<!DOCTYPE html><html>"} /^<head>$$/, /^<\/body>$$/ {print} END {print "</html>"}' | sed -e 's,^ *<\(/\|\)script.*>$$,,g' > test/unit_test_result.html
grep '^ <title>✔ ' test/unit_test_result.html > /dev/null grep '^ <title>✔ ' test/unit_test_result.html > /dev/null
.phony: clean .phony: clean
......
...@@ -16,11 +16,11 @@ ...@@ -16,11 +16,11 @@
<table> <table>
<tr> <tr>
<td>Query (String):<br /><textarea id="str">1:abc AND 2:def</textarea></td> <td>Query (String):<br /><textarea id="str">1:abc AND 2:def</textarea></td>
<td>Query (Object):<br /><textarea id="obj">{&quot;type&quot;:&quot;complex&quot;,&quot;operator&quot;:&quot;AND&quot;,&quot;query_list&quot;:[{&quot;type&quot;:&quot;simple&quot;,&quot;operator&quot;:&quot;=&quot;,&quot;id&quot;:&quot;1&quot;,&quot;value&quot;:&quot;abc&quot;},{&quot;type&quot;:&quot;simple&quot;,&quot;operator&quot;:&quot;=&quot;,&quot;id&quot;:&quot;2&quot;,&quot;value&quot;:&quot;def&quot;}]}</textarea></td> <td>Query (Object):<br /><textarea id="obj">{&quot;type&quot;:&quot;complex&quot;,&quot;operator&quot;:&quot;AND&quot;,&quot;query_list&quot;:[{&quot;type&quot;:&quot;simple&quot;,&quot;operator&quot;:&quot;=&quot;,&quot;key&quot;:&quot;1&quot;,&quot;value&quot;:&quot;abc&quot;},{&quot;type&quot;:&quot;simple&quot;,&quot;operator&quot;:&quot;=&quot;,&quot;key&quot;:&quot;2&quot;,&quot;value&quot;:&quot;def&quot;}]}</textarea></td>
</tr> </tr>
<tr> <tr>
<td>Object List:<br /><textarea id="list">[{&quot;1&quot;:&quot;abc&quot;,&quot;2&quot;:&quot;def&quot;},{&quot;1&quot;:&quot;def&quot;,&quot;2&quot;:&quot;abc&quot;}]</textarea></td> <td>Item list (to filter, from 'Query (Object)'):<br /><textarea id="list">[{&quot;1&quot;:&quot;abc&quot;,&quot;2&quot;:&quot;def&quot;},{&quot;1&quot;:&quot;def&quot;,&quot;2&quot;:&quot;abc&quot;}]</textarea></td>
<td>Result (Query String):<br /><textarea id="result">[{&quot;1&quot;:&quot;abc&quot;,&quot;2&quot;:&quot;def&quot;}]</textarea></td> <td>Result list:<br /><textarea id="result">[{&quot;1&quot;:&quot;abc&quot;,&quot;2&quot;:&quot;def&quot;}]</textarea></td>
</tr> </tr>
<tr> <tr>
<td><label for="wildcard">Wildcard char: </label></td> <td><label for="wildcard">Wildcard char: </label></td>
...@@ -39,38 +39,36 @@ ...@@ -39,38 +39,36 @@
<td><input type="text" id="limit" name="limit" value="[0,100]" /></td> <td><input type="text" id="limit" name="limit" value="[0,100]" /></td>
</tr> </tr>
</table> </table>
<button onclick="parse()">Parse</button> <button onclick="searchTextToJson()">Search text to JSON</button>
<button onclick="serialize()">Serialize</button> <button onclick="jsonToSearchText()">JSON to Search text</button>
<button onclick="query()">Query</button> <button onclick="query()">Query</button>
<script type="text/javascript" src="../lib/md5/md5.js"></script> <script type="text/javascript" src="../lib/md5/md5.js"></script>
<script type="text/javascript" src="../lib/jsSha2/sha2.js"></script> <script type="text/javascript" src="../lib/jsSha2/sha2.js"></script>
<script type="text/javascript" src="../lib/sjcl/sjcl.min.js"></script> <script type="text/javascript" src="../lib/sjcl/sjcl.min.js"></script>
<script type="text/javascript" src="../jio.min.js"></script> <script type="text/javascript" src="../jio.min.js"></script>
<script type="text/javascript" src="../complex_queries.min.js"></script> <script type="text/javascript" src="../complex_queries.min.js"></script>
<script type="text/javascript" <script type="text/javascript"
src="http://code.jquery.com/jquery-1.8.2.min.js"></script> src="http://code.jquery.com/jquery-1.8.2.min.js"></script>
<script type="text/javascript"> <script type="text/javascript">
<!-- <!--
var parse = function () { var searchTextToJson = function () {
$('#obj').attr('value',JSON.stringify(jIO.ComplexQueries.parse($('#str').attr('value')))); $("#obj").attr("value", JSON.stringify(complex_queries.Query.parseStringToObject($("#str").attr("value"))));
}; };
var serialize = function () { var jsonToSearchText = function () {
$('#str').attr('value',jIO.ComplexQueries.serialize(JSON.parse($('#obj').attr('value')))); $("#str").attr("value", complex_queries.QueryFactory.create(JSON.parse($("#obj").attr("value"))).toString());
}; };
var query = function () { var query = function () {
$('#result').attr('value',JSON.stringify( var list = JSON.parse($("#list").attr("value"));
jIO.ComplexQueries.query( $("#str").attr("value", complex_queries.QueryFactory.create(JSON.parse($("#obj").attr("value"))).exec(
{ list,
query:$('#str').attr('value'), {
filter:{ "wildcard_character": $('#wildcard').attr('value'),
sort_on:JSON.parse($('#sort_on').attr('value')), "sort_on": JSON.parse($("#sort_on").attr("value")),
limit:JSON.parse($('#limit').attr('value')), "limit": JSON.parse($("#limit").attr("value")),
select_list:JSON.parse($('#select_list').attr('value')) "select_list": JSON.parse($("#select_list").attr("value"))
}, }
wildcard_character:$('#wildcard').attr('value') ));
}, JSON.parse($('#list').attr('value')) $("#result").attr("value", JSON.stringify(list));
)
));
}; };
// --> // -->
</script> </script>
......
...@@ -92,8 +92,6 @@ var clearlog = function () { ...@@ -92,8 +92,6 @@ var clearlog = function () {
</td> </td>
<td colspan="1" style="text-align: center;"> <td colspan="1" style="text-align: center;">
Options:<br /> Options:<br />
<label for="include_docs">Include Docs</label>
<input type="checkbox" id="include_docs" /><br />
<label for="show_conflicts">Get Conflicts</label> <label for="show_conflicts">Get Conflicts</label>
<input type="checkbox" id="show_conflicts" /><br /> <input type="checkbox" id="show_conflicts" /><br />
<label for="show_revision_history">Get Revision History</label> <label for="show_revision_history">Get Revision History</label>
...@@ -101,7 +99,7 @@ var clearlog = function () { ...@@ -101,7 +99,7 @@ var clearlog = function () {
<label for="show_revision_info">Get Revision Info</label> <label for="show_revision_info">Get Revision Info</label>
<input type="checkbox" id="show_revision_info" /><br /> <input type="checkbox" id="show_revision_info" /><br />
<label for="max_retry">Max Retry</label> <label for="max_retry">Max Retry</label>
<input type="number" id="max_retry" value="0" style="width:30px;"/> <input type="number" id="max_retry" value="0" style="width: 3em;"/>
(0 = infinite) (0 = infinite)
</td> </td>
</tr> </tr>
...@@ -111,14 +109,36 @@ var clearlog = function () { ...@@ -111,14 +109,36 @@ var clearlog = function () {
<button onclick="put()">put</button> <button onclick="put()">put</button>
<button onclick="get()">get</button> <button onclick="get()">get</button>
<button onclick="remove()">remove</button> <button onclick="remove()">remove</button>
<button onclick="allDocs()">allDocs</button>
<!-- </td> -->
<!-- <td style="text-align: center;"> -->
- <button onclick="putAttachment()">putAttachment</button> - <button onclick="putAttachment()">putAttachment</button>
<button onclick="getAttachment()">getAttachment</button> <button onclick="getAttachment()">getAttachment</button>
<button onclick="removeAttachment()">removeAttachment</button> <button onclick="removeAttachment()">removeAttachment</button>
</td> </td>
</tr> </tr>
<tr>
<td colspan="1" style="width: 50%;">
<label for="query">AllDocs Query:</label>
<textarea id="query" rows="3" style="width: 98%;">a: 2</textarea>
</td>
<td colspan="1" style="text-align: center;">
AllDocs Options:<br />
<label for="include_docs">Include Docs</label>
<input type="checkbox" id="include_docs" /><br />
<label for="wildcard">Wildcard char: </label>
<input type="text" id="wildcard" name="wildcard" value="%" maxlength="1" style="width: 1em;"/><br />
<label for="sort_on">Sort on: </label>
<input type="text" id="sort_on" name="sort_on" value="[[&quot;author&quot;, &quot;ascending&quot;], [&quot;title&quot;, &quot;descending&quot;]]"
style="width: 80%;"/><br />
<label for="select_list">Select_list: </label>
<input type="text" id="select_list" name="select_list" value="[&quot;author&quot;, &quot;title&quot;]" style="width: 80%;"/><br />
<label for="limit">Limit: </label>
<input type="text" id="limit" name="limit" value="[0, 100]" style="width: 80%;"/><br />
</td>
</tr>
<tr>
<td colspan="2" style="text-align: center;">
<button onclick="allDocs()">allDocs</button>
</td>
</tr>
</table> </table>
<br /> <br />
<div style="text-align: center;"> <div style="text-align: center;">
...@@ -130,6 +150,7 @@ var clearlog = function () { ...@@ -130,6 +150,7 @@ var clearlog = function () {
<div id="log"> <div id="log">
</div> </div>
<script type="text/javascript" src="../lib/md5/md5.js"></script> <script type="text/javascript" src="../lib/md5/md5.js"></script>
<script type="text/javascript" src="../complex_queries.js"></script>
<script type="text/javascript" src="../jio.js"></script> <script type="text/javascript" src="../jio.js"></script>
<script type="text/javascript" src="../src/jio.storage/localstorage.js"> <script type="text/javascript" src="../src/jio.storage/localstorage.js">
</script> </script>
...@@ -223,7 +244,13 @@ var command = function (method) { ...@@ -223,7 +244,13 @@ var command = function (method) {
return error('no jio set'); return error('no jio set');
} }
opts.query = $('#query').attr('value');
opts.include_docs = $('#include_docs').attr('checked') ? true : false; opts.include_docs = $('#include_docs').attr('checked') ? true : false;
opts.wildcard_character = $('#wildcard').attr('value') || "";
opts.sort_on = JSON.parse($('#sort_on').attr('value') || null);
opts.select_list = JSON.parse($('#select_list').attr('value') || null);
opts.limit = JSON.parse($('#limit').attr('value') || null);
opts.conflicts = $('#show_conflicts').attr('checked') ? true : false; opts.conflicts = $('#show_conflicts').attr('checked') ? true : false;
opts.revs = $('#show_revision_history').attr('checked') ? true : false; opts.revs = $('#show_revision_history').attr('checked') ? true : false;
opts.revs_info = $('#show_revision_info').attr('checked') ? true : false; opts.revs_info = $('#show_revision_info').attr('checked') ? true : false;
......
JSCCDIR = /opt/jscc
JSCCCMD = rhino $(JSCCDIR)/jscc.js -t $(JSCCDIR)/driver_web.js_
SRC_DIR = ../../src/queries
BUILD_DIR = ../../built/queries
OUT = $(BUILD_DIR)/complex-queries.js
PARSER_PAR = $(SRC_DIR)/parser.par
PARSER_OUT = $(BUILD_DIR)/parser.js
auto: prepare parser grunt
prepare:
mkdir -p $(BUILD_DIR)
parser:
$(JSCCCMD) $(PARSER_PAR) >> $(PARSER_OUT)
grunt:
grunt
/*global module:false*/
module.exports = function(grunt) {
// Project configuration.
grunt.initConfig({
pkg: '<json:package.json>',
meta: {
banner: '/*! <%= pkg.title || pkg.name %> - v<%= pkg.version %> - '+
'<%= grunt.template.today("yyyy-mm-dd") %>\n' +
'<%= pkg.homepage ? "* " + pkg.homepage + "\n" : "" %>' +
'* Copyright (c) <%= grunt.template.today("yyyy") %> Nexedi;' +
' Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %> */'
},
concat: {
dist: {
src: ['<banner:meta.banner>',
// Wrapper top
'<file_strip_banner:../../src/queries/begin.js>',
// code
'<file_strip_banner:../../src/queries/parser-begin.js>',
'<file_strip_banner:../../built/queries/parser.js>',
'<file_strip_banner:../../src/queries/parser-end.js>',
'<file_strip_banner:../../src/queries/serializer.js>',
'<file_strip_banner:../../src/queries/query.js>',
// Wrapper bottom
'<file_strip_banner:../../src/queries/end.js>'],
dest: '../../<%= pkg.name %>.js'
}
},
min: {
dist: {
src: ['<banner:meta.banner>', '<config:concat.dist.dest>'],
dest: '../../<%= pkg.name %>.min.js'
}
},
qunit: {
files: ['../../test/cq-tests.html']
},
lint: {
files: ['grunt.js',
'../../src/queries/serializer.js',
'../../src/queries/query.js']
},
watch: {
files: '<config:lint.files>',
tasks: 'lint qunit'
},
jshint: {
options: {
curly: true,
eqeqeq: true,
immed: true,
latedef: true,
newcap: true,
noarg: true,
sub: true,
undef: true,
boss: true,
eqnull: true,
browser: true
},
globals: {
scope: true,
console: true,
unescape: true,
// Needed to avoid "not defined error" with requireJs
define: true,
require: true,
// Needed to avoid "not defined error" with sinonJs
sinon: true,
module: true,
test: true,
ok: true,
deepEqual: true,
expect: true,
stop: true,
start: true,
equal: true
}
},
uglify: {}
});
// Default task.
grunt.registerTask('default', 'lint concat min');
};
{
"name": "complex-queries",
"title": "Complex Queries",
"description": "Complex Queries",
"version": "0.1.0",
"homepage": "",
"author": {
"name": "Tristan Cavelier",
"email": "tristan.cavelier@tiolive.com"
},
"repository": {
"type": "git",
"url": "http://git.erp5.org/repos/jio.git"
},
"bugs": {
"url": ""
},
"licenses": [
],
"dependencies": {
},
"keywords": []
}
...@@ -301,9 +301,16 @@ jIO.addStorageType("erp5", function (spec, my) { ...@@ -301,9 +301,16 @@ jIO.addStorageType("erp5", function (spec, my) {
* @param {string} method The ERP5 request method * @param {string} method The ERP5 request method
*/ */
priv.genericCommand = function (command, method) { priv.genericCommand = function (command, method) {
var option = command.cloneOption();
if (complex_queries !== undefined &&
method === 'allDocs' &&
option.query) {
option.query =
complex_queries.QueryFactory.create(option.query || "").serialized();
}
erp5.genericRequest( erp5.genericRequest(
command.cloneDoc(), command.cloneDoc(),
command.cloneOption(), option,
method method
).always(function (err, response) { ).always(function (err, response) {
if (err) { if (err) {
......
/* /*
* Copyright 2013, Nexedi SA * Copyright 2013, Nexedi SA
* Released under the LGPL license. * Released under the LGPL license.
* http://www.gnu.org/licenses/lgpl.html * http://www.gnu.org/licenses/lgpl.html
*/ */
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global jIO: true, localStorage: true, setTimeout: true */ /*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true, regexp: true */
/*global jIO: true, localStorage: true, define: true, complex_queries: true */
/** /**
* JIO Index Storage. * JIO Index Storage.
* Manages indexes for specified storages. * Manages indexes for specified storages.
* Description: * Description:
* { * {
* "type": "index", * "type": "index",
* "indices": [ * "indices": [{
* {"indexA",["field_A"]}, * "id": "index_title_subject.json", // doc id where to store indices
* {"indexAB",["field_A","field_B"]} * "index": ["title", "subject"] // metadata to index
* ], * "sub_storage": <sub storage where to store index>
* "field_types": { * (default equal to parent sub_storage field)
* "field_A": "dateTime", * }, {
* "field_B": "string" * "id": "index_year.json",
* }, * "index": "year"
* "storage": [ * ...
* <sub storage description>, * }],
* ... * "sub_storage": <sub storage description>
* ]
* } * }
* Index file will contain *
* Sent document metadata will be:
* index_titre_subject.json
* { * {
* "_id": "app-name_indices.json", * "_id": "index_title_subject.json",
* "indexA": * "indexing": ["title", "subject"],
* "fieldA": { * "free": [0],
* "keyword_abc": ["some_id","some_other_id",...] * "location": {
* } * "foo": 1,
* "bar": 2,
* ...
* }, * },
* "indexAB": { * "database": [
* "fieldA": { * {},
* "keyword_abc": ["some_id"] * {"_id": "foo", "title": "...", "subject": ...},
* }, * {"_id": "bar", "title": "...", "subject": ...},
* "fieldB": { * ...
* "keyword_def": ["some_id"] * ]
* }
* }
* } * }
* NOTES: *
* It may be difficult to "un-sort" multi-field indices, like * index_year.json
* indexAB, because all keywords will be listed regrardless * {
* of underlying field, so an index on author and year would produce * "_id": "index_year.json",
* two entries per record like: * "indexing": ["year"],
* * "free": [1],
* "William Shakespeare":["id_Romeo_and_Juliet", "id_Othello"], * "location": {
* "1591":["id_Romeo_and_Juliet"], * "foo": 0,
* "1603":["id_Othello"] * "bar": 2,
* * ...
* So for direct lookups, this should be convient, but for other types * },
* of queries, it depends * "database": [
* {"_id": "foo", "year": "..."},
* {},
* {"_id": "bar", "year": "..."},
* ...
* ]
* }
*
* A put document will be indexed to the free location if exist, else it will be
* indexed at the end of the database. The document id will be indexed, also, in
* 'location' to quickly replace metadata.
*
* Only one or two loops are executed:
* - one to filter retrieved document list (no query -> no loop)
* - one to format the result to a JIO response
*/ */
jIO.addStorageType('indexed', function (spec, my) { (function () {
"use strict"; "use strict";
var that, priv = {};
spec = spec || {}; var error_dict = {
that = my.basicStorage(spec, my); "Corrupted Index": {
"status": 24,
priv.indices = spec.indices; "statusText": "Corrupt",
priv.field_types = spec.field_types; "error": "corrupt",
priv.substorage_key = "sub_storage"; "reason": "corrupted index database"
priv.substorage = spec[priv.substorage_key]; },
priv.index_indicator = spec.sub_storage.application_name || "index"; "Corrupted Metadata": {
priv.index_suffix = priv.index_indicator + "_indices.json"; "status": 24,
"statusText": "Corrupt",
"error": "corrupt",
"reason": "corrupted document"
},
"Not Found": {
"status": 404,
"statusText": "Not Found",
"error": "not_found",
"reason": "missing document"
},
"Conflict": {
"status": 409,
"statusText": "Conflicts",
"error": "conflicts",
"reason": "already exist"
},
"Different Index": {
"status": 40,
"statusText": "Check failed",
"error": "check_failed",
"reason": "incomplete database"
}
};
my.env = my.env || spec.env || {}; /**
* Generate a JIO Error Object
*
* @method generateErrorObject
* @param {String} name The error name
* @param {String} message The error message
* @param {String} [reason] The error reason
* @return {Object} A jIO error object
*/
function generateErrorObject(name, message, reason) {
if (!error_dict[name]) {
return {
"status": 0,
"statusText": "Unknown",
"error": "unknown",
"message": message,
"reason": reason || "unknown"
};
}
return {
"status": error_dict[name].status,
"statusText": error_dict[name].statusText,
"error": error_dict[name].error,
"message": message,
"reason": reason || error_dict[name].reason
};
}
that.specToStore = function () { /**
var o = {}; * Get the real type of an object
o[priv.substorage_key] = priv.substorage; * @method type
o.env = my.env; * @param {Any} value The value to check
return o; * @return {String} The value type
}; */
function type(value) {
// returns "String", "Object", "Array", "RegExp", ...
return (/^\[object ([a-zA-Z]+)\]$/).exec(
Object.prototype.toString.call(value)
)[1];
}
/** /**
* Generate a new uuid * Generate a new uuid
* @method generateUuid * @method generateUuid
* @return {string} The new uuid * @return {string} The new uuid
*/ */
priv.generateUuid = function () { function generateUuid() {
var S4 = function () { var S4 = function () {
var i, string = Math.floor( var i, string = Math.floor(
Math.random() * 0x10000 /* 65536 */ Math.random() * 0x10000 /* 65536 */
...@@ -98,863 +168,687 @@ jIO.addStorageType('indexed', function (spec, my) { ...@@ -98,863 +168,687 @@ jIO.addStorageType('indexed', function (spec, my) {
S4() + "-" + S4() + "-" +
S4() + "-" + S4() + "-" +
S4() + S4() + S4(); S4() + S4() + S4();
}; }
/**
* Get number of elements in object
* @method getObjectSize
* @param {object} obj The object to check
* @return {number} size The amount of elements in the object
*/
priv.getObjectSize = function (obj) {
var size = 0, key;
for (key in obj) {
if (obj.hasOwnProperty(key)) {
size += 1;
}
}
return size;
};
/** /**
* Creates an empty indices array * A JSON Index manipulator
* @method createEmptyIndexArray *
* @param {array} indices An array of indices (optional) * @class JSONIndex
* @return {object} The new index array * @constructor
*/ */
priv.createEmptyIndexArray = function (indices) { function JSONIndex(spec) {
var i, k, j = priv.indices.length, new_index, var that = this;
new_index_object = {}, new_index_name, new_index_fields; spec = spec || {};
if (indices === undefined) { /**
for (i = 0; i < j; i += 1) { * The document id
new_index = priv.indices[i]; *
new_index_name = new_index.name; * @property _id
new_index_fields = new_index.fields; * @type String
new_index_object[new_index_name] = {}; */
that._id = spec._id;
// loop index fields and add objects to hold value/id pairs
for (k = 0; k < new_index_fields.length; k += 1) { /**
new_index_object[new_index_name][new_index_fields[k]] = {}; * The array with metadata key to index
} *
* @property _indexing
* @type Array
*/
that._indexing = spec.indexing || [];
/**
* The array of free location index
*
* @property _free
* @type Array
* @default []
*/
that._free = spec.free || [];
/**
* The dictionnary document id -> database index
*
* @property _location
* @type Object
* @default {}
*/
that._location = spec.location || {};
/**
* The database array containing document metadata
*
* @property _database
* @type Array
* @default []
*/
that._database = spec.database || [];
/**
* Adds a metadata object in the database, replace if already exist
*
* @method put
* @param {Object} meta The metadata to add
* @return {Boolean} true if added, false otherwise
*/
that.put = function (meta) {
var underscored_meta_re = /^_.*$/, k, needed_meta = {}, ok = false;
if (typeof meta._id !== "string" && meta._id !== "") {
throw new TypeError("Corrupted Metadata");
} }
} for (k in meta) {
return new_index_object; if (meta.hasOwnProperty(k)) {
}; if (underscored_meta_re.test(k)) {
if (k === "_id") {
/** needed_meta[k] = meta[k];
* Determine if a key/value pair exists in an object by VALUE }
* @method searchObjectByValue } else if (that._indexing_object[k]) {
* @param {object} indexToSearch The index to search needed_meta[k] = meta[k];
* @param {string} docid The document id to find ok = true;
* @param {string} passback The value that should be returned
* @return {boolean} true/false
*/
priv.searchIndexByValue = function (indexToSearch, docid, passback) {
var key, obj, prop;
for (key in indexToSearch) {
if (indexToSearch.hasOwnProperty(key)) {
obj = indexToSearch[key];
for (prop in obj) {
if (obj[prop] === docid) {
return passback === "bool" ? true : key;
} }
} }
} }
} if (ok) {
return false; if (typeof that._location[meta._id] === "number") {
}; that._database[that._location[meta._id]] = needed_meta;
} else if (that._free.length > 0) {
/** k = that._free.shift();
* Get element position in array that._database[k] = needed_meta;
* @method getPositionInArray that._location[meta._id] = k;
* @param {object} indices The index file } else {
* @param {object} indices The index file that._database.push(needed_meta);
* @returns {number} i Position of element in array that._location[meta._id] = that._database.length - 1;
*/ }
priv.getPositionInArray = function (element, array) { return true;
var i, l = array.length;
for (i = 0; i < l; i += 1) {
if (array[i] === element) {
return i;
} }
} if (typeof that._location[meta._id] === "number") {
return null; that.remove(meta);
}; }
return false;
};
/** /**
* Find id in indices * Removes a metadata object from the database if exist
* @method isDocidInIndex *
* @param {object} indices The file containing the indeces * @method remove
* @param {object} doc The document which should be added to the index * @param {Object} meta The metadata to remove
* @return {boolean} true/false */
*/ that.remove = function (meta) {
priv.isDocidInIndex = function (indices, doc) { if (typeof meta._id !== "string") {
var index, i, j, label, l = priv.indices.length; throw new TypeError("Corrupted Metadata");
// loop indices
for (i = 0; i < l; i += 1) {
index = {};
index.reference = priv.indices[i];
index.reference_size = index.reference.fields.length;
index.current = indices[index.reference.name];
for (j = 0; j < index.reference_size; j += 1) {
label = index.reference.fields[j];
index.current_size = priv.getObjectSize(index.current[label]);
// check for existing entries to remove (put-update)
if (index.current_size > 0) {
if (priv.searchIndexByValue(index.current[label], doc._id, "bool")) {
return true;
}
}
} }
} if (typeof that._location[meta._id] !== "number") {
return false; throw new ReferenceError("Not Found");
}; }
that._database[that._location[meta._id]] = null;
that._free.push(that._location[meta._id]);
delete that._location[meta._id];
};
/** /**
* Clean up indexes when removing a file * Checks if the index database document is correct
* @method cleanIndices *
* @param {object} indices The file containing the indeces * @method check
* @param {object} doc The document which should be added to the index */
* @return {object} indices The cleaned up file that.check = function () {
*/ var id, database_meta;
priv.cleanIndices = function (indices, doc) { if (typeof that._id !== "string" ||
var i, j, k, index, key, label, l = priv.indices.length; that._id === "" ||
type(that._free) !== "Array" ||
// loop indices (indexA, indexAB...) type(that._indexing) !== "Array" ||
for (i = 0; i < l; i += 1) { type(that._location) !== "Object" ||
index = {}; type(that._database) !== "Array" ||
index.reference = priv.indices[i]; that._indexing.length === 0) {
index.reference_size = index.reference.fields.length; throw new TypeError("Corrupted Index");
index.current = indices[index.reference.name];
// loop index fields
for (j = 0; j < index.reference_size; j += 1) {
label = index.reference.fields[j];
index.current_size = priv.getObjectSize(index.current[label]);
// loop field entries
for (k = 0; k < index.current_size; k += 1) {
key = priv.searchIndexByValue(index.current[label], doc._id, "key");
index.result_array = index.current[label][key];
if (!!key) {
// if there is more than one docid in the result array,
// just remove this one and not the whole array
if (index.result_array.length > 1) {
index.result_array.splice(k, 1);
} else {
delete index.current[label][key];
}
}
}
} }
} for (id in that._location) {
return indices; if (that._location.hasOwnProperty(id)) {
}; database_meta = that._database[that._location[id]];
/** if (type(database_meta) !== "Object" ||
* Adds entries to indices database_meta._id !== id) {
* @method createEmptyIndexArray throw new TypeError("Corrupted Index");
* @param {object} indices The file containing the indeces
* @param {object} doc The document which should be added to the index
*/
priv.updateIndices = function (indices, doc) {
var i, j, index, value, label, key, l = priv.indices.length;
// loop indices
for (i = 0; i < l; i += 1) {
index = {};
index.reference = priv.indices[i];
index.reference_size = index.reference.fields.length;
index.current = indices[index.reference.name];
// build array of values to create entries in index
for (j = 0; j < index.reference_size; j += 1) {
label = index.reference.fields[j];
value = doc[label];
if (value !== undefined) {
index.current_size = priv.getObjectSize(index.current[label]);
// check for existing entries to remove (put-update)
if (index.current_size > 0) {
key = priv.searchIndexByValue(
index.current[label],
doc._id,
"key"
);
if (!!key) {
delete index.current[label][key];
}
}
if (index.current[label][value] === undefined) {
index.current[label][value] = [];
} }
// add a new entry
index.current[label][value].push(doc._id);
} }
} }
} };
return indices;
};
/**
* Check available indices to find the best one.
* TODOS: NOT NICE, redo
* @method findBestIndexForQuery
* @param {object} syntax of query
* @returns {object} response The query object constructed from Index file
*/
priv.findBestIndexForQuery = function (syntax) {
var i, j, k, l, n, p, o, element, key, block,
search_ids, use_index = [], select_ids = {}, index, query_param,
current_query, current_query_size;
// try to parse into object
if (syntax.query !== undefined) {
current_query = jIO.ComplexQueries.parse(syntax.query);
} else {
current_query = {};
current_query_size = 0;
}
// loop indices that.equals = function (json_index) {
for (i = 0; i < priv.indices.length; i += 1) { function equalsDirection(a, b) {
search_ids = []; var k;
block = false; for (k in a._location) {
index = {}; if (a._location.hasOwnProperty(k)) {
index.reference = priv.indices[i]; if (b._location[k] === undefined ||
index.reference_size = index.reference.fields.length; JSON.stringify(b._database[b._location[k]]) !==
JSON.stringify(a._database[a._location[k]])) {
if (current_query_size !== 0) { return false;
// rebuild search_ids for iteration
if (current_query.query_list === undefined) {
search_ids.push(current_query.id);
} else {
for (j = 0; j < current_query.query_list.length; j += 1) {
if (priv.getPositionInArray(current_query.query_list[j].id,
search_ids) === null) {
search_ids.push(current_query.query_list[j].id);
} }
} }
} }
return true;
}
if (!equalsDirection(that, json_index)) {
return false;
}
if (!equalsDirection(json_index, that)) {
return false;
}
return true;
};
// loop search ids and find matches in index that.checkDocument = function (doc) {
for (k = 0; k < search_ids.length; k += 1) { var i, key, db_doc;
query_param = search_ids[0]; if (typeof that._location[doc._id] !== "number" ||
for (l = 0; l < index.reference_size; l += 1) { (db_doc = that._database(that._location[doc._id])._id) !== doc._id) {
if (query_param === index.reference.fields[l]) { throw new TypeError("Different Index");
search_ids.splice( }
priv.getPositionInArray(query_param, search_ids), for (i = 0; i < that._indexing.length; i += 1) {
1 key = that._indexing[i];
); if (doc[key] !== db_doc[key]) {
} throw new TypeError("Different Index");
}
} }
} }
};
// rebuild select_ids /**
for (o = 0; o < syntax.filter.select_list.length; o += 1) { * Recreates database indices and remove free space
element = syntax.filter.select_list[o]; *
select_ids[element] = true; * @method repair
*/
that.repair = function () {
var i = 0, meta;
that._free = [];
that._location = {};
if (type(that._database) !== "Array") {
that._database = [];
} }
while (i < that._database.length) {
// search_ids empty = all needed search fields found on index meta = that._database[i];
if (search_ids.length === 0) { if (type(meta) === "Object" &&
p = priv.getObjectSize(select_ids); typeof meta._id === "string" && meta._id !== "" &&
if (p === 0) { !that._location[meta._id]) {
use_index.push({ that._location[meta._id] = i;
"name": index.reference.name, i += 1;
"search": true,
"results": false
});
} else { } else {
for (n = 0; n < index.reference_size; n += 1) { that._database.splice(i, 1);
delete select_ids[index.reference.fields[n]];
}
for (key in select_ids) {
if (select_ids.hasOwnProperty(key)) {
use_index.push({
"name": index.reference.name,
"search": true,
"results": false
});
block = true;
}
}
if (block === false) {
use_index.push({
"name": index.reference.name,
"search": true,
"results": true
});
}
} }
} }
} };
return use_index;
}; /**
* Returns the serialized version of this object (not cloned)
*
* @method serialized
* @return {Object} The serialized version
*/
that.serialized = function () {
return {
"_id": that._id,
"indexing": that._indexing,
"free": that._free,
"location": that._location,
"database": that._database
};
};
that.check();
that._indexing_object = {};
that._indexing.forEach(function (meta_key) {
that._indexing_object[meta_key] = true;
});
}
/** /**
* Converts the indices file into an object usable by complex queries * The JIO index storage constructor
* @method constructQueryObject
* @param {object} indices The index file
* @returns {object} response The query object constructed from Index file
*/
priv.constructQueryObject = function (indices, query_syntax) {
var j, k, l, m, n, use_index, index,
index_name, field_names, field, key, element,
query_index, query_object = [], field_name,
entry;
// returns index-to-use|can-do-query|can-do-query-and-results
use_index = priv.findBestIndexForQuery(query_syntax);
if (use_index.length > 0) {
for (j = 0; j < use_index.length; j += 1) {
index = use_index[j];
// NOTED: the index could be used to:
// (a) get all document ids matching query
// (b) get all document ids and results (= run complex query on index)
// right now, only (b) is supported, because the complex query is
// a single step process. If it was possible to first get the
// relevant document ids, then get the results, the index could be
// used to do the first step plus use GET on the returned documents
if (index.search && index.results) {
index_name = use_index[j].name;
query_index = indices[index_name];
// get fieldnames from this index
for (k = 0; k < priv.indices.length; k += 1) {
if (priv.indices[k].name === use_index[j].name) {
field_names = priv.indices[k].fields;
}
}
for (l = 0; l < field_names.length; l += 1) {
field_name = field_names[l];
// loop entries for this field name
field = query_index[field_name];
for (key in field) {
if (field.hasOwnProperty(key)) {
element = field[key];
// key can be "string" or "number" right now
if (priv.field_types[field_name] === "number") {
key = +key;
}
for (m = 0; m < element.length; m += 1) {
if (priv.searchIndexByValue(
query_object,
element[m],
"bool"
)) {
// loop object
for (n = 0; n < query_object.length; n += 1) {
entry = query_object[n];
if (entry.id === element[m]) {
entry[field_name] = key;
}
}
} else {
entry = {};
entry.id = element[m];
entry[field_name] = key;
query_object.push(entry);
}
}
}
}
}
}
}
}
return query_object;
};
/**
* Build the alldocs response from the index file (overriding substorage)
* @method allDocsResponseFromIndex
* @param {object} command The JIO command
* @param {boolean} include_docs Whether to also supply the document
* @param {object} option The options set for this method
* @returns {object} response The allDocs response
*/ */
priv.allDocsResponseFromIndex = function (indices, include_docs, option) { function indexStorage(spec, my) {
var i, j, k, m, n = 0, l = priv.indices.length, var that, priv = {};
index, key, obj, prop, found, file, label,
unique_count = 0, unique_docids = [], all_doc_response = {}, that = my.basicStorage(spec, my);
success = function (content) {
file = { value: {} }; priv.indices = spec.indices;
file.id = unique_docids[n]; priv.sub_storage = spec.sub_storage;
file.key = unique_docids[n];
file.doc = content; // Overrides
all_doc_response.rows.push(file);
// async counter, must be in callback that.specToStore = function () {
n += 1; return {
if (n === unique_count) { "indices": priv.indices,
that.success(all_doc_response); "sub_storage": priv.sub_storage
}
},
error = function () {
that.error({
"status": 404,
"statusText": "Not Found",
"error": "not_found",
"message": "Cannot find the document",
"reason": "Cannot get a document from substorage"
});
return;
}; };
};
// loop indices /**
for (i = 0; i < l; i += 1) { * Return the similarity percentage (1 >= p >= 0) between two index lists.
index = {}; *
index.reference = priv.indices[i]; * @method similarityPercentage
index.reference_size = index.reference.fields.length; * @param {Array} list_a An index list
index.current = indices[index.reference.name]; * @param {Array} list_b Another index list
* @return {Number} The similarity percentage
// a lot of loops, not sure this is the fastest way */
// loop index fields priv.similarityPercentage = function (list_a, list_b) {
for (j = 0; j < index.reference_size; j += 1) { var ai, bi, count = 0;
label = index.reference.fields[j]; for (ai = 0; ai < list_a.length; ai += 1) {
index.current_field = index.current[label]; for (bi = 0; bi < list_b.length; bi += 1) {
index.current_size = priv.getObjectSize(index.current_field); if (list_a[ai] === list_b[bi]) {
count += 1;
// loop field id array
for (j = 0; j < index.current_size; j += 1) {
for (key in index.current_field) {
if (index.current_field.hasOwnProperty(key)) {
obj = index.current_field[key];
for (prop in obj) {
if (obj.hasOwnProperty(prop)) {
for (k = 0; k < unique_docids.length; k += 1) {
if (obj[prop] === unique_docids[k]) {
found = true;
break;
}
}
if (!found) {
unique_docids.push(obj[prop]);
unique_count += 1;
}
}
}
}
} }
} }
} }
} return count / (list_a.length > list_b.length ?
list_a.length : list_b.length);
};
// construct allDocs response /**
all_doc_response.total_rows = unique_count; * Select the good index to use according to a select list.
all_doc_response.rows = []; *
for (m = 0; m < unique_count; m += 1) { * @method selectIndex
// include_docs * @param {Array} select_list An array of strings
if (include_docs) { * @return {Number} The index index
that.addJob( */
"get", priv.selectIndex = function (select_list) {
priv.substorage, var i, tmp, selector = {"index": 0, "similarity": 0};
unique_docids[m], for (i = 0; i < priv.indices.length; i += 1) {
option, tmp = priv.similarityPercentage(select_list,
success, priv.indices[i].index);
error if (tmp > selector.similarity) {
); selector.index = i;
} else { selector.similarity = tmp;
file = { value: {} };
file.id = unique_docids[m];
file.key = unique_docids[m];
all_doc_response.rows.push(file);
if (m === (unique_count - 1)) {
return all_doc_response;
} }
} }
} return selector.index;
}; };
/** /**
* Post document to substorage and create/update index file(s) * Get a database
* @method post *
* @param {object} command The JIO command * @method getIndexDatabase
* @param {string} source The source of the function call * @param {Object} option The command option
*/ * @param {Number} number The location in priv.indices
priv.postOrPut = function (command, source) { * @param {Function} callback The callback
var f = {}, indices, doc; */
doc = command.cloneDoc(); priv.getIndexDatabase = function (option, number, callback) {
if (typeof doc._id !== "string") {
doc._id = priv.generateUuid();
}
f.getIndices = function () {
var option = command.cloneOption();
that.addJob( that.addJob(
"get", "get",
priv.substorage, priv.indices[number].sub_storage || priv.sub_storage,
{"_id": priv.index_suffix}, {"_id": priv.indices[number].id},
option, option,
function (response) { function (response) {
indices = response; callback(new JSONIndex(response));
f.postDocument("put");
}, },
function (err) { function (err) {
switch (err.status) { if (err.status === 404) {
case 404: callback(new JSONIndex({
if (source !== 'PUTATTACHMENT') { "_id": priv.indices[number].id,
indices = priv.createEmptyIndexArray(); "indexing": priv.indices[number].index
f.postDocument("post"); }));
} else { return;
that.error({
"status": 404,
"statusText": "Not Found",
"error": "not found",
"message": "Document not found",
"reason": "Document not found"
});
return;
}
break;
default:
err.message = "Cannot retrieve index array";
that.error(err);
break;
} }
err.message = "Unable to get index database.";
that.error(err);
} }
); );
}; };
f.postDocument = function (index_update_method) {
if (priv.isDocidInIndex(indices, doc) && source === 'POST') { /**
// POST the document already exists * Gets a list containing all the databases set in the storage description.
that.error({ *
"status": 409, * @method getIndexDatabaseList
"statusText": "Conflicts", * @param {Object} option The command option
"error": "conflicts", * @param {Function} callback The result callback(database_list)
"message": "Cannot create a new document", */
"reason": "Document already exists" priv.getIndexDatabaseList = function (option, callback) {
}); var i, count = 0, callbacks = {}, response_list = [];
return; callbacks.error = function (index) {
return function (err) {
if (err.status === 404) {
response_list[index] = new JSONIndex({
"_id": priv.indices[index].id,
"indexing": priv.indices[index].index
});
count += 1;
if (count === priv.indices.length) {
callback(response_list);
}
return;
}
err.message = "Unable to get index database.";
that.error(err);
};
};
callbacks.success = function (index) {
return function (response) {
response_list[index] = new JSONIndex(response);
count += 1;
if (count === priv.indices.length) {
callback(response_list);
}
};
};
for (i = 0; i < priv.indices.length; i += 1) {
that.addJob(
"get",
priv.indices[i].sub_storage || priv.sub_storage,
{"_id": priv.indices[i].id},
option,
callbacks.success(i),
callbacks.error(i)
);
} }
if (source !== 'PUTATTACHMENT') { };
indices = priv.updateIndices(indices, doc);
/**
* Saves all the databases to the remote(s).
*
* @method storeIndexDatabaseList
* @param {Array} database_list The database list
* @param {Object} option The command option
* @param {Function} callback The result callback(err, response)
*/
priv.storeIndexDatabaseList = function (database_list, option, callback) {
var i, count = 0, count_max = 0, onResponse, onError;
onResponse = function (response) {
count += 1;
if (count === count_max) {
callback({"ok": true});
}
};
onError = function (err) {
err.message = "Unable to store index database.";
that.error(err);
};
for (i = 0; i < priv.indices.length; i += 1) {
if (database_list[i] !== undefined) {
count_max += 1;
that.addJob(
"put",
priv.indices[i].sub_storage || priv.sub_storage,
database_list[i].serialized(),
option,
onResponse,
onError
);
}
} }
};
/**
* A generic request method which delegates the request to the sub storage.
* On response, it will index the document from the request and update all
* the databases.
*
* @method genericRequest
* @param {Command} command The JIO command
* @param {Function} method The request method
*/
priv.genericRequest = function (command, method) {
var doc = command.cloneDoc(), option = command.cloneOption();
that.addJob( that.addJob(
source === 'PUTATTACHMENT' ? "putAttachment" : "post", method,
priv.substorage, priv.sub_storage,
doc, doc,
command.cloneOption(), option,
function () { function (response) {
if (source !== 'PUTATTACHMENT') { switch (method) {
f.sendIndices(index_update_method); case "post":
} else { case "put":
that.success({ case "remove":
"ok": true, doc._id = response.id;
"id": doc._id, priv.getIndexDatabaseList(option, function (database_list) {
"attachment": doc._attachment var i;
}); switch (method) {
} case "post":
}, case "put":
function (err) { for (i = 0; i < database_list.length; i += 1) {
switch (err.status) { database_list[i].put(doc);
case 409: }
// file already exists break;
if (source !== 'PUTATTACHMENT') { case "remove":
f.sendIndices(index_update_method); for (i = 0; i < database_list.length; i += 1) {
} else { database_list[i].remove(doc);
that.success({ }
"ok": true, break;
"id": doc._id default:
break;
}
priv.storeIndexDatabaseList(database_list, option, function () {
that.success({"ok": true, "id": doc._id});
}); });
} });
break; break;
default: default:
err.message = "Cannot upload document"; that.success(response);
that.error(err);
break; break;
} }
}
);
};
f.sendIndices = function (method) {
indices._id = priv.index_suffix;
that.addJob(
method,
priv.substorage,
indices,
command.cloneOption(),
function () {
that.success({
"ok": true,
"id": doc._id
});
}, },
function (err) { function (err) {
// xxx do we try to delete the posted document ? return that.error(err);
err.message = "Cannot save index file";
that.error(err);
} }
); );
}; };
f.getIndices();
};
/** /**
* Update the document metadata and update the index * Post the document metadata and update the index
* @method put * @method post
* @param {object} command The JIO command * @param {object} command The JIO command
*/ */
that.post = function (command) { that.post = function (command) {
priv.postOrPut(command, 'POST'); priv.genericRequest(command, 'post');
}; };
/** /**
* Update the document metadata and update the index * Update the document metadata and update the index
* @method put * @method put
* @param {object} command The JIO command * @param {object} command The JIO command
*/ */
that.put = function (command) { that.put = function (command) {
priv.postOrPut(command, 'PUT'); priv.genericRequest(command, 'put');
}; };
/** /**
* Add an attachment to a document (no index modification) * Add an attachment to a document (no index modification)
* @method putAttachment * @method putAttachment
* @param {object} command The JIO command * @param {object} command The JIO command
*/ */
that.putAttachment = function (command) { that.putAttachment = function (command) {
priv.postOrPut(command, 'PUTATTACHMENT'); priv.genericRequest(command, 'putAttachment');
}; };
/** /**
* Get the document metadata * Get the document metadata
* @method get * @method get
* @param {object} command The JIO command * @param {object} command The JIO command
*/ */
that.get = function (command) { that.get = function (command) {
that.addJob( priv.genericRequest(command, 'get');
"get", };
priv.substorage,
command.cloneDoc(),
command.cloneOption(),
function (response) {
that.success(response);
},
function (err) {
that.error(err);
}
);
};
/** /**
* Get the attachment. * Get the attachment.
* @method getAttachment * @method getAttachment
* @param {object} command The JIO command * @param {object} command The JIO command
*/ */
that.getAttachment = function (command) { that.getAttachment = function (command) {
that.addJob( priv.genericRequest(command, 'getAttachment');
"getAttachment", };
priv.substorage,
command.cloneDoc(),
command.cloneOption(),
function (response) {
that.success(response);
},
function (err) {
that.error(err);
}
);
};
/** /**
* Remove document - removing documents updates index!. * Remove document - removing documents updates index!.
* @method remove * @method remove
* @param {object} command The JIO command * @param {object} command The JIO command
*/ */
that.remove = function (command) { that.remove = function (command) {
var f = {}, indices, doc, docid, option; priv.genericRequest(command, 'remove');
};
doc = command.cloneDoc(); /**
option = command.cloneOption(); * Remove attachment
* @method removeAttachment
* @param {object} command The JIO command
*/
that.removeAttachment = function (command) {
priv.genericRequest(command, 'removeAttachment');
};
f.removeDocument = function (type) { /**
that.addJob( * Gets a document list from the substorage
"remove", * Options:
priv.substorage, * - {boolean} include_docs Also retrieve the actual document content.
doc, * @method allDocs
option, * @param {object} command The JIO command
function (response) { */
that.success(response); that.allDocs = function (command) {
}, var option = command.cloneOption(),
function () { index = priv.selectIndex(option.select_list || []);
that.error({ // Include docs option is ignored, if you want to get all the document,
"status": 409, // don't use index storage!
"statusText": "Conflict",
"error": "conflict", option.select_list = option.select_list || [];
"message": "Document Update Conflict", option.select_list.push("_id");
"reason": "Could not delete document or attachment" priv.getIndexDatabase(option, index, function (db) {
}); var i, id;
db = db._database;
complex_queries.QueryFactory.create(option.query || '').
exec(db, option);
for (i = 0; i < db.length; i += 1) {
id = db[i]._id;
delete db[i]._id;
db[i] = {
"id": id,
"key": id,
"value": db[i],
};
} }
); that.success({"total_rows": db.length, "rows": db});
});
};
that.check = function (command) {
that.repair(command, true);
}; };
f.getIndices = function () {
priv.repairIndexDatabase = function (command, index, just_check) {
var i, option = command.cloneOption();
that.addJob( that.addJob(
"get", 'allDocs',
priv.substorage, priv.sub_storage,
{"_id": priv.index_suffix}, {},
option, {'include_docs': true},
function (response) { function (response) {
// if deleting an attachment var db_list = [], db = new JSONIndex({
if (typeof command.getAttachmentId() === 'string') { "_id": command.getDocId(),
f.removeDocument('attachment'); "indexing": priv.indices[index].index
} else { });
indices = priv.cleanIndices(response, doc); for (i = 0; i < response.rows.length; i += 1) {
// store update index file db.put(response.rows[i].doc);
that.addJob( }
"put", db_list[index] = db;
priv.substorage, if (just_check) {
indices, priv.getIndexDatabase(option, index, function (current_db) {
command.cloneOption(), if (db.equals(current_db)) {
function () { return that.success({"ok": true, "_id": command.getDocId()});
// remove actual document
f.removeDocument('doc');
},
function (err) {
err.message = "Cannot save index file";
that.error(err);
} }
); return that.error(generateErrorObject(
"Different Index",
"Check failed",
"corrupt index database"
));
});
} else {
priv.storeIndexDatabaseList(db_list, {}, function () {
that.success({"ok": true, "_id": command.getDocId()});
});
} }
}, },
function () { function (err) {
that.error({ err.message = "Unable to repair the index database";
"status": 404, that.error(err);
"statusText": "Not Found",
"error": "not_found",
"message": "Document index not found, please check document ID",
"reason": "Incorrect document ID"
});
return;
} }
); );
}; };
f.getIndices();
};
/** priv.repairDocument = function (command, just_check) {
* Remove document - removing documents updates index!. var i, option = command.cloneOption();
* @method remove
* @param {object} command The JIO command
*/
that.removeAttachment = function (command) {
var f = {}, indices, doc, docid, option;
doc = command.cloneDoc();
option = command.cloneOption();
f.removeDocument = function (type) {
that.addJob(
"removeAttachment",
priv.substorage,
doc,
option,
that.success,
that.error
);
};
f.getIndices = function () {
that.addJob( that.addJob(
"get", "get",
priv.substorage, priv.sub_storage,
{"_id": priv.index_suffix}, command.cloneDoc(),
option, {},
function (response) { function (response) {
// if deleting an attachment response._id = command.getDocId();
if (typeof command.getAttachmentId() === 'string') { priv.getIndexDatabaseList(option, function (database_list) {
f.removeDocument('attachment'); if (just_check) {
} else { for (i = 0; i < database_list.length; i += 1) {
indices = priv.cleanIndices(response, doc); try {
// store update index file database_list[i].checkDocument(response);
that.addJob( } catch (e) {
"put", return that.error(generateErrorObject(
priv.substorage, e.message,
indices, "Check failed",
command.cloneOption(), "corrupt index database"
function () { ));
// remove actual document }
f.removeDocument('doc');
},
function (err) {
err.message = "Cannot save index file";
that.error(err);
} }
); that.success({"_id": command.getDocId(), "ok": true});
} } else {
for (i = 0; i < database_list.length; i += 1) {
database_list[i].put(response);
}
priv.storeIndexDatabaseList(database_list, option, function () {
that.success({"ok": true, "id": command.getDocId()});
});
}
});
}, },
function (err) { function (err) {
that.error(err); err.message = "Unable to repair document";
return that.error(err);
} }
); );
}; };
f.getIndices();
};
/** that.repair = function (command, just_check) {
* Gets a document list from the substorage var database_index = -1, i;
* Options: for (i = 0; i < priv.indices.length; i += 1) {
* - {boolean} include_docs Also retrieve the actual document content. if (priv.indices[i].id === command.getDocId()) {
* @method allDocs database_index = i;
* @param {object} command The JIO command break;
*/ }
//{ }
// "total_rows": 4,
// "rows": [
// {
// "id": "otherdoc",
// "key": "otherdoc",
// "value": {
// "rev": "1-3753476B70A49EA4D8C9039E7B04254C"
// }
// },{...}
// ]
//}
that.allDocs = function (command) {
var f = {}, option, all_docs_response, query_object, query_syntax,
query_response;
option = command.cloneOption();
f.getIndices = function () {
that.addJob( that.addJob(
"get", "repair",
priv.substorage, priv.sub_storage,
{"_id": priv.index_suffix}, command.cloneDoc(),
option, command.cloneOption(),
function (response) { function (response) {
query_syntax = command.getOption('query'); if (database_index !== -1) {
if (query_syntax !== undefined) { priv.repairIndexDatabase(command, database_index, just_check);
// build complex query object
query_object = priv.constructQueryObject(response, query_syntax);
if (query_object.length === 0) {
that.addJob(
"allDocs",
priv.substorage,
undefined,
option,
that.success,
that.error
);
} else {
// we can use index, run query on index
query_response =
jIO.ComplexQueries.query(query_syntax, query_object);
that.success(query_response);
}
} else if (command.getOption('include_docs')) {
priv.allDocsResponseFromIndex(response, true, option);
} else { } else {
all_docs_response = priv.repairDocument(command, just_check);
priv.allDocsResponseFromIndex(response, false, option);
that.success(all_docs_response);
} }
}, },
that.error function (err) {
err.message = "Could not repair sub storage";
that.error(err);
}
); );
}; };
f.getIndices();
}; return that;
return that; }
});
if (typeof exports === "object") {
// nodejs export module
Object.defineProperty(exports, "jio_index_storage", {
configurable: false,
enumerable: true,
writable: false,
value: indexStorage
});
} else if (typeof define === "function" && define.amd) {
// requirejs export
define(indexStorage);
} else {
// classical browser and web workers JIO export
jIO.addStorageType("indexed", indexStorage);
}
}());
...@@ -3,8 +3,11 @@ ...@@ -3,8 +3,11 @@
* Released under the LGPL license. * Released under the LGPL license.
* http://www.gnu.org/licenses/lgpl.html * http://www.gnu.org/licenses/lgpl.html
*/ */
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */ /*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global jIO: true, localStorage: true, setTimeout: true */ /*global jIO: true, localStorage: true, setTimeout: true,
complex_queries: true */
/** /**
* JIO Local Storage. Type = 'local'. * JIO Local Storage. Type = 'local'.
* Local browser "database" storage. * Local browser "database" storage.
...@@ -134,9 +137,11 @@ jIO.addStorageType('local', function (spec, my) { ...@@ -134,9 +137,11 @@ jIO.addStorageType('local', function (spec, my) {
} }
doc = localstorage.getItem(priv.localpath + "/" + doc_id); doc = localstorage.getItem(priv.localpath + "/" + doc_id);
if (doc === null) { if (doc === null) {
doc = command.cloneDoc();
doc._id = doc_id;
// the document does not exist // the document does not exist
localstorage.setItem(priv.localpath + "/" + doc_id, localstorage.setItem(priv.localpath + "/" + doc_id,
command.cloneDoc()); doc);
that.success({ that.success({
"ok": true, "ok": true,
"id": doc_id "id": doc_id
...@@ -359,47 +364,66 @@ jIO.addStorageType('local', function (spec, my) { ...@@ -359,47 +364,66 @@ jIO.addStorageType('local', function (spec, my) {
* @param {object} command The JIO command * @param {object} command The JIO command
*/ */
that.allDocs = function (command) { that.allDocs = function (command) {
var i, j, file, items = 0, var i, row, path_re, rows = [], document_list = [], option, document_object;
s = new RegExp("^" + priv.localpath + "\\/[^/]+$"), path_re = new RegExp(
all_doc_response = {}, "^" + complex_queries.stringEscapeRegexpCharacters(priv.localpath) +
query_object = [], query_syntax, query_response = []; "/[^/]+$"
);
query_syntax = command.getOption('query'); option = command.cloneOption();
if (query_syntax === undefined) { if (typeof complex_queries !== "object" ||
all_doc_response.rows = []; (option.query === undefined && option.sort_on === undefined &&
option.select_list === undefined &&
option.include_docs === undefined)) {
rows = [];
for (i in localStorage) { for (i in localStorage) {
if (localStorage.hasOwnProperty(i)) { if (localStorage.hasOwnProperty(i)) {
// filter non-documents // filter non-documents
if (s.test(i)) { if (path_re.test(i)) {
items += 1; row = { value: {} };
j = i.split('/').slice(-1)[0]; row.id = i.split('/').slice(-1)[0];
row.key = row.id;
file = { value: {} };
file.id = j;
file.key = j;
if (command.getOption('include_docs')) { if (command.getOption('include_docs')) {
file.doc = JSON.parse(localStorage.getItem(i)); row.doc = JSON.parse(localStorage.getItem(i));
} }
all_doc_response.rows.push(file); rows.push(row);
} }
} }
} }
all_doc_response.total_rows = items; that.success({"rows": rows, "total_rows": rows.length});
that.success(all_doc_response);
} else { } else {
// create complex query object from returned results // create complex query object from returned results
for (i in localStorage) { for (i in localStorage) {
if (localStorage.hasOwnProperty(i)) { if (localStorage.hasOwnProperty(i)) {
if (s.test(i)) { if (path_re.test(i)) {
items += 1; document_list.push(localstorage.getItem(i));
j = i.split('/').slice(-1)[0];
query_object.push(localstorage.getItem(i));
} }
} }
} }
query_response = jIO.ComplexQueries.query(query_syntax, query_object); option.select_list = option.select_list || [];
that.success(query_response); option.select_list.push("_id");
if (option.include_docs === true) {
document_object = {};
document_list.forEach(function (meta) {
document_object[meta._id] = meta;
});
}
complex_queries.QueryFactory.create(option.query || "").
exec(document_list, option);
document_list = document_list.map(function (value) {
var o = {
"id": value._id,
"key": value._id
};
if (option.include_docs === true) {
o.doc = document_object[value._id];
delete document_object[value._id];
}
delete value._id;
o.value = value;
return o;
});
that.success({"total_rows": document_list.length,
"rows": document_list});
} }
}; };
......
...@@ -4,11 +4,27 @@ ...@@ -4,11 +4,27 @@
* http://www.gnu.org/licenses/lgpl.html * http://www.gnu.org/licenses/lgpl.html
*/ */
(function (scope) { /**
* Provides some function to use complex queries with item list
*
* @module complex_queries
*/
var complex_queries;
(function () {
"use strict"; "use strict";
Object.defineProperty(scope, "ComplexQueries", { var to_export = {}, module_name = "complex_queries";
configurable: false, /**
enumerable: false, * Add a secured (write permission denied) property to an object.
writable: false, *
value: {} * @param {Object} object The object to fill
}); * @param {String} key The object key where to store the property
* @param {Any} value The value to store
*/
function _export(key, value) {
Object.defineProperty(to_export, key, {
"configurable": false,
"enumerable": true,
"writable": false,
"value": value
});
}
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global newClass: true, Query: true, query_class_dict: true,
_export: true, QueryFactory: true */
/**
* 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
*/
var ComplexQuery = newClass(Query, function (spec) {
/**
* Logical operator to use to compare object values
*
* @attribute operator
* @type String
* @default "AND"
* @optional
*/
this.operator = spec.operator || "AND";
/**
* 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(QueryFactory.create);
/**
* #crossLink "Query/match:method"
*/
this.match = function (item, wildcard_character) {
return this[this.operator](item, wildcard_character);
};
/**
* #crossLink "Query/toString:method"
*/
this.toString = function () {
var str_list = ["("], this_operator = this.operator;
this.query_list.forEach(function (query) {
str_list.push(query.toString());
str_list.push(this_operator);
});
str_list.pop(); // remove last operator
str_list.push(")");
return str_list.join(" ");
};
/**
* #crossLink "Query/serialized:method"
*/
this.serialized = function () {
var s = {
"type": "complex",
"operator": this.operator,
"query_list": []
};
this.query_list.forEach(function (query) {
s.query_list.push(query.serialized());
});
return s;
};
/**
* Comparison operator, test if all sub queries match the
* item value
*
* @method AND
* @param {Object} item The item to match
* @param {String} wildcard_character The wildcard character
* @return {Boolean} true if all match, false otherwise
*/
this.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;
}
}
return true;
};
/**
* Comparison operator, test if one of the sub queries matches the
* item value
*
* @method OR
* @param {Object} item The item to match
* @param {String} wildcard_character The wildcard character
* @return {Boolean} true if one match, false otherwise
*/
this.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;
}
}
return false;
};
/**
* Comparison operator, test if the sub query does not match the
* item value
*
* @method NOT
* @param {Object} item The item to match
* @param {String} wildcard_character The wildcard character
* @return {Boolean} true if one match, false otherwise
*/
this.NOT = function (item, wildcard_character) {
return !this.query_list[0].match(item, wildcard_character);
};
});
query_class_dict.complex = ComplexQuery;
_export("ComplexQuery", ComplexQuery);
}(jIO)); if (typeof define === "function" && define.amd) {
define(to_export);
} else if (typeof window === "object") {
Object.defineProperty(window, module_name, {
configurable: false,
enumerable: true,
writable: false,
value: to_export
});
} else if (typeof exports === "object") {
var i;
for (i in to_export) {
if (to_export.hasOwnProperty(i)) {
exports[i] = to_export[i];
}
}
} else {
complex_queries = to_export;
}
}());
Object.defineProperty(scope.ComplexQueries, "parse", { function parseStringToObject(string) {
configurable: false,
enumerable: false,
writable: false,
value: function (string) {
return result; return result;
} } // parseStringToObject
});
...@@ -37,7 +37,7 @@ boolean_expression ...@@ -37,7 +37,7 @@ boolean_expression
expression expression
: LEFT_PARENTHESE search_text RIGHT_PARENTHESE [* %% = %2; *] : LEFT_PARENTHESE search_text RIGHT_PARENTHESE [* %% = %2; *]
| COLUMN expression [* simpleQuerySetId(%2,%1.split(':').slice(0,-1).join(':')); %% = %2; *] | COLUMN expression [* simpleQuerySetKey(%2,%1.split(':').slice(0,-1).join(':')); %% = %2; *]
| value [* %% = %1; *] | value [* %% = %1; *]
; ;
...@@ -53,59 +53,60 @@ string ...@@ -53,59 +53,60 @@ string
[* [*
var arrayExtend = function () { var arrayExtend = function () {
var j,i,newlist=[],listoflists = arguments; var j, i, newlist = [], list_list = arguments;
for (j=0; j<listoflists.length; ++j) { for (j = 0; j < list_list.length; j += 1) {
for (i=0; i<listoflists[j].length; ++i) { for (i = 0; i < list_list[j].length; i += 1) {
newlist.push(listoflists[j][i]); newlist.push(list_list[j][i]);
}
} }
return newlist; }
}; return newlist;
var mkSimpleQuery = function (id,value,operator) {
return {type:'simple',operator:'=',id:id,value:value}; }, mkSimpleQuery = function (key, value, operator) {
}; return {"type": "simple", "operator": "=", "key": key, "value": value};
var mkNotQuery = function (query) {
if (query.operator === 'NOT') { }, mkNotQuery = function (query) {
return query.query_list[0]; if (query.operator === "NOT") {
} return query.query_list[0];
return {type:'complex',operator:'NOT',query_list:[query]}; }
}; return {"type": "complex", "operator": "NOT", "query_list": [query]};
var mkComplexQuery = function (operator,query_list) {
var i,query_list2 = []; }, mkComplexQuery = function (operator, query_list) {
for (i=0; i<query_list.length; ++i) { var i, query_list2 = [];
if (query_list[i].operator === operator) { for (i = 0; i < query_list.length; i += 1) {
query_list2 = arrayExtend(query_list2,query_list[i].query_list); if (query_list[i].operator === operator) {
} else { query_list2 = arrayExtend(query_list2, query_list[i].query_list);
query_list2.push(query_list[i]); } else {
} query_list2.push(query_list[i]);
}
return {type:'complex',operator:operator,query_list:query_list2};
};
var simpleQuerySetId = function (query, id) {
var i;
if (query.type === 'complex') {
for (i = 0; i < query.query_list.length; ++i) {
simpleQuerySetId (query.query_list[i],id);
}
return true;
}
if (query.type === 'simple' && !query.id) {
query.id = id;
return true;
} }
return false; }
}; return {type:"complex",operator:operator,query_list:query_list2};
var error_offsets = [];
var error_lookaheads = []; }, simpleQuerySetKey = function (query, key) {
var error_count = 0; var i;
var result; if (query.type === "complex") {
if ( ( error_count = __##PREFIX##parse( string, error_offsets, error_lookaheads ) ) > 0 ) { for (i = 0; i < query.query_list.length; ++i) {
var i; simpleQuerySetKey (query.query_list[i],key);
for (i = 0; i < error_count; ++i) {
throw new Error ( "Parse error near \"" +
string.substr ( error_offsets[i] ) +
"\", expecting \"" +
error_lookaheads[i].join() + "\"" );
} }
return true;
}
if (query.type === "simple" && !query.key) {
query.key = key;
return true;
}
return false;
},
error_offsets = [],
error_lookaheads = [],
error_count = 0,
result;
if ((error_count = __##PREFIX##parse(string, error_offsets, error_lookaheads)) > 0) {
var i;
for (i = 0; i < error_count; i += 1) {
throw new Error("Parse error near \"" +
string.substr(error_offsets[i]) +
"\", expecting \"" +
error_lookaheads[i].join() + "\"");
}
} }
*] *]
Object.defineProperty(scope.ComplexQueries,"query",{ /*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
configurable:false,enumerable:false,writable:false, /*global newClass: true, sortFunction: true, parseStringToObject: true,
value: function (query, object_list) { _export: true, stringEscapeRegexpCharacters: true */
var wildcard_character = typeof query.wildcard_character === 'string' ?
query.wildcard_character : '%', /**
operator_actions = { * The query to use to filter a list of objects.
'=': function (value1, value2) { * This is an abstract class.
value1 = '' + value1; *
return value1.match (convertToRegexp ( * @class Query
value2, wildcard_character * @constructor
)) || false && true; */
}, var Query = newClass(function () {
'!=': function (value1, value2) {
value1 = '' + value1; var that = this, emptyFunction = function () {};
return !(value1.match (convertToRegexp (
value2, wildcard_character /**
))); * Filter the item list with matching item only
}, *
'<': function (value1, value2) { return value1 < value2; }, * @method exec
'<=': function (value1, value2) { return value1 <= value2; }, * @param {Array} item_list The list of object
'>': function (value1, value2) { return value1 > value2; }, * @param {Object} [option] Some operation option
'>=': function (value1, value2) { return value1 >= value2; }, * @param {String} [option.wildcard_character="%"] The wildcard character
'AND': function (item, query_list) { * @param {Array} [option.select_list] A object keys to retrieve
var i; * @param {Array} [option.sort_on] Couples of object keys and "ascending"
for (i=0; i<query_list.length; ++i) { * or "descending"
if (! itemMatchesQuery (item, query_list[i])) { * @param {Array} [option.limit] Couple of integer, first is an index and
return false; * second is the length.
} */
} that.exec = function (item_list, option) {
return true; var i = 0;
}, while (i < item_list.length) {
'OR': function (item, query_list) { if (!that.match(item_list[i], option.wildcard_character)) {
var i; item_list.splice(i, 1);
for (i=0; i<query_list.length; ++i) { } else {
if (itemMatchesQuery (item, query_list[i])) { i += 1;
return true; }
} }
} if (option.sort_on) {
return false; Query.sortOn(option.sort_on, item_list);
}, }
'NOT': function (item, query_list) { if (option.limit) {
return !itemMatchesQuery(item, query_list[0]); item_list.splice(0, option.limit[0]);
} if (option.limit[1]) {
}, item_list.splice(option.limit[1]);
convertToRegexp = function (string) { }
return subString('^' + string.replace( }
new RegExp( Query.filterListSelect(option.select_list || [], item_list);
'([\\{\\}\\(\\)\\^\\$\\&\\.\\*\\?\\\/\\+\\|\\[\\]\\-\\\\])'. };
replace (wildcard_character?
'\\'+wildcard_character:undefined,''), /**
'g' * Test if an item matches this query
), *
'\\$1' * @method match
) + '$',(wildcard_character||undefined), '.*'); * @param {Object} item The object to test
}, * @return {Boolean} true if match, false otherwise
subString = function (string, substring, newsubstring) { */
var res = '', i = 0; that.match = function (item, wildcard_character) {
if (substring === undefined) { return true;
return string; };
}
while (1) { /**
var tmp = string.indexOf(substring,i); * The recursive parser.
if (tmp === -1) { *
break; * @method recParse
} * @private
for (; i < tmp; ++i) { * @param {Object} object The object shared in the parse process
res += string[i]; * @param {Object} options Some options usable in the parseMethods
} * @return {Any} The parser result
res += newsubstring; */
i += substring.length; function recParse(object, option) {
} var i, query = object.parsed;
for (; i<string.length; ++i) { if (query.type === "complex") {
res += string[i]; for (i = 0; i < query.query_list.length; i += 1) {
} object.parsed = query.query_list[i];
return res; recParse(object, option);
}, query.query_list[i] = object.parsed;
itemMatchesQuery = function (item, query_object) { }
var i; object.parsed = query;
if (query_object.type === 'complex') { that.onParseComplexQuery(object, option);
return operator_actions[query_object.operator]( } else if (query.type === "simple") {
item, query_object.query_list that.onParseSimpleQuery(object, option);
); }
} else { }
if (query_object.id) {
if (typeof item[query_object.id] !== 'undefined') { /**
return operator_actions[query_object.operator]( * Browse the Query in deep calling parser method in each step.
item[query_object.id], query_object.value *
); * `onParseStart` is called first, on end `onParseEnd` is called.
} else { * It starts from the simple queries at the bottom of the tree calling the
return false; * parser method `onParseSimpleQuery`, and go up calling the
} * `onParseComplexQuery` method.
} else { *
return true; * @method parse
} * @param {Object} option Any options you want (except 'parsed')
} * @return {Any} The parse result
}, */
select = function (list, select_list) { that.parse = function (option) {
var i; var object;
if (select_list.length === 0) { object = {"parsed": JSON.parse(JSON.stringify(that.serialized()))};
return; that.onParseStart(object, option);
} recParse(object, option);
for (i=0; i<list.length; ++i) { that.onParseEnd(object, option);
var list_value = {}, k; return object.parsed;
for (k=0; k<select_list.length; ++k) { };
list_value[select_list[k]] =
list[i][select_list[k]]; /**
} * Called before parsing the query. Must be overridden!
list[i] = list_value; *
} * @method onParseStart
}, * @param {Object} object The object shared in the parse process
sortFunction = function (key, asc) { * @param {Object} option Some option gave in parse()
if (asc === 'descending') { */
return function (a,b) { that.onParseStart = emptyFunction;
return a[key] < b[key] ? 1 : a[key] > b[key] ? -1 : 0;
}; /**
} * Called when parsing a simple query. Must be overridden!
return function (a,b) { *
return a[key] > b[key] ? 1 : a[key] < b[key] ? -1 : 0; * @method onParseSimpleQuery
}; * @param {Object} object The object shared in the parse process
}, * @param {Object} option Some option gave in parse()
mergeList = function (list, list_to_merge, index) { */
var i,j; that.onParseSimpleQuery = emptyFunction;
for (i = index,j = 0; i < list_to_merge.length + index; ++i, ++j) {
list[i] = list_to_merge[j]; /**
} * Called when parsing a complex query. Must be overridden!
}, *
sort = function (list, sort_list) { * @method onParseComplexQuery
var i, tmp, key, asc, sortAndMerge = function() { * @param {Object} object The object shared in the parse process
sort(tmp,sort_list.slice(1)); * @param {Object} option Some option gave in parse()
mergeList(list,tmp,i-tmp.length); */
tmp = [list[i]]; that.onParseComplexQuery = emptyFunction;
};
if (list.length < 2) { /**
return; * Called after parsing the query. Must be overridden!
} *
if (sort_list.length === 0) { * @method onParseEnd
return; * @param {Object} object The object shared in the parse process
} * @param {Object} option Some option gave in parse()
key = sort_list[0][0]; */
asc = sort_list[0][1]; that.onParseEnd = emptyFunction;
list.sort (sortFunction (key,asc));
tmp = [list[0]]; /**
for (i = 1; i < list.length; ++i) { * Convert this query to a parsable string.
if (tmp[0][key] === list[i][key]) { *
tmp.push(list[i]); * @method toString
} else { * @return {String} The string version of this query
sortAndMerge(); */
} that.toString = function () {
} return "";
sortAndMerge(); };
},
limit = function (list, limit_list) { /**
var i; * Convert this query to an jsonable object in order to be remake thanks to
if (typeof limit_list[0] !== 'undefined') { * QueryFactory class.
if (typeof limit_list[1] !== 'undefined') { *
if (list.length > limit_list[1] + limit_list[0]) { * @method serialized
list.length = limit_list[1] + limit_list[0]; * @return {Object} The jsonable object
} */
list.splice(0,limit_list[0]); that.serialized = function () {
} else { return undefined;
list.length = limit_list[0]; };
}
} }, {"static_methods": {
},
//////////////////////////////////////////////////////////// /**
result_list = [], result_list_tmp = [], j; * Filter a list of items, modifying them to select only wanted keys.
object_list = object_list || []; *
if (query.query === undefined) { * @method filterListSelect
result_list = object_list; * @static
} else { * @param {Array} select_option Key list to keep
for (j=0; j<object_list.length; ++j) { * @param {Array} list The item list to filter
if ( itemMatchesQuery ( */
object_list[j], scope.ComplexQueries.parse (query.query) "filterListSelect": function (select_option, list) {
)) { var i, j, new_item;
result_list.push(object_list[j]); for (i = 0; i < list.length; i += 1) {
} new_item = {};
} for (j = 0; j < select_option.length; j += 1) {
} new_item[select_option[j]] = list[i][select_option[j]];
if (query.filter) { }
select(result_list,query.filter.select_list || []); for (j in new_item) {
sort(result_list,query.filter.sort_on || []); if (new_item.hasOwnProperty(j)) {
limit(result_list,query.filter.limit || []); list[i] = new_item;
break;
} }
return result_list; }
}
},
/**
* Sort a list of items, according to keys and directions.
*
* @method sortOn
* @static
* @param {Array} sort_on_option List of couples [key, direction]
* @param {Array} list The item list to sort
*/
"sortOn": function (sort_on_option, list) {
var sort_index;
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]
));
} }
}); },
/**
* Parse a text request to a json query object tree
*
* @method parseStringToObject
* @static
* @param {String} string The string to parse
* @return {Object} The json query tree
*/
"parseStringToObject": parseStringToObject,
/**
* Convert a search text to a regexp.
*
* @method convertStringToRegExp
* @static
* @param {String} string The string to convert
* @param {String} [wildcard_character=undefined] The wildcard chararter
* @return {RegExp} The search text regexp
*/
"convertStringToRegExp": function (string, wildcard_character) {
return new RegExp("^" + stringEscapeRegexpCharacters(string).replace(
stringEscapeRegexpCharacters(wildcard_character),
'.*'
) + "$");
}
}});
_export("Query", Query);
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global _export: true, ComplexQuery: true, SimpleQuery: true,
newClass: true, Query: true */
var query_class_dict = {}, QueryFactory;
/**
* Provides static methods to create Query object
*
* @class QueryFactory
*/
QueryFactory = newClass({
"static_methods": {
/**
* 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
*/
"create": function (object) {
if (object === "") {
return new Query();
}
if (typeof object === "string") {
object = Query.parseStringToObject(object);
}
if (typeof (object || {}).type === "string" &&
query_class_dict[object.type]) {
return new query_class_dict[object.type](object);
}
return null;
}
}
}, function () {});
_export("QueryFactory", QueryFactory);
Object.defineProperty(scope.ComplexQueries,"serialize",{ /*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
configurable:false,enumerable:false,writable:false,value:function(query){ /*global _export: true, to_export: true */
var str_list = [], i;
if (query.type === 'complex') { function objectToSearchText(query) {
str_list.push ( '(' ); var str_list = [];
for (i=0; i<query.query_list.length; ++i) { if (query.type === "complex") {
str_list.push( scope.ComplexQueries.serialize(query.query_list[i]) ); str_list.push("(");
str_list.push( query.operator ); (query.query_list || []).forEach(function (sub_query) {
} str_list.push(objectToSearchText(sub_query));
str_list.length --; str_list.push(query.operator);
str_list.push ( ')' ); });
return str_list.join(' '); str_list.length -= 1;
} else if (query.type === 'simple') { str_list.push(")");
return query.id + (query.id?': ':'') + query.operator + ' "' + query.value + '"'; return str_list.join(" ");
} }
return query; if (query.type === "simple") {
} return query.id + (query.id ? ": " : "") + (query.operator || "=") + ' "' +
}); query.value + '"';
}
throw new TypeError("This object is not a query");
}
_export("objectToSearchText", objectToSearchText);
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global newClass: true, Query: true,
query_class_dict: true, _export: true */
/**
* 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
*/
var SimpleQuery = newClass(Query, function (spec) {
/**
* Operator to use to compare object values
*
* @attribute operator
* @type String
* @default "="
* @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;
/**
* #crossLink "Query/match:method"
*/
this.match = function (item, wildcard_character) {
return this[this.operator](item[this.key], this.value, wildcard_character);
};
/**
* #crossLink "Query/toString:method"
*/
this.toString = function () {
return (this.key ? this.key + ": " : "") + (this.operator || "=") + ' "' +
this.value + '"';
};
/**
* #crossLink "Query/serialized:method"
*/
this.serialized = function () {
return {
"type": "simple",
"operator": this.operator,
"key": this.key,
"value": this.value
};
};
/**
* 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
* @param {String} wildcard_character The wildcard_character
* @return {Boolean} true if match, false otherwise
*/
this["="] = function (object_value, comparison_value,
wildcard_character) {
return Query.convertStringToRegExp(
comparison_value.toString(),
wildcard_character || "%"
).test(object_value.toString());
};
/**
* 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
* @param {String} wildcard_character The wildcard_character
* @return {Boolean} true if not match, false otherwise
*/
this["!="] = function (object_value, comparison_value,
wildcard_character) {
return !Query.convertStringTextToRegExp(
comparison_value.toString(),
wildcard_character || "%"
).test(object_value.toString());
};
/**
* 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
*/
this["<"] = function (object_value, comparison_value) {
return object_value < comparison_value;
};
/**
* 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
*/
this["<="] = function (object_value, comparison_value) {
return object_value <= comparison_value;
};
/**
* 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
*/
this[">"] = function (object_value, comparison_value) {
return object_value > comparison_value;
};
/**
* 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
*/
this[">="] = function (object_value, comparison_value) {
return object_value >= comparison_value;
};
});
query_class_dict.simple = SimpleQuery;
_export("SimpleQuery", SimpleQuery);
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global _export: true */
/**
* Create a class, manage inheritance, static methods,
* protected attributes and can hide methods or/and secure methods
*
* @param {Class} Class Classes to inherit from (0..n). The last class
* parameter will inherit from the previous one, and so on
* @param {Object} option Class option (0..n)
* @param {Boolean} [option.secure_methods=false] Make methods not configurable
* and not writable
* @param {Boolean} [option.hide_methods=false] Make methods not enumerable
* @param {Boolean} [option.secure_static_methods=true] Make static methods not
* configurable and not
* writable
* @param {Boolean} [option.hide_static_methods=false] Make static methods not
* enumerable
* @param {Object} [option.static_methods={}] Object of static methods
* @param {Function} constructor The new class constructor
* @return {Class} The new class
*/
function newClass() {
var j, k, constructors = [], option, new_class;
for (j = 0; j < arguments.length; j += 1) {
if (typeof arguments[j] === "function") {
constructors.push(arguments[j]);
} else if (typeof arguments[j] === "object") {
option = option || {};
for (k in arguments[j]) {
if (arguments[j].hasOwnProperty(k)) {
option[k] = arguments[j][k];
}
}
}
}
function postObjectCreation(that) {
// modify the object according to 'option'
var key;
if (option) {
for (key in that) {
if (that.hasOwnProperty(key)) {
if (typeof that[key] === "function") {
Object.defineProperty(that, key, {
"configurable": option.secure_methods ? false : true,
"enumerable": option.hide_methods ? false : true,
"writable": option.secure_methods ? false : true,
"value": that[key]
});
}
}
}
}
}
function postClassCreation(that) {
// modify the object according to 'option'
var key;
if (option) {
for (key in that) {
if (that.hasOwnProperty(key)) {
if (typeof that[key] === "function") {
Object.defineProperty(that, key, {
"configurable": option.secure_static_methods ===
false ? true : false,
"enumerable": option.hide_static_methods ? false : true,
"writable": option.secure_static_methods === false ? true : false,
"value": that[key]
});
}
}
}
}
}
new_class = function (spec, my) {
var i;
spec = spec || {};
my = my || {};
// don't use forEach !
for (i = 0; i < constructors.length; i += 1) {
constructors[i].apply(this, [spec, my]);
}
postObjectCreation(this);
return this;
};
option = option || {};
option.static_methods = option.static_methods || {};
for (j in option.static_methods) {
if (option.static_methods.hasOwnProperty(j)) {
new_class[j] = option.static_methods[j];
}
}
postClassCreation(new_class);
return new_class;
}
/**
* 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");
}
}
_export("stringEscapeRegexpCharacters", stringEscapeRegexpCharacters);
/**
* 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) {
if (way === 'descending') {
return function (a, b) {
return a[key] < b[key] ? 1 : a[key] > b[key] ? -1 : 0;
};
}
return function (a, b) {
return a[key] > b[key] ? 1 : a[key] < b[key] ? -1 : 0;
};
}
...@@ -1174,7 +1174,7 @@ test ("AllDocs", function(){ ...@@ -1174,7 +1174,7 @@ test ("AllDocs", function(){
// include docs // include docs
o.allDocsResponse = {}; o.allDocsResponse = {};
o.allDocsResponse.rows = []; o.allDocsResponse.rows = [];
o.allDocsResponse.total_rows = 15; o.allDocsResponse.total_rows = m;
for (i = 0; i < m; i += 1) { for (i = 0; i < m; i += 1) {
o.allDocsResponse.rows.push({ o.allDocsResponse.rows.push({
"id": "doc_"+(i < 10 ? "0"+i : i), "id": "doc_"+(i < 10 ? "0"+i : i),
...@@ -1186,7 +1186,7 @@ test ("AllDocs", function(){ ...@@ -1186,7 +1186,7 @@ test ("AllDocs", function(){
// alldocs // alldocs
o.spy(o, "value", o.allDocsResponse, "All docs (include docs)"); o.spy(o, "value", o.allDocsResponse, "All docs (include docs)");
o.jio.allDocs({"include_docs":true}, function (err, response) { o.jio.allDocs({"include_docs": true}, function (err, response) {
if (response && response.rows) { if (response && response.rows) {
response.rows.sort(function (a, b) { response.rows.sort(function (a, b) {
return a.id > b.id ? 1 : a.id < b.id ? -1 : 0; return a.id > b.id ? 1 : a.id < b.id ? -1 : 0;
...@@ -1197,65 +1197,56 @@ test ("AllDocs", function(){ ...@@ -1197,65 +1197,56 @@ test ("AllDocs", function(){
o.tick(o); o.tick(o);
// complex queries // complex queries
o.thisShouldBeTheAnswer4 = [ o.thisShouldBeTheAnswer4 = {"total_rows": 0, "rows": []};
{"title": "Inception", "year": 2010}, o.allDocsResponse.rows.forEach(function (row) {
{"title": "The Dark Knight", "year": 2008}, var new_row;
{"title": "Lord of the Rings - Return of the King", "year": 2003}, if (row.doc.year >= 1980) {
{"title": "Lord Of the Rings - Fellowship of the Ring", "year": 2001}, new_row = JSON.parse(JSON.stringify(row));
{"title": "Fight Club", "year": 1999} new_row.value.title = row.doc.title;
]; new_row.value.year = row.doc.year;
delete new_row.doc;
o.thisShouldBeTheAnswer4.rows.push(new_row);
o.thisShouldBeTheAnswer4.total_rows += 1;
}
});
o.thisShouldBeTheAnswer4.rows.sort(function (a, b) {
return a.value.year > b.value.year ? -1 :
a.value.year < b.value.year ? 1 : 0;
});
o.thisShouldBeTheAnswer4.total_rows = 5;
o.thisShouldBeTheAnswer4.rows.length = 5;
o.spy(o, "value", o.thisShouldBeTheAnswer4, o.spy(o, "value", o.thisShouldBeTheAnswer4,
"allDocs (complex queries year >= 1980, all query options)"); "allDocs (complex queries year >= 1980, all query options)");
o.jio.allDocs({ o.jio.allDocs({
"query":{ "query": '(year: >= "1980")',
"query":'(year: >= "1980")', "limit": [0,5],
"filter": { "sort_on": [["year", "descending"]],
"limit":[0,5], "select_list": ["title", "year"]
"sort_on":[['year','descending']],
"select_list":['title','year']
},
"wildcard_character":'%'
}
}, o.f); }, o.f);
o.tick(o); o.tick(o);
// empty query returns all // empty query returns all
o.thisShouldBeTheAnswer5 = [ o.thisShouldBeTheAnswer5 = {"total_rows": 0, "rows": []};
{"title": "The Good, The Bad and The Ugly"}, o.allDocsResponse.rows.forEach(function (row) {
{"title": "The Dark Knight"}, var new_row = JSON.parse(JSON.stringify(row));
{"title": "Star Wars Episode V"}, new_row.value.title = row.doc.title;
{"title": "Shawshank Redemption"}, o.thisShouldBeTheAnswer5.rows.push(new_row);
{"title": "Schindlers List"}, o.thisShouldBeTheAnswer5.total_rows += 1;
{"title": "Pulp Fiction"}, });
{"title": "One flew over the Cuckoo's Nest"}, o.thisShouldBeTheAnswer5.rows.sort(function (a, b) {
{"title": "Lord of the Rings - Return of the King"}, return a.value.title > b.value.title ? -1 :
{"title": "Lord Of the Rings - Fellowship of the Ring"}, a.value.title < b.value.title ? 1 : 0;
{"title": "Inception"}, });
{"title": "Godfellas"},
{"title": "Godfather 2"},
{"title": "Godfather"},
{"title": "Fight Club"},
{"title": "12 Angry Men"}
];
o.spy(o, "value", o.thisShouldBeTheAnswer5, o.spy(o, "value", o.thisShouldBeTheAnswer5,
"allDocs (empty query in complex query)"); "allDocs (empty query in complex query)");
o.jio.allDocs({ o.jio.allDocs({
"query":{ "sort_on": [["title", "descending"]],
"filter": { "select_list": ["title"],
"sort_on":[['title','descending']], "include_docs": true
"select_list":['title'] }, o.f);
},
"wildcard_character":'%'
}
}, function (err, response) {
if (response && response.rows) {
response.rows.sort(function (a, b) {
return a.id > b.id ? 1 : a.id < b.id ? -1 : 0;
});
}
o.f(err, response);
});
o.tick(o); o.tick(o);
o.jio.stop(); o.jio.stop();
...@@ -4334,56 +4325,62 @@ test ("Post", function () { ...@@ -4334,56 +4325,62 @@ test ("Post", function () {
var o = generateTools(this); var o = generateTools(this);
o.jio = JIO.newJio({ o.jio = JIO.newJio({
"type": "indexed", "type": "indexed",
"indices": [ "indices": [
{"name":"indexA", "fields":["findMeA"]}, {"id": "A", "index": ["title"]},
{"name":"indexAB", "fields":["findMeA","findMeB"]} {"id": "B", "index": ["title", "year"]}
], ],
"field_types": { "sub_storage": {
"findMeA": "string", "type": "local",
"findMeB": "string" "username": "ipost",
}, "application_name": "ipost"
"sub_storage": { }
"type": "local",
"username": "ipost",
"application_name": "ipost"
}
}); });
// post without id // post without id
o.spy (o, "status", undefined, "Post without id"); o.spy (o, "jobstatus", "done", "Post without id");
o.jio.post({}, o.f); o.jio.post({}, function (err, response) {
o.id = (response || {}).id;
o.f(err, response);
});
o.tick(o); o.tick(o);
// post non empty document // post non empty document
o.doc = {"_id": "some_id", "title": "myPost1", o.doc = {"_id": "some_id", "title": "My Title",
"findMeA":"keyword_abc", "findMeB":"keyword_def" "year": 2000, "hey": "def"};
};
o.spy (o, "value", {"ok": true, "id": "some_id"}, "Post document"); o.spy (o, "value", {"ok": true, "id": "some_id"}, "Post document");
o.jio.post(o.doc, o.f); o.jio.post(o.doc, o.f);
o.tick(o); o.tick(o);
// check document // check document
o.fakeIndex = { o.fakeIndexA = {
"_id": "ipost_indices.json", "_id": "A",
"indexAB": { "indexing": ["title"],
"findMeA": { "free": [],
"keyword_abc":["some_id"] "location": {
}, "some_id": 0
"findMeB": {
"keyword_def":["some_id"]
}
}, },
"indexA": { "database": [
"findMeA": { {"_id": "some_id", "title": "My Title"}
"keyword_abc":["some_id"] ]
}
}
}; };
o.jio.get({"_id": "ipost_indices.json"}, function (err, response) { o.spy(o, "value", o.fakeIndexA, "Check index file");
o.actualIndex = response; o.jio.get({"_id": "A"}, o.f);
deepEqual(o.actualIndex, o.fakeIndex, "Check index file"); o.tick(o);
});
o.fakeIndexB = {
"_id": "B",
"indexing": ["title", "year"],
"free": [],
"location": {
"some_id": 0
},
"database": [
{"_id": "some_id", "title": "My Title", "year": 2000}
]
};
o.spy(o, "value", o.fakeIndexB, "Check index file");
o.jio.get({"_id": "B"}, o.f);
o.tick(o); o.tick(o);
// post with escapable characters // post with escapable characters
...@@ -4411,21 +4408,17 @@ test ("Put", function(){ ...@@ -4411,21 +4408,17 @@ test ("Put", function(){
var o = generateTools(this); var o = generateTools(this);
o.jio = JIO.newJio({ o.jio = JIO.newJio({
"type": "indexed", "type": "indexed",
"indices": [ "indices": [
{"name":"indexA", "fields":["author"]}, {"id": "A", "index": ["author"]},
{"name":"indexAB", "fields":["author","year"]} {"id": "B", "index": ["year"]}
], ],
"field_types": { "sub_storage": {
"author": "string", "type": "local",
"year": "number" "username": "iput",
}, "application_name": "iput"
"sub_storage": { }
"type": "local", });
"username": "iput",
"application_name": "iput"
}
});
// put without id // put without id
// error 20 -> document id required // error 20 -> document id required
...@@ -4434,151 +4427,241 @@ test ("Put", function(){ ...@@ -4434,151 +4427,241 @@ test ("Put", function(){
o.tick(o); o.tick(o);
// put non empty document // put non empty document
o.doc = {"_id": "put1", "title": "myPut1", "author":"John Doe"}; o.doc = {"_id": "put1", "title": "myPut1", "author": "John Doe"};
o.spy (o, "value", {"ok": true, "id": "put1"}, "Put-create document"); o.spy (o, "value", {"ok": true, "id": "put1"}, "Put-create document");
o.jio.put(o.doc, o.f); o.jio.put(o.doc, o.f);
o.tick(o); o.tick(o);
// check index file // check index file
o.fakeIndex = { o.fakeIndexA = {
"indexA": { "_id": "A",
"author": { "indexing": ["author"],
"John Doe": ["put1"] "free": [],
} "location": {
}, "put1": 0
"indexAB": {
"author": {
"John Doe": ["put1"]
},
"year": {}
}, },
"_id": "iput_indices.json" "database": [{"_id": "put1", "author": "John Doe"}]
}; };
o.jio.get({"_id": "iput_indices.json"}, function (err, response) { o.spy(o, "value", o.fakeIndexA, "Check index file");
o.actualIndex = response; o.jio.get({"_id": "A"}, o.f);
deepEqual(o.actualIndex, o.fakeIndex, "Check index file"); o.tick(o);
});
o.fakeIndexB = {
"_id": "B",
"indexing": ["year"],
"free": [],
"location": {},
"database": []
};
o.spy(o, "value", o.fakeIndexB, "Check index file");
o.jio.get({"_id": "B"}, o.f);
o.tick(o); o.tick(o);
// modify document - modify keyword on index! // modify document - modify keyword on index!
o.doc = {"_id": "put1", "title": "myPuttter1", "author":"Jane Doe"}; o.doc = {"_id": "put1", "title": "myPuttter1", "author": "Jane Doe"};
o.spy (o, "value", {"ok": true, "id": "put1"}, "Modify existing document"); o.spy (o, "value", {"ok": true, "id": "put1"}, "Modify existing document");
o.jio.put(o.doc, o.f); o.jio.put(o.doc, o.f);
o.tick(o); o.tick(o);
// check index file // check index file
o.fakeIndex = { o.fakeIndexA.database[0].author = "Jane Doe";
"indexA": { o.spy(o, "value", o.fakeIndexA, "Check index file");
"author": { o.jio.get({"_id": "A"}, o.f);
"Jane Doe": ["put1"]
}
},
"indexAB": {
"author": {
"Jane Doe": ["put1"]
},
"year": {}
},
"_id": "iput_indices.json"
};
o.jio.get({"_id": "iput_indices.json"}, function (err, response) {
o.actualIndex = response;
deepEqual(o.actualIndex, o.fakeIndex, "Check index file");
});
o.tick(o); o.tick(o);
// add new document with same keyword! // add new document with same keyword!
o.doc = {"_id": "new_doc", "title": "myPut2", "author":"Jane Doe"}; o.doc = {"_id": "new_doc", "title": "myPut2", "author": "Jane Doe"};
o.spy (o, "value", {"ok": true, "id": "new_doc"}, o.spy (o, "value", {"ok": true, "id": "new_doc"},
"Add new document with same keyword"); "Add new document with same keyword");
o.jio.put(o.doc, o.f); o.jio.put(o.doc, o.f);
o.tick(o); o.tick(o);
// check index file // check index file
o.fakeIndex = { o.fakeIndexA.location.new_doc = 1;
"indexA": { o.fakeIndexA.database.push({"_id": "new_doc", "author": "Jane Doe"});
"author": { o.spy(o, "value", o.fakeIndexA, "Check index file");
"Jane Doe": ["put1", "new_doc"] o.jio.get({"_id": "A"}, o.f);
}
},
"indexAB": {
"author": {
"Jane Doe": ["put1", "new_doc"]
},
"year": {}
},
"_id": "iput_indices.json"
};
o.jio.get({"_id": "iput_indices.json"}, function (err, response) {
o.actualIndex = response;
deepEqual(o.actualIndex, o.fakeIndex, "Check index file");
});
o.tick(o); o.tick(o);
// add second keyword to index file // add second keyword to index file
o.doc = {"_id": "put1", "title": "myPut2", "author":"Jane Doe", o.doc = {"_id": "put1", "title": "myPut2", "author": "Jane Doe",
"year":"1912"}; "year":"1912"};
o.spy (o, "value", {"ok": true, "id": "put1"}, o.spy (o, "value", {"ok": true, "id": "put1"},
"add second keyword to index file"); "add second keyword to index file");
o.jio.put(o.doc, o.f); o.jio.put(o.doc, o.f);
o.tick(o); o.tick(o);
// check index file // check index file
o.fakeIndex = { o.spy(o, "value", o.fakeIndexA, "Check index file");
"indexA": { o.jio.get({"_id": "A"}, o.f);
"author": { o.tick(o);
"Jane Doe": ["put1"]
} o.fakeIndexB.location.put1 = 0;
}, o.fakeIndexB.database.push({"_id": "put1", "year": "1912"});
"indexAB": { o.spy(o, "value", o.fakeIndexB, "Check index file");
"author": { o.jio.get({"_id": "B"}, o.f);
"Jane Doe": ["put1"]
},
"year": {
"1912": ["put1"]
}
},
"_id": "iput_indices.json"
};
o.jio.get({"_id": "iput_indices.json"}, function (err, response) {
o.actualIndex = response;
deepEqual(o.actualIndex, o.fakeIndex, "Check index file");
});
o.tick(o); o.tick(o);
// remove a keyword from an existing document // remove a keyword from an existing document
o.doc = {"_id": "new_doc", "title": "myPut2"}; o.doc = {"_id": "new_doc", "title": "myPut2"};
o.spy (o, "value", {"ok": true, "id": "new_doc"}, o.spy (o, "value", {"ok": true, "id": "new_doc"},
"Remove keyword from existing document"); "Remove keyword from existing document");
o.jio.put(o.doc, o.f); o.jio.put(o.doc, o.f);
o.tick(o); o.tick(o);
// check index file // check index file
o.fakeIndex = { delete o.fakeIndexA.location.new_doc;
"indexA": { o.fakeIndexA.database[1] = null;
"author": { o.fakeIndexA.free.push(1);
"Jane Doe": ["put1"] o.spy(o, "value", o.fakeIndexA, "Check index file");
} o.jio.get({"_id": "A"}, o.f);
},
"indexAB": {
"author": {
"Jane Doe": ["put1"]
},
"year": {
"1912": ["put1"]
}
},
"_id": "iput_indices.json"
};
o.jio.get({"_id": "iput_indices.json"}, function (err, response) {
o.actualIndex = response;
deepEqual(o.actualIndex, o.fakeIndex, "Check index file");
});
o.tick(o); o.tick(o);
o.jio.stop(); o.jio.stop();
}); });
test("Check & Repair", function () {
var o = generateTools(this), i;
o.jio = JIO.newJio({
"type": "indexed",
"indices": [
{"id": "A", "index": ["director"]},
{"id": "B", "index": ["year"]}
],
"sub_storage": {
"type": "local",
"username": "indexstoragerepair"
}
});
o.fakeIndexA = {
"_id": "A",
"indexing": ["director"],
"free": [],
"database": []
};
o.fakeIndexB = {
"_id": "B",
"indexing": ["year"],
"free": [],
"database": []
};
for (i = 0; i < 10; i += 1) {
o.jio.put({
"_id": "id" + i,
"director": "D" + i,
"year": i,
"title": "T" + i
});
o.tmp = o.fakeIndexA.free.pop() || o.fakeIndexA.database.length;
o.fakeIndexA.database[o.tmp] = {"_id": "id" + i, "director": "D" + i};
o.tmp = o.fakeIndexB.free.pop() || o.fakeIndexB.database.length;
o.fakeIndexB.database[o.tmp] = {"_id": "id" + i, "year": i};
}
o.clock.tick(5000);
o.spy(o, "status", 40, "Check database");
o.jio.check({"_id": "A"}, o.f);
o.tick(o);
o.spy(o, "status", 40, "Check database");
o.jio.check({"_id": "B"}, o.f);
o.tick(o);
o.spy(o, "value", {"_id": "A", "ok": true}, "Repair database");
o.jio.repair({"_id": "A"}, o.f);
o.tick(o);
o.spy(o, "value", {"_id": "B", "ok": true}, "Repair database");
o.jio.repair({"_id": "B"}, o.f);
o.tick(o);
o.spy(o, "value", {"_id": "A", "ok": true}, "Check database again");
o.jio.check({"_id": "A"}, o.f);
o.tick(o);
o.spy(o, "value", {"_id": "B", "ok": true}, "Check database again");
o.jio.check({"_id": "B"}, o.f);
o.tick(o);
// check index file
o.spy(o, "value", o.fakeIndexA, "Manually check index file");
o.jio.get({"_id": "A"}, function (err, response) {
if (response) {
delete response.location;
response.database.sort(function (a, b) {
return a._id < b._id ? -1 : a._id > b._id ? 1 : 0;
});
}
o.f(err, response);
});
o.tick(o);
o.spy(o, "value", o.fakeIndexB, "Manually check index file");
o.jio.get({"_id": "B"}, function (err, response) {
if (response) {
delete response.location;
response.database.sort(function (a, b) {
return a._id < b._id ? -1 : a._id > b._id ? 1 : 0;
});
}
o.f(err, response);
});
o.tick(o);
o.jio2 = JIO.newJio({"type": "local", "username": "indexstoragerepair"});
o.jio2.put({"_id": "blah", "title": "t", "year": "y", "director": "d"});
o.clock.tick(1000);
o.jio2.stop();
o.fakeIndexA.database.unshift({"_id": "blah", "director": "d"});
o.fakeIndexB.database.unshift({"_id": "blah", "year": "y"});
o.spy(o, "status", 40, "Check Document");
o.jio.check({"_id": "blah"}, o.f)
o.tick(o);
o.spy(o, "value", {"id": "blah", "ok": true}, "Repair Document");
o.jio.repair({"_id": "blah"}, o.f)
o.tick(o);
o.spy(o, "value", {"id": "blah", "ok": true}, "Check Document again");
o.jio.repair({"_id": "blah"}, o.f)
o.tick(o);
// check index file
o.spy(o, "value", o.fakeIndexA, "Manually check index file");
o.jio.get({"_id": "A"}, function (err, response) {
if (response) {
delete response.location;
response.database.sort(function (a, b) {
return a._id < b._id ? -1 : a._id > b._id ? 1 : 0;
});
}
o.f(err, response);
});
o.tick(o);
o.spy(o, "value", o.fakeIndexB, "Manually check index file");
o.jio.get({"_id": "B"}, function (err, response) {
if (response) {
delete response.location;
response.database.sort(function (a, b) {
return a._id < b._id ? -1 : a._id > b._id ? 1 : 0;
});
}
o.f(err, response);
});
o.tick(o);
o.jio.stop();
});
test ("PutAttachment", function(){ test ("PutAttachment", function(){
// not sure these need to be run, because the index does not change // not sure these need to be run, because the index does not change
...@@ -4587,21 +4670,17 @@ test ("PutAttachment", function(){ ...@@ -4587,21 +4670,17 @@ test ("PutAttachment", function(){
var o = generateTools(this); var o = generateTools(this);
o.jio = JIO.newJio({ o.jio = JIO.newJio({
"type": "indexed", "type": "indexed",
"indices": [ "indices": [
{"name":"indexA", "fields":["author"]}, {"id": "A", "index": ["author"]},
{"name":"indexAB", "fields":["author","year"]} {"id": "B", "index": ["year"]}
], ],
"field_types": { "sub_storage": {
"author": "string", "type": "local",
"year": "number" "username": "iputatt",
}, "application_name": "iputatt"
"sub_storage": { }
"type": "local", });
"username": "iputatt",
"application_name": "iputatt"
}
});
// putAttachment without doc id // putAttachment without doc id
// error 20 -> document id required // error 20 -> document id required
...@@ -4624,7 +4703,7 @@ test ("PutAttachment", function(){ ...@@ -4624,7 +4703,7 @@ test ("PutAttachment", function(){
// putAttachment with document // putAttachment with document
o.doc = {"_id": "putattmt1","title": "myPutAttmt1"}; o.doc = {"_id": "putattmt1","title": "myPutAttmt1"};
o.spy (o, "value", {"ok": true, "id": "putattmt1"}, o.spy (o, "value", {"ok": true, "id": "putattmt1"},
"Put underlying document"); "Put underlying document");
o.jio.put(o.doc, o.f); o.jio.put(o.doc, o.f);
o.tick(o); o.tick(o);
...@@ -4704,21 +4783,17 @@ test ("Get", function(){ ...@@ -4704,21 +4783,17 @@ test ("Get", function(){
var o = generateTools(this); var o = generateTools(this);
o.jio = JIO.newJio({ o.jio = JIO.newJio({
"type": "indexed", "type": "indexed",
"indices": [ "indices": [
{"name":"indexA", "fields":["author"]}, {"id": "A", "index": ["author"]},
{"name":"indexAB", "fields":["author","year"]} {"id": "B", "index": ["year"]}
], ],
"field_types": { "sub_storage": {
"author": "string", "type": "local",
"year": "number" "username": "iget",
}, "application_name": "iget"
"sub_storage": { }
"type": "local", });
"username": "iget",
"application_name": "iget"
}
});
// get inexistent document // get inexistent document
o.spy(o, "status", 404, "Get inexistent document"); o.spy(o, "status", 404, "Get inexistent document");
...@@ -4776,13 +4851,9 @@ test ("Remove", function(){ ...@@ -4776,13 +4851,9 @@ test ("Remove", function(){
o.jio = JIO.newJio({ o.jio = JIO.newJio({
"type": "indexed", "type": "indexed",
"indices": [ "indices": [
{"name":"indexA", "fields":["author"]}, {"id": "A", "index": ["author"]},
{"name":"indexAB", "fields":["author","year"]} {"id": "B", "index": ["year"]}
], ],
"field_types": {
"author": "string",
"year": "number"
},
"sub_storage": { "sub_storage": {
"type": "local", "type": "local",
"username": "irem", "username": "irem",
...@@ -4818,26 +4889,30 @@ test ("Remove", function(){ ...@@ -4818,26 +4889,30 @@ test ("Remove", function(){
o.tick(o); o.tick(o);
// check index // check index
o.fakeIndex = { o.fakeIndexA = {
"_id": "irem_indices.json", "_id": "A",
"indexA": { "indexing": ["author"],
"author": { "free": [0],
"Martin Mustermann": ["removeAlso"] "location": {
} "removeAlso": 1
}, },
"indexAB": { "database": [null, {"_id": "removeAlso", "author": "Martin Mustermann"}]
"year": {
"2525": ["removeAlso"]
},
"author": {
"Martin Mustermann": ["removeAlso"]
}
}
}; };
o.jio.get({"_id": "irem_indices.json"},function(err, response){ o.spy(o, "value", o.fakeIndexA, "Check index file");
o.actualIndex = response; o.jio.get({"_id": "A"}, o.f);
deepEqual(o.actualIndex, o.fakeIndex, "Check index file"); o.tick(o);
});
o.fakeIndexB = {
"_id": "B",
"indexing": ["year"],
"free": [0],
"location": {
"removeAlso": 1
},
"database": [null, {"_id": "removeAlso", "year": "2525"}]
};
o.spy(o, "value", o.fakeIndexB, "Check index file");
o.jio.get({"_id": "B"}, o.f);
o.tick(o); o.tick(o);
// check document // check document
...@@ -4877,29 +4952,18 @@ test ("Remove", function(){ ...@@ -4877,29 +4952,18 @@ test ("Remove", function(){
o.tick(o); o.tick(o);
// check index // check index
o.fakeIndex = { o.fakeIndexA.free = [];
"_id": "irem_indices.json", o.fakeIndexA.location.remove3 = 0;
"indexA": { o.fakeIndexA.database[0] = {"_id": "remove3", "author": "Mrs Sunshine"};
"author":{ o.spy(o, "value", o.fakeIndexA, "Check index file");
"Martin Mustermann": ["removeAlso"], o.jio.get({"_id": "A"}, o.f);
"Mrs Sunshine": ["remove3"] o.tick(o);
}
}, o.fakeIndexB.free = [];
"indexAB": { o.fakeIndexB.location.remove3 = 0;
"year": { o.fakeIndexB.database[0] = {"_id": "remove3", "year": "1234"};
"1234": ["remove3"], o.spy(o, "value", o.fakeIndexB, "Check index file");
"2525": ["removeAlso"] o.jio.get({"_id": "B"}, o.f);
},
"author": {
"Martin Mustermann": ["removeAlso"],
"Mrs Sunshine": ["remove3"]
}
}
};
o.jio.get({"_id": "irem_indices.json"}, function (err, response) {
o.actualIndex = response;
deepEqual(o.actualIndex, o.fakeIndex, "Check index file");
});
o.tick(o); o.tick(o);
// remove document and attachment together // remove document and attachment together
...@@ -4909,26 +4973,18 @@ test ("Remove", function(){ ...@@ -4909,26 +4973,18 @@ test ("Remove", function(){
o.tick(o); o.tick(o);
// check index // check index
o.fakeIndex = { o.fakeIndexA.free = [0];
"_id": "irem_indices.json", delete o.fakeIndexA.location.remove3;
"indexA": { o.fakeIndexA.database[0] = null;
"author": { o.spy(o, "value", o.fakeIndexA, "Check index file");
"Martin Mustermann": ["removeAlso"] o.jio.get({"_id": "A"}, o.f);
} o.tick(o);
},
"indexAB": { o.fakeIndexB.free = [0];
"year": { delete o.fakeIndexB.location.remove3;
"2525": ["removeAlso"] o.fakeIndexB.database[0] = null;
}, o.spy(o, "value", o.fakeIndexB, "Check index file");
"author": { o.jio.get({"_id": "B"}, o.f);
"Martin Mustermann": ["removeAlso"]
}
}
};
o.jio.get({"_id": "irem_indices.json"}, function (err, response) {
o.actualIndex = response;
deepEqual(o.actualIndex, o.fakeIndex, "Check index file");
});
o.tick(o); o.tick(o);
// check attachment // check attachment
...@@ -4948,22 +5004,18 @@ test ("AllDocs", function () { ...@@ -4948,22 +5004,18 @@ test ("AllDocs", function () {
var o = generateTools(this); var o = generateTools(this);
o.jio = JIO.newJio({ o.jio = JIO.newJio({
"type": "indexed", "type": "indexed",
"indices": [ "indices": [
{"name":"indexA", "fields":["author"]}, {"id": "A", "index": ["author"]},
{"name":"indexAB", "fields":["author","year"]} {"id": "B", "index": ["year"]}
], ],
"field_types": { "sub_storage": {
"author": "string", "type": "local",
"year": "number" "username": "iall",
}, "application_name": "iall"
"sub_storage": { }
"type": "local", });
"username": "iall",
"application_name": "iall"
}
});
// adding documents // adding documents
o.all1 = { "_id": "dragon.doc", o.all1 = { "_id": "dragon.doc",
...@@ -4992,34 +5044,25 @@ test ("AllDocs", function () { ...@@ -4992,34 +5044,25 @@ test ("AllDocs", function () {
o.tick(o); o.tick(o);
// check index // check index
o.fakeIndex = { o.fakeIndexA = {
"_id": "iall_indices.json", "_id": "A",
"indexA": { "indexing": ["author"],
"author": { "free": [],
"Dr. No": ["dragon.doc"], "location": {
"Dr. Who": ["timemachine"], "dragon.doc": 0,
"Dr. Snuggles": ["rocket.ppt"], "timemachine": 1,
"Dr. House":["stick.jpg"] "rocket.ppt": 2,
} "stick.jpg": 3
}, },
"indexAB": { "database": [
"author": { {"_id": "dragon.doc", "author": "Dr. No"},
"Dr. No": ["dragon.doc"], {"_id": "timemachine", "author": "Dr. Who"},
"Dr. Who": ["timemachine"], {"_id": "rocket.ppt", "author": "Dr. Snuggles"},
"Dr. Snuggles": ["rocket.ppt"], {"_id": "stick.jpg", "author": "Dr. House"}
"Dr. House":["stick.jpg"] ]
},
"year": {
"1968": ["dragon.doc", "timemachine"],
"1985": ["rocket.ppt"],
"2005":["stick.jpg"]
}
}
}; };
o.jio.get({"_id": "iall_indices.json"}, function (err, response) { o.spy(o, "value", o.fakeIndexA, "Check index file");
o.actualIndex = response; o.jio.get({"_id": "A"}, o.f);
deepEqual(o.actualIndex, o.fakeIndex, "Check index file");
});
o.tick(o); o.tick(o);
o.thisShouldBeTheAnswer = { o.thisShouldBeTheAnswer = {
...@@ -5035,19 +5078,6 @@ test ("AllDocs", function () { ...@@ -5035,19 +5078,6 @@ test ("AllDocs", function () {
o.jio.allDocs(o.f); o.jio.allDocs(o.f);
o.tick(o); o.tick(o);
o.thisShouldBeTheAnswer2 = {
"rows": [
{"id": "dragon.doc", "key": "dragon.doc", "value": {}, "doc": o.all1 },
{"id": "timemachine", "key": "timemachine", "value": {}, "doc": o.all2 },
{"id": "rocket.ppt", "key": "rocket.ppt", "value": {}, "doc": o.all3 },
{"id": "stick.jpg", "key": "stick.jpg", "value": {}, "doc": o.all4 }
],
"total_rows": 4
}
o.spy(o, "value", o.thisShouldBeTheAnswer2, "allDocs (include_docs)");
o.jio.allDocs({"include_docs":true}, o.f);
o.tick(o);
o.jio.stop(); o.jio.stop();
}); });
...@@ -5058,16 +5088,9 @@ test ("AllDocs Complex Queries", function () { ...@@ -5058,16 +5088,9 @@ test ("AllDocs Complex Queries", function () {
o.jio = JIO.newJio({ o.jio = JIO.newJio({
"type": "indexed", "type": "indexed",
"indices": [ "indices": [
{"name":"indexA", "fields":["director"]}, {"id":"A", "index": ["director"]},
{"name":"indexAB", "fields":["title","year"]} {"id":"B", "index": ["title", "year"]}
//,
//{"name":"indexABC", "fields":["title","year","director"]}
], ],
"field_types": {
"director": "string",
"title": "string",
"year": "number"
},
"sub_storage": { "sub_storage": {
"type": "local", "type": "local",
"username": "icomplex", "username": "icomplex",
...@@ -5085,148 +5108,153 @@ test ("AllDocs Complex Queries", function () { ...@@ -5085,148 +5108,153 @@ test ("AllDocs Complex Queries", function () {
"One flew over the Cuckoo's Nest", "Inception", "Godfellas" "One flew over the Cuckoo's Nest", "Inception", "Godfellas"
]; ];
o.years = [1994,1972,1974,1994,1966,1957,2008,1993,2003,1999,1980,2001, o.years = [1994,1972,1974,1994,1966,1957,2008,1993,2003,1999,1980,2001,
1975,2010,1990 1975,2010,1990];
];
o.director = ["Frank Darabont", "Francis Ford Coppola", o.director = ["Frank Darabont", "Francis Ford Coppola",
"Francis Ford Coppola", "Quentin Tarantino", "Sergio Leone", "Francis Ford Coppola", "Quentin Tarantino", "Sergio Leone",
"Sidney Lumet", "Christopher Nolan", "Steven Spielberg", "Sidney Lumet", "Christopher Nolan", "Steven Spielberg",
"Peter Jackson", "David Fincher", "Irvin Kershner", "Peter Jackson", "Peter Jackson", "David Fincher", "Irvin Kershner", "Peter Jackson",
"Milos Forman", "Christopher Nolan", " Martin Scorsese" "Milos Forman", "Christopher Nolan", " Martin Scorsese"
] ];
o.fakeIndexA = {
"_id": "A",
"indexing": ["director"],
"free": [],
"location": {},
"database": []
};
o.fakeIndexB = {
"_id": "B",
"indexing": ["title", "year"],
"free": [],
"location": {},
"database": []
};
for (i = 0; i < m; i += 1) { for (i = 0; i < m; i += 1) {
o.fakeDoc = {}; o.jio.put({
o.fakeDoc._id = ""+i; "_id": "" + i,
o.fakeDoc.title = o.titles[i]; "director": o.director[i],
o.fakeDoc.year = o.years[i]; "year": o.years[i],
o.fakeDoc.director = o.director[i]; "title": o.titles[i]
o.jio.put(o.fakeDoc); });
o.tmp = o.fakeIndexA.free.pop() || o.fakeIndexA.database.length;
o.fakeIndexA.database[o.tmp] = {"_id": "" + i, "director": o.director[i]};
o.fakeIndexA.location["" + i] = o.tmp;
o.tmp = o.fakeIndexB.free.pop() || o.fakeIndexB.database.length;
o.fakeIndexB.database[o.tmp] = {
"_id": "" + i,
"year": o.years[i],
"title": o.titles[i]
};
o.fakeIndexB.location["" + i] = o.tmp;
o.clock.tick(1000); o.clock.tick(1000);
} }
// o.clock.tick(1000);
// check index file
o.spy(o, "value", o.fakeIndexA, "Check index file");
o.jio.get({"_id": "A"}, o.f);
o.tick(o);
o.spy(o, "value", o.fakeIndexB, "Check index file");
o.jio.get({"_id": "B"}, o.f);
o.tick(o);
// response // response
o.allDocsResponse = {}; o.allDocsResponse = {};
o.allDocsResponse.rows = []; o.allDocsResponse.rows = [];
o.allDocsResponse.total_rows = 15; o.allDocsResponse.total_rows = m;
for (i = 0; i < m; i += 1) { for (i = 0; i < m; i += 1) {
o.allDocsResponse.rows.push({ o.allDocsResponse.rows.push({
"id": ""+i,
"key": ""+i,
"value": {}
});
};
// alldocs
o.jio.allDocs(function (e, r) {
var x = r.rows.sort(o.sortArrayById('id', true, parseInt));
deepEqual(
{"total_rows":r.total_rows,"rows":x}, o.allDocsResponse,
"AllDocs response generated from index"
);
});
o.clock.tick(1000);
// include docs
o.allDocsResponse2 = {};
o.allDocsResponse2.rows = [];
o.allDocsResponse2.total_rows = 15;
for (i = 0; i < m; i += 1) {
o.allDocsResponse2.rows.push({
"id": ""+i, "id": ""+i,
"key": ""+i, "key": ""+i,
"value": {}, "value": {},
"doc": localstorage.getItem(o.localpath+"/"+i) "doc": {
"_id": ""+i,
"title": o.titles[i],
"year": o.years[i],
"director": o.director[i]
}
}); });
}; }
o.response = JSON.parse(JSON.stringify(o.allDocsResponse));
for (i = 0; i < o.response.rows.length; i += 1) {
delete o.response.rows[i].doc;
}
// alldocs // alldocs
o.jio.allDocs({"include_docs":true}, function(e,r) { o.spy(o, "value", o.response, "AllDocs response generated from index");
var x = r.rows.sort(o.sortArrayById('id', true, parseInt)); o.jio.allDocs(o.f);
deepEqual( o.tick(o, 1000);
{"total_rows":r.total_rows,"rows":x}, o.allDocsResponse2,
"AllDocs response generated from index (include docs)"
);
});
o.clock.tick(1000);
// complex queries // complex queries
o.thisShouldBeTheAnswer4 = [ o.response = JSON.parse(JSON.stringify(o.allDocsResponse));
{"title": "Inception", "year": 2010}, i = 0;
{"title": "The Dark Knight", "year": 2008}, while (i < o.response.rows.length) {
{"title": "Lord of the Rings - Return of the King", "year": 2003}, if (o.response.rows[i].year < 1980) {
{"title": "Lord Of the Rings - Fellowship of the Ring", "year": 2001}, o.response.rows.splice(i, 1);
{"title": "Fight Club", "year": 1999} } else {
]; o.response.rows[i].value = {
o.spy(o, "value", o.thisShouldBeTheAnswer4, "year": o.response.rows[i].doc.year,
"title": o.response.rows[i].doc.title
}
delete o.response.rows[i].doc;
i += 1;
}
}
o.response.rows.sort(function (a, b) {
return a.value.year > b.value.year ? -1 :
a.value.year < b.value.year ? 1 : 0;
});
o.response.rows.length = 5;
o.response.total_rows = 5;
o.spy(o, "value", o.response,
"allDocs (complex queries year >= 1980, index used to do query)"); "allDocs (complex queries year >= 1980, index used to do query)");
o.jio.allDocs({ o.jio.allDocs({
"query":{ // "query":'(year: >= "1980" AND year: < "2000")',
// "query":'(year: >= "1980" AND year: < "2000")', "query": '(year: >= "1980")',
"query":'(year: >= "1980")', "limit": [0, 5],
"filter": { "sort_on": [['year', 'descending']],
"limit":[0,5], "select_list": ['title', 'year']
"sort_on":[['year','descending']],
"select_list":['title','year']
},
"wildcard_character":'%'
}
}, o.f); }, o.f);
o.tick(o); o.tick(o);
// complex queries // complex queries
o.thisShouldBeTheAnswer5 = [ o.spy(o, "value", {"total_rows": 0, "rows": []},
{"director": "Christopher Nolan", "year": 2010},
{"director": "Christopher Nolan", "year": 2008},
{"director": "Peter Jackson", "year": 2003},
{"director": "Peter Jackson", "year": 2001},
{"director": "David Fincher", "year": 1999}
];
o.spy(o, "value", o.thisShouldBeTheAnswer5,
"allDocs (complex queries year >= 1980, can't use index)"); "allDocs (complex queries year >= 1980, can't use index)");
o.jio.allDocs({ o.jio.allDocs({
"query":{ // "query":'(year: >= "1980" AND year: < "2000")',
// "query":'(year: >= "1980" AND year: < "2000")', "query": '(year: >= "1980")',
"query":'(year: >= "1980")', "limit": [0, 5],
"filter": { "sort_on": [['year','descending']],
"limit":[0,5], "select_list": ['director', 'year']
"sort_on":[['year','descending']],
"select_list":['director','year']
},
"wildcard_character":'%'
}
}, o.f); }, o.f);
o.tick(o); o.tick(o);
// empty query returns all // empty query returns all
o.thisShouldBeTheAnswer6 = [ o.response = JSON.parse(JSON.stringify(o.allDocsResponse));
{"title": "The Good, The Bad and The Ugly"}, i = 0;
{"title": "The Dark Knight"}, while (i < o.response.rows.length) {
{"title": "Star Wars Episode V"}, o.response.rows[i].value.title =
{"title": "Shawshank Redemption"}, o.response.rows[i].doc.title;
{"title": "Schindlers List"}, delete o.response.rows[i].doc;
{"title": "Pulp Fiction"}, i += 1;
{"title": "One flew over the Cuckoo's Nest"}, }
{"title": "Lord of the Rings - Return of the King"}, o.response.rows.sort(function (a, b) {
{"title": "Lord Of the Rings - Fellowship of the Ring"}, return a.value.title > b.value.title ? -1 :
{"title": "Inception"}, a.value.title < b.value.title ? 1 : 0;
{"title": "Godfellas"}, });
{"title": "Godfather 2"}, o.spy(o, "value", o.response,
{"title": "Godfather"},
{"title": "Fight Club"},
{"title": "12 Angry Men"}
];
o.spy(o, "value", o.thisShouldBeTheAnswer6,
"allDocs (empty query in complex query)"); "allDocs (empty query in complex query)");
o.jio.allDocs({ o.jio.allDocs({
"query":{ "sort_on":[['title','descending']],
"filter": { "select_list":['title']
"sort_on":[['title','descending']],
"select_list":['title']
},
"wildcard_character":'%'
}
}, o.f); }, o.f);
o.tick(o); o.tick(o);
......
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