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,8 +39,8 @@ ...@@ -39,8 +39,8 @@
<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>
...@@ -51,26 +51,24 @@ ...@@ -51,26 +51,24 @@
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'), "wildcard_character": $('#wildcard').attr('value'),
filter:{ "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"
* ...
* }],
* "sub_storage": <sub storage description>
* }
*
* Sent document metadata will be:
* index_titre_subject.json
* {
* "_id": "index_title_subject.json",
* "indexing": ["title", "subject"],
* "free": [0],
* "location": {
* "foo": 1,
* "bar": 2,
* ...
* }, * },
* "storage": [ * "database": [
* <sub storage description>, * {},
* {"_id": "foo", "title": "...", "subject": ...},
* {"_id": "bar", "title": "...", "subject": ...},
* ... * ...
* ] * ]
* } * }
* Index file will contain *
* index_year.json
* { * {
* "_id": "app-name_indices.json", * "_id": "index_year.json",
* "indexA": * "indexing": ["year"],
* "fieldA": { * "free": [1],
* "keyword_abc": ["some_id","some_other_id",...] * "location": {
* } * "foo": 0,
* }, * "bar": 2,
* "indexAB": { * ...
* "fieldA": {
* "keyword_abc": ["some_id"]
* }, * },
* "fieldB": { * "database": [
* "keyword_def": ["some_id"] * {"_id": "foo", "year": "..."},
* } * {},
* } * {"_id": "bar", "year": "..."},
* ...
* ]
* } * }
* NOTES:
* It may be difficult to "un-sort" multi-field indices, like
* indexAB, because all keywords will be listed regrardless
* of underlying field, so an index on author and year would produce
* two entries per record like:
* *
* "William Shakespeare":["id_Romeo_and_Juliet", "id_Othello"], * A put document will be indexed to the free location if exist, else it will be
* "1591":["id_Romeo_and_Juliet"], * indexed at the end of the database. The document id will be indexed, also, in
* "1603":["id_Othello"] * 'location' to quickly replace metadata.
* *
* So for direct lookups, this should be convient, but for other types * Only one or two loops are executed:
* of queries, it depends * - 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 || {};
that = my.basicStorage(spec, my);
priv.indices = spec.indices;
priv.field_types = spec.field_types;
priv.substorage_key = "sub_storage";
priv.substorage = spec[priv.substorage_key];
priv.index_indicator = spec.sub_storage.application_name || "index";
priv.index_suffix = priv.index_indicator + "_indices.json";
my.env = my.env || spec.env || {}; var error_dict = {
"Corrupted Index": {
"status": 24,
"statusText": "Corrupt",
"error": "corrupt",
"reason": "corrupted index database"
},
"Corrupted Metadata": {
"status": 24,
"statusText": "Corrupt",
"error": "corrupt",
"reason": "corrupted document"
},
"Not Found": {
"status": 404,
"statusText": "Not Found",
"error": "not_found",
"reason": "missing document"
},
"Conflict": {
"status": 409,
"statusText": "Conflicts",
"error": "conflicts",
"reason": "already exist"
},
"Different Index": {
"status": 40,
"statusText": "Check failed",
"error": "check_failed",
"reason": "incomplete database"
}
};
that.specToStore = function () { /**
var o = {}; * Generate a JIO Error Object
o[priv.substorage_key] = priv.substorage; *
o.env = my.env; * @method generateErrorObject
return o; * @param {String} name The error name
* @param {String} message The error message
* @param {String} [reason] The error reason
* @return {Object} A jIO error object
*/
function generateErrorObject(name, message, reason) {
if (!error_dict[name]) {
return {
"status": 0,
"statusText": "Unknown",
"error": "unknown",
"message": message,
"reason": reason || "unknown"
};
}
return {
"status": error_dict[name].status,
"statusText": error_dict[name].statusText,
"error": error_dict[name].error,
"message": message,
"reason": reason || error_dict[name].reason
}; };
}
/**
* Get the real type of an object
* @method type
* @param {Any} value The value to check
* @return {String} The value type
*/
function type(value) {
// returns "String", "Object", "Array", "RegExp", ...
return (/^\[object ([a-zA-Z]+)\]$/).exec(
Object.prototype.toString.call(value)
)[1];
}
/** /**
* Generate a new uuid * 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,610 +168,470 @@ jIO.addStorageType('indexed', function (spec, my) { ...@@ -98,610 +168,470 @@ jIO.addStorageType('indexed', function (spec, my) {
S4() + "-" + S4() + "-" +
S4() + "-" + S4() + "-" +
S4() + S4() + S4(); S4() + S4() + S4();
}; }
/** /**
* Get number of elements in object * A JSON Index manipulator
* @method getObjectSize *
* @param {object} obj The object to check * @class JSONIndex
* @return {number} size The amount of elements in the object * @constructor
*/ */
priv.getObjectSize = function (obj) { function JSONIndex(spec) {
var size = 0, key; var that = this;
for (key in obj) { spec = spec || {};
if (obj.hasOwnProperty(key)) {
size += 1;
}
}
return size;
};
/** /**
* Creates an empty indices array * The document id
* @method createEmptyIndexArray *
* @param {array} indices An array of indices (optional) * @property _id
* @return {object} The new index array * @type String
*/ */
priv.createEmptyIndexArray = function (indices) { that._id = spec._id;
var i, k, j = priv.indices.length, new_index,
new_index_object = {}, new_index_name, new_index_fields;
if (indices === undefined) { /**
for (i = 0; i < j; i += 1) { * The array with metadata key to index
new_index = priv.indices[i]; *
new_index_name = new_index.name; * @property _indexing
new_index_fields = new_index.fields; * @type Array
new_index_object[new_index_name] = {}; */
that._indexing = spec.indexing || [];
// loop index fields and add objects to hold value/id pairs /**
for (k = 0; k < new_index_fields.length; k += 1) { * The array of free location index
new_index_object[new_index_name][new_index_fields[k]] = {}; *
} * @property _free
} * @type Array
} * @default []
return new_index_object; */
}; that._free = spec.free || [];
/** /**
* Determine if a key/value pair exists in an object by VALUE * The dictionnary document id -> database index
* @method searchObjectByValue *
* @param {object} indexToSearch The index to search * @property _location
* @param {string} docid The document id to find * @type Object
* @param {string} passback The value that should be returned * @default {}
* @return {boolean} true/false
*/ */
priv.searchIndexByValue = function (indexToSearch, docid, passback) { that._location = spec.location || {};
var key, obj, prop;
for (key in indexToSearch) { /**
if (indexToSearch.hasOwnProperty(key)) { * The database array containing document metadata
obj = indexToSearch[key]; *
for (prop in obj) { * @property _database
if (obj[prop] === docid) { * @type Array
return passback === "bool" ? true : key; * @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) {
if (meta.hasOwnProperty(k)) {
if (underscored_meta_re.test(k)) {
if (k === "_id") {
needed_meta[k] = meta[k];
}
} else if (that._indexing_object[k]) {
needed_meta[k] = meta[k];
ok = true;
}
}
}
if (ok) {
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();
that._database[k] = needed_meta;
that._location[meta._id] = k;
} else {
that._database.push(needed_meta);
that._location[meta._id] = that._database.length - 1;
} }
return true;
} }
if (typeof that._location[meta._id] === "number") {
that.remove(meta);
} }
return false; return false;
}; };
/** /**
* Get element position in array * Removes a metadata object from the database if exist
* @method getPositionInArray *
* @param {object} indices The index file * @method remove
* @param {object} indices The index file * @param {Object} meta The metadata to remove
* @returns {number} i Position of element in array
*/ */
priv.getPositionInArray = function (element, array) { that.remove = function (meta) {
var i, l = array.length; if (typeof meta._id !== "string") {
for (i = 0; i < l; i += 1) { throw new TypeError("Corrupted Metadata");
if (array[i] === element) {
return i;
} }
if (typeof that._location[meta._id] !== "number") {
throw new ReferenceError("Not Found");
} }
return null; that._database[that._location[meta._id]] = null;
that._free.push(that._location[meta._id]);
delete that._location[meta._id];
}; };
/** /**
* Find id in indices * Checks if the index database document is correct
* @method isDocidInIndex *
* @param {object} indices The file containing the indeces * @method check
* @param {object} doc The document which should be added to the index
* @return {boolean} true/false
*/ */
priv.isDocidInIndex = function (indices, doc) { that.check = function () {
var index, i, j, label, l = priv.indices.length; var id, database_meta;
if (typeof that._id !== "string" ||
// loop indices that._id === "" ||
for (i = 0; i < l; i += 1) { type(that._free) !== "Array" ||
index = {}; type(that._indexing) !== "Array" ||
index.reference = priv.indices[i]; type(that._location) !== "Object" ||
index.reference_size = index.reference.fields.length; type(that._database) !== "Array" ||
index.current = indices[index.reference.name]; that._indexing.length === 0) {
throw new TypeError("Corrupted Index");
for (j = 0; j < index.reference_size; j += 1) { }
label = index.reference.fields[j]; for (id in that._location) {
index.current_size = priv.getObjectSize(index.current[label]); if (that._location.hasOwnProperty(id)) {
database_meta = that._database[that._location[id]];
// check for existing entries to remove (put-update) if (type(database_meta) !== "Object" ||
if (index.current_size > 0) { database_meta._id !== id) {
if (priv.searchIndexByValue(index.current[label], doc._id, "bool")) { throw new TypeError("Corrupted Index");
return true;
}
} }
} }
} }
return false;
}; };
/** that.equals = function (json_index) {
* Clean up indexes when removing a file function equalsDirection(a, b) {
* @method cleanIndices var k;
* @param {object} indices The file containing the indeces for (k in a._location) {
* @param {object} doc The document which should be added to the index if (a._location.hasOwnProperty(k)) {
* @return {object} indices The cleaned up file if (b._location[k] === undefined ||
*/ JSON.stringify(b._database[b._location[k]]) !==
priv.cleanIndices = function (indices, doc) { JSON.stringify(a._database[a._location[k]])) {
var i, j, k, index, key, label, l = priv.indices.length; return false;
// loop indices (indexA, indexAB...)
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];
// 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];
}
}
} }
} }
} }
return indices; return true;
};
/**
* Adds entries to indices
* @method createEmptyIndexArray
* @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 (!equalsDirection(that, json_index)) {
return false;
} }
if (index.current[label][value] === undefined) { if (!equalsDirection(json_index, that)) {
index.current[label][value] = []; return false;
} }
// add a new entry return true;
index.current[label][value].push(doc._id); };
that.checkDocument = function (doc) {
var i, key, db_doc;
if (typeof that._location[doc._id] !== "number" ||
(db_doc = that._database(that._location[doc._id])._id) !== doc._id) {
throw new TypeError("Different Index");
} }
for (i = 0; i < that._indexing.length; i += 1) {
key = that._indexing[i];
if (doc[key] !== db_doc[key]) {
throw new TypeError("Different Index");
} }
} }
return indices;
}; };
/** /**
* Check available indices to find the best one. * Recreates database indices and remove free space
* TODOS: NOT NICE, redo *
* @method findBestIndexForQuery * @method repair
* @param {object} syntax of query
* @returns {object} response The query object constructed from Index file
*/ */
priv.findBestIndexForQuery = function (syntax) { that.repair = function () {
var i, j, k, l, n, p, o, element, key, block, var i = 0, meta;
search_ids, use_index = [], select_ids = {}, index, query_param, that._free = [];
current_query, current_query_size; that._location = {};
if (type(that._database) !== "Array") {
// try to parse into object that._database = [];
if (syntax.query !== undefined) { }
current_query = jIO.ComplexQueries.parse(syntax.query); while (i < that._database.length) {
} else { meta = that._database[i];
current_query = {}; if (type(meta) === "Object" &&
current_query_size = 0; typeof meta._id === "string" && meta._id !== "" &&
} !that._location[meta._id]) {
that._location[meta._id] = i;
// loop indices i += 1;
for (i = 0; i < priv.indices.length; i += 1) {
search_ids = [];
block = false;
index = {};
index.reference = priv.indices[i];
index.reference_size = index.reference.fields.length;
if (current_query_size !== 0) {
// rebuild search_ids for iteration
if (current_query.query_list === undefined) {
search_ids.push(current_query.id);
} else { } else {
for (j = 0; j < current_query.query_list.length; j += 1) { that._database.splice(i, 1);
if (priv.getPositionInArray(current_query.query_list[j].id,
search_ids) === null) {
search_ids.push(current_query.query_list[j].id);
}
}
}
// loop search ids and find matches in index
for (k = 0; k < search_ids.length; k += 1) {
query_param = search_ids[0];
for (l = 0; l < index.reference_size; l += 1) {
if (query_param === index.reference.fields[l]) {
search_ids.splice(
priv.getPositionInArray(query_param, search_ids),
1
);
}
}
} }
} }
};
// rebuild select_ids /**
for (o = 0; o < syntax.filter.select_list.length; o += 1) { * Returns the serialized version of this object (not cloned)
element = syntax.filter.select_list[o]; *
select_ids[element] = true; * @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
};
};
// search_ids empty = all needed search fields found on index that.check();
if (search_ids.length === 0) { that._indexing_object = {};
p = priv.getObjectSize(select_ids); that._indexing.forEach(function (meta_key) {
if (p === 0) { that._indexing_object[meta_key] = true;
use_index.push({
"name": index.reference.name,
"search": true,
"results": false
});
} else {
for (n = 0; n < index.reference_size; n += 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({ * The JIO index storage constructor
"name": index.reference.name, */
"search": true, function indexStorage(spec, my) {
"results": true var that, priv = {};
});
} that = my.basicStorage(spec, my);
}
} priv.indices = spec.indices;
} priv.sub_storage = spec.sub_storage;
return use_index;
// Overrides
that.specToStore = function () {
return {
"indices": priv.indices,
"sub_storage": priv.sub_storage
};
}; };
/** /**
* Converts the indices file into an object usable by complex queries * Return the similarity percentage (1 >= p >= 0) between two index lists.
* @method constructQueryObject *
* @param {object} indices The index file * @method similarityPercentage
* @returns {object} response The query object constructed from Index file * @param {Array} list_a An index list
* @param {Array} list_b Another index list
* @return {Number} The similarity percentage
*/ */
priv.constructQueryObject = function (indices, query_syntax) { priv.similarityPercentage = function (list_a, list_b) {
var j, k, l, m, n, use_index, index, var ai, bi, count = 0;
index_name, field_names, field, key, element, for (ai = 0; ai < list_a.length; ai += 1) {
query_index, query_object = [], field_name, for (bi = 0; bi < list_b.length; bi += 1) {
entry; if (list_a[ai] === list_b[bi]) {
count += 1;
// 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 count / (list_a.length > list_b.length ?
list_a.length : list_b.length);
};
/**
* Select the good index to use according to a select list.
*
* @method selectIndex
* @param {Array} select_list An array of strings
* @return {Number} The index index
*/
priv.selectIndex = function (select_list) {
var i, tmp, selector = {"index": 0, "similarity": 0};
for (i = 0; i < priv.indices.length; i += 1) {
tmp = priv.similarityPercentage(select_list,
priv.indices[i].index);
if (tmp > selector.similarity) {
selector.index = i;
selector.similarity = tmp;
} }
} }
return query_object; return selector.index;
}; };
/** /**
* Build the alldocs response from the index file (overriding substorage) * Get a database
* @method allDocsResponseFromIndex *
* @param {object} command The JIO command * @method getIndexDatabase
* @param {boolean} include_docs Whether to also supply the document * @param {Object} option The command option
* @param {object} option The options set for this method * @param {Number} number The location in priv.indices
* @returns {object} response The allDocs response * @param {Function} callback The callback
*/ */
priv.allDocsResponseFromIndex = function (indices, include_docs, option) { priv.getIndexDatabase = function (option, number, callback) {
var i, j, k, m, n = 0, l = priv.indices.length, that.addJob(
index, key, obj, prop, found, file, label, "get",
unique_count = 0, unique_docids = [], all_doc_response = {}, priv.indices[number].sub_storage || priv.sub_storage,
success = function (content) { {"_id": priv.indices[number].id},
file = { value: {} }; option,
file.id = unique_docids[n]; function (response) {
file.key = unique_docids[n]; callback(new JSONIndex(response));
file.doc = content;
all_doc_response.rows.push(file);
// async counter, must be in callback
n += 1;
if (n === unique_count) {
that.success(all_doc_response);
}
}, },
error = function () { function (err) {
that.error({ if (err.status === 404) {
"status": 404, callback(new JSONIndex({
"statusText": "Not Found", "_id": priv.indices[number].id,
"error": "not_found", "indexing": priv.indices[number].index
"message": "Cannot find the document", }));
"reason": "Cannot get a document from substorage"
});
return; return;
};
// 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];
// a lot of loops, not sure this is the fastest way
// loop index fields
for (j = 0; j < index.reference_size; j += 1) {
label = index.reference.fields[j];
index.current_field = index.current[label];
index.current_size = priv.getObjectSize(index.current_field);
// 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;
}
}
}
} }
err.message = "Unable to get index database.";
that.error(err);
} }
);
};
/**
* Gets a list containing all the databases set in the storage description.
*
* @method getIndexDatabaseList
* @param {Object} option The command option
* @param {Function} callback The result callback(database_list)
*/
priv.getIndexDatabaseList = function (option, callback) {
var i, count = 0, callbacks = {}, response_list = [];
callbacks.error = function (index) {
return function (err) {
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);
} }
};
// construct allDocs response };
all_doc_response.total_rows = unique_count; for (i = 0; i < priv.indices.length; i += 1) {
all_doc_response.rows = [];
for (m = 0; m < unique_count; m += 1) {
// include_docs
if (include_docs) {
that.addJob( that.addJob(
"get", "get",
priv.substorage, priv.indices[i].sub_storage || priv.sub_storage,
unique_docids[m], {"_id": priv.indices[i].id},
option, option,
success, callbacks.success(i),
error callbacks.error(i)
); );
} else {
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;
} }
};
/**
* 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
);
} }
} }
}; };
/** /**
* Post document to substorage and create/update index file(s) * A generic request method which delegates the request to the sub storage.
* @method post * On response, it will index the document from the request and update all
* @param {object} command The JIO command * the databases.
* @param {string} source The source of the function call *
* @method genericRequest
* @param {Command} command The JIO command
* @param {Function} method The request method
*/ */
priv.postOrPut = function (command, source) { priv.genericRequest = function (command, method) {
var f = {}, indices, doc; var doc = command.cloneDoc(), option = command.cloneOption();
doc = command.cloneDoc();
if (typeof doc._id !== "string") {
doc._id = priv.generateUuid();
}
f.getIndices = function () {
var option = command.cloneOption();
that.addJob( that.addJob(
"get", method,
priv.substorage, priv.sub_storage,
{"_id": priv.index_suffix}, doc,
option, option,
function (response) { function (response) {
indices = response; switch (method) {
f.postDocument("put"); case "post":
}, case "put":
function (err) { case "remove":
switch (err.status) { doc._id = response.id;
case 404: priv.getIndexDatabaseList(option, function (database_list) {
if (source !== 'PUTATTACHMENT') { var i;
indices = priv.createEmptyIndexArray(); switch (method) {
f.postDocument("post"); case "post":
} else { case "put":
that.error({ for (i = 0; i < database_list.length; i += 1) {
"status": 404, database_list[i].put(doc);
"statusText": "Not Found", }
"error": "not found", break;
"message": "Document not found", case "remove":
"reason": "Document not found" for (i = 0; i < database_list.length; i += 1) {
}); database_list[i].remove(doc);
return;
} }
break; break;
default: default:
err.message = "Cannot retrieve index array";
that.error(err);
break; break;
} }
} priv.storeIndexDatabaseList(database_list, option, function () {
); that.success({"ok": true, "id": doc._id});
};
f.postDocument = function (index_update_method) {
if (priv.isDocidInIndex(indices, doc) && source === 'POST') {
// POST the document already exists
that.error({
"status": 409,
"statusText": "Conflicts",
"error": "conflicts",
"message": "Cannot create a new document",
"reason": "Document already exists"
}); });
return;
}
if (source !== 'PUTATTACHMENT') {
indices = priv.updateIndices(indices, doc);
}
that.addJob(
source === 'PUTATTACHMENT' ? "putAttachment" : "post",
priv.substorage,
doc,
command.cloneOption(),
function () {
if (source !== 'PUTATTACHMENT') {
f.sendIndices(index_update_method);
} else {
that.success({
"ok": true,
"id": doc._id,
"attachment": doc._attachment
});
}
},
function (err) {
switch (err.status) {
case 409:
// file already exists
if (source !== 'PUTATTACHMENT') {
f.sendIndices(index_update_method);
} else {
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');
}; };
/** /**
...@@ -710,7 +640,7 @@ jIO.addStorageType('indexed', function (spec, my) { ...@@ -710,7 +640,7 @@ jIO.addStorageType('indexed', function (spec, my) {
* @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');
}; };
/** /**
...@@ -719,7 +649,7 @@ jIO.addStorageType('indexed', function (spec, my) { ...@@ -719,7 +649,7 @@ jIO.addStorageType('indexed', function (spec, my) {
* @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');
}; };
/** /**
...@@ -728,18 +658,7 @@ jIO.addStorageType('indexed', function (spec, my) { ...@@ -728,18 +658,7 @@ jIO.addStorageType('indexed', function (spec, my) {
* @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);
}
);
}; };
/** /**
...@@ -748,18 +667,7 @@ jIO.addStorageType('indexed', function (spec, my) { ...@@ -748,18 +667,7 @@ jIO.addStorageType('indexed', function (spec, my) {
* @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);
}
);
}; };
/** /**
...@@ -768,193 +676,179 @@ jIO.addStorageType('indexed', function (spec, my) { ...@@ -768,193 +676,179 @@ jIO.addStorageType('indexed', function (spec, my) {
* @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
});
for (i = 0; i < response.rows.length; i += 1) {
db.put(response.rows[i].doc);
}
db_list[index] = db;
if (just_check) {
priv.getIndexDatabase(option, index, function (current_db) {
if (db.equals(current_db)) {
return that.success({"ok": true, "_id": command.getDocId()});
}
return that.error(generateErrorObject(
"Different Index",
"Check failed",
"corrupt index database"
));
});
} else { } else {
indices = priv.cleanIndices(response, doc); priv.storeIndexDatabaseList(db_list, {}, function () {
// store update index file that.success({"ok": true, "_id": command.getDocId()});
that.addJob( });
"put", }
priv.substorage,
indices,
command.cloneOption(),
function () {
// remove actual document
f.removeDocument('doc');
}, },
function (err) { function (err) {
err.message = "Cannot save index file"; err.message = "Unable to repair the index database";
that.error(err); that.error(err);
} }
); );
}
},
function () {
that.error({
"status": 404,
"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) {
for (i = 0; i < database_list.length; i += 1) {
try {
database_list[i].checkDocument(response);
} catch (e) {
return that.error(generateErrorObject(
e.message,
"Check failed",
"corrupt index database"
));
}
}
that.success({"_id": command.getDocId(), "ok": true});
} else { } else {
indices = priv.cleanIndices(response, doc); for (i = 0; i < database_list.length; i += 1) {
// store update index file database_list[i].put(response);
that.addJob(
"put",
priv.substorage,
indices,
command.cloneOption(),
function () {
// remove actual document
f.removeDocument('doc');
},
function (err) {
err.message = "Cannot save index file";
that.error(err);
} }
); 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)); }
} }
option.select_list = option.select_list || [];
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];
} }
query_response = jIO.ComplexQueries.query(query_syntax, query_object); delete value._id;
that.success(query_response); 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) { }, mkSimpleQuery = function (key, value, operator) {
return {type:'simple',operator:'=',id:id,value:value}; return {"type": "simple", "operator": "=", "key": key, "value": value};
};
var mkNotQuery = function (query) { }, mkNotQuery = function (query) {
if (query.operator === 'NOT') { if (query.operator === "NOT") {
return query.query_list[0]; 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) { }, mkComplexQuery = function (operator, query_list) {
var i,query_list2 = []; var i, query_list2 = [];
for (i=0; i<query_list.length; ++i) { for (i = 0; i < query_list.length; i += 1) {
if (query_list[i].operator === operator) { if (query_list[i].operator === operator) {
query_list2 = arrayExtend(query_list2,query_list[i].query_list); query_list2 = arrayExtend(query_list2, query_list[i].query_list);
} else { } else {
query_list2.push(query_list[i]); query_list2.push(query_list[i]);
} }
} }
return {type:'complex',operator:operator,query_list:query_list2}; return {type:"complex",operator:operator,query_list:query_list2};
};
var simpleQuerySetId = function (query, id) { }, simpleQuerySetKey = function (query, key) {
var i; var i;
if (query.type === 'complex') { if (query.type === "complex") {
for (i = 0; i < query.query_list.length; ++i) { for (i = 0; i < query.query_list.length; ++i) {
simpleQuerySetId (query.query_list[i],id); simpleQuerySetKey (query.query_list[i],key);
} }
return true; return true;
} }
if (query.type === 'simple' && !query.id) { if (query.type === "simple" && !query.key) {
query.id = id; query.key = key;
return true; return true;
} }
return false; return false;
}; },
var error_offsets = []; error_offsets = [],
var error_lookaheads = []; error_lookaheads = [],
var error_count = 0; error_count = 0,
var result; result;
if ( ( error_count = __##PREFIX##parse( string, error_offsets, error_lookaheads ) ) > 0 ) {
if ((error_count = __##PREFIX##parse(string, error_offsets, error_lookaheads)) > 0) {
var i; var i;
for (i = 0; i < error_count; ++i) { for (i = 0; i < error_count; i += 1) {
throw new Error ( "Parse error near \"" + throw new Error("Parse error near \"" +
string.substr ( error_offsets[i] ) + string.substr(error_offsets[i]) +
"\", expecting \"" + "\", expecting \"" +
error_lookaheads[i].join() + "\"" ); 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) {
if (itemMatchesQuery (item, query_list[i])) {
return true;
}
}
return false;
},
'NOT': function (item, query_list) {
return !itemMatchesQuery(item, query_list[0]);
}
},
convertToRegexp = function (string) {
return subString('^' + string.replace(
new RegExp(
'([\\{\\}\\(\\)\\^\\$\\&\\.\\*\\?\\\/\\+\\|\\[\\]\\-\\\\])'.
replace (wildcard_character?
'\\'+wildcard_character:undefined,''),
'g'
),
'\\$1'
) + '$',(wildcard_character||undefined), '.*');
},
subString = function (string, substring, newsubstring) {
var res = '', i = 0;
if (substring === undefined) {
return string;
}
while (1) {
var tmp = string.indexOf(substring,i);
if (tmp === -1) {
break;
}
for (; i < tmp; ++i) {
res += string[i];
}
res += newsubstring;
i += substring.length;
}
for (; i<string.length; ++i) {
res += string[i];
}
return res;
},
itemMatchesQuery = function (item, query_object) {
var i;
if (query_object.type === 'complex') {
return operator_actions[query_object.operator](
item, query_object.query_list
);
} else {
if (query_object.id) {
if (typeof item[query_object.id] !== 'undefined') {
return operator_actions[query_object.operator](
item[query_object.id], query_object.value
);
} else { } else {
return false; i += 1;
} }
} else {
return true;
} }
if (option.sort_on) {
Query.sortOn(option.sort_on, item_list);
} }
}, if (option.limit) {
select = function (list, select_list) { item_list.splice(0, option.limit[0]);
var i; if (option.limit[1]) {
if (select_list.length === 0) { item_list.splice(option.limit[1]);
return;
}
for (i=0; i<list.length; ++i) {
var list_value = {}, k;
for (k=0; k<select_list.length; ++k) {
list_value[select_list[k]] =
list[i][select_list[k]];
} }
list[i] = list_value;
} }
}, Query.filterListSelect(option.select_list || [], item_list);
sortFunction = function (key, asc) {
if (asc === '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; * Test if an item matches this query
*
* @method match
* @param {Object} item The object to test
* @return {Boolean} true if match, false otherwise
*/
that.match = function (item, wildcard_character) {
return true;
}; };
},
mergeList = function (list, list_to_merge, index) { /**
var i,j; * The recursive parser.
for (i = index,j = 0; i < list_to_merge.length + index; ++i, ++j) { *
list[i] = list_to_merge[j]; * @method recParse
} * @private
}, * @param {Object} object The object shared in the parse process
sort = function (list, sort_list) { * @param {Object} options Some options usable in the parseMethods
var i, tmp, key, asc, sortAndMerge = function() { * @return {Any} The parser result
sort(tmp,sort_list.slice(1)); */
mergeList(list,tmp,i-tmp.length); function recParse(object, option) {
tmp = [list[i]]; var i, query = object.parsed;
if (query.type === "complex") {
for (i = 0; i < query.query_list.length; i += 1) {
object.parsed = query.query_list[i];
recParse(object, option);
query.query_list[i] = object.parsed;
}
object.parsed = query;
that.onParseComplexQuery(object, option);
} else if (query.type === "simple") {
that.onParseSimpleQuery(object, option);
}
}
/**
* Browse the Query in deep calling parser method in each step.
*
* `onParseStart` is called first, on end `onParseEnd` is called.
* It starts from the simple queries at the bottom of the tree calling the
* parser method `onParseSimpleQuery`, and go up calling the
* `onParseComplexQuery` method.
*
* @method parse
* @param {Object} option Any options you want (except 'parsed')
* @return {Any} The parse result
*/
that.parse = function (option) {
var object;
object = {"parsed": JSON.parse(JSON.stringify(that.serialized()))};
that.onParseStart(object, option);
recParse(object, option);
that.onParseEnd(object, option);
return object.parsed;
}; };
if (list.length < 2) {
return; /**
} * Called before parsing the query. Must be overridden!
if (sort_list.length === 0) { *
return; * @method onParseStart
* @param {Object} object The object shared in the parse process
* @param {Object} option Some option gave in parse()
*/
that.onParseStart = emptyFunction;
/**
* Called when parsing a simple query. Must be overridden!
*
* @method onParseSimpleQuery
* @param {Object} object The object shared in the parse process
* @param {Object} option Some option gave in parse()
*/
that.onParseSimpleQuery = emptyFunction;
/**
* Called when parsing a complex query. Must be overridden!
*
* @method onParseComplexQuery
* @param {Object} object The object shared in the parse process
* @param {Object} option Some option gave in parse()
*/
that.onParseComplexQuery = emptyFunction;
/**
* Called after parsing the query. Must be overridden!
*
* @method onParseEnd
* @param {Object} object The object shared in the parse process
* @param {Object} option Some option gave in parse()
*/
that.onParseEnd = emptyFunction;
/**
* Convert this query to a parsable string.
*
* @method toString
* @return {String} The string version of this query
*/
that.toString = function () {
return "";
};
/**
* Convert this query to an jsonable object in order to be remake thanks to
* QueryFactory class.
*
* @method serialized
* @return {Object} The jsonable object
*/
that.serialized = function () {
return undefined;
};
}, {"static_methods": {
/**
* Filter a list of items, modifying them to select only wanted keys.
*
* @method filterListSelect
* @static
* @param {Array} select_option Key list to keep
* @param {Array} list The item list to filter
*/
"filterListSelect": function (select_option, list) {
var i, j, new_item;
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]];
}
for (j in new_item) {
if (new_item.hasOwnProperty(j)) {
list[i] = new_item;
break;
} }
key = sort_list[0][0];
asc = sort_list[0][1];
list.sort (sortFunction (key,asc));
tmp = [list[0]];
for (i = 1; i < list.length; ++i) {
if (tmp[0][key] === list[i][key]) {
tmp.push(list[i]);
} else {
sortAndMerge();
} }
} }
sortAndMerge();
}, },
limit = function (list, limit_list) {
var i; /**
if (typeof limit_list[0] !== 'undefined') { * Sort a list of items, according to keys and directions.
if (typeof limit_list[1] !== 'undefined') { *
if (list.length > limit_list[1] + limit_list[0]) { * @method sortOn
list.length = limit_list[1] + limit_list[0]; * @static
} * @param {Array} sort_on_option List of couples [key, direction]
list.splice(0,limit_list[0]); * @param {Array} list The item list to sort
} else { */
list.length = limit_list[0]; "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]
));
} }
}, },
////////////////////////////////////////////////////////////
result_list = [], result_list_tmp = [], j; /**
object_list = object_list || []; * Parse a text request to a json query object tree
if (query.query === undefined) { *
result_list = object_list; * @method parseStringToObject
} else { * @static
for (j=0; j<object_list.length; ++j) { * @param {String} string The string to parse
if ( itemMatchesQuery ( * @return {Object} The json query tree
object_list[j], scope.ComplexQueries.parse (query.query) */
)) { "parseStringToObject": parseStringToObject,
result_list.push(object_list[j]);
} /**
} * Convert a search text to a regexp.
} *
if (query.filter) { * @method convertStringToRegExp
select(result_list,query.filter.select_list || []); * @static
sort(result_list,query.filter.sort_on || []); * @param {String} string The string to convert
limit(result_list,query.filter.limit || []); * @param {String} [wildcard_character=undefined] The wildcard chararter
} * @return {RegExp} The search text regexp
return result_list; */
} "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.push(query.operator);
});
str_list.length -= 1;
str_list.push(")");
return str_list.join(" ");
} }
str_list.length --; if (query.type === "simple") {
str_list.push ( ')' ); return query.id + (query.id ? ": " : "") + (query.operator || "=") + ' "' +
return str_list.join(' '); query.value + '"';
} else if (query.type === 'simple') {
return query.id + (query.id?': ':'') + query.operator + ' "' + query.value + '"';
} }
return query; 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();
...@@ -4336,13 +4327,9 @@ test ("Post", function () { ...@@ -4336,13 +4327,9 @@ test ("Post", function () {
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": {
"findMeA": "string",
"findMeB": "string"
},
"sub_storage": { "sub_storage": {
"type": "local", "type": "local",
"username": "ipost", "username": "ipost",
...@@ -4351,39 +4338,49 @@ test ("Post", function () { ...@@ -4351,39 +4338,49 @@ test ("Post", function () {
}); });
// 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": { "database": [
"keyword_def":["some_id"] {"_id": "some_id", "title": "My Title"}
} ]
};
o.spy(o, "value", o.fakeIndexA, "Check index file");
o.jio.get({"_id": "A"}, o.f);
o.tick(o);
o.fakeIndexB = {
"_id": "B",
"indexing": ["title", "year"],
"free": [],
"location": {
"some_id": 0
}, },
"indexA": { "database": [
"findMeA": { {"_id": "some_id", "title": "My Title", "year": 2000}
"keyword_abc":["some_id"] ]
}
}
}; };
o.jio.get({"_id": "ipost_indices.json"}, function (err, response) { o.spy(o, "value", o.fakeIndexB, "Check index file");
o.actualIndex = response; o.jio.get({"_id": "B"}, o.f);
deepEqual(o.actualIndex, o.fakeIndex, "Check index file");
});
o.tick(o); o.tick(o);
// post with escapable characters // post with escapable characters
...@@ -4413,13 +4410,9 @@ test ("Put", function(){ ...@@ -4413,13 +4410,9 @@ test ("Put", 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": "iput", "username": "iput",
...@@ -4434,89 +4427,64 @@ test ("Put", function(){ ...@@ -4434,89 +4427,64 @@ 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": { "database": [{"_id": "put1", "author": "John Doe"}]
"author": {
"John Doe": ["put1"]
},
"year": {}
},
"_id": "iput_indices.json"
}; };
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");
...@@ -4524,26 +4492,14 @@ test ("Put", function(){ ...@@ -4524,26 +4492,14 @@ test ("Put", function(){
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
...@@ -4554,25 +4510,152 @@ test ("Put", function(){ ...@@ -4554,25 +4510,152 @@ test ("Put", function(){
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);
}, o.tick(o);
"indexAB": {
"author": { o.jio.stop();
"Jane Doe": ["put1"] });
},
"year": { test("Check & Repair", function () {
"1912": ["put1"] 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"
} }
}, });
"_id": "iput_indices.json"
o.fakeIndexA = {
"_id": "A",
"indexing": ["director"],
"free": [],
"database": []
};
o.fakeIndexB = {
"_id": "B",
"indexing": ["year"],
"free": [],
"database": []
}; };
o.jio.get({"_id": "iput_indices.json"}, function (err, response) {
o.actualIndex = response; for (i = 0; i < 10; i += 1) {
deepEqual(o.actualIndex, o.fakeIndex, "Check index file"); 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.tick(o);
...@@ -4589,13 +4672,9 @@ test ("PutAttachment", function(){ ...@@ -4589,13 +4672,9 @@ test ("PutAttachment", 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": "iputatt", "username": "iputatt",
...@@ -4706,13 +4785,9 @@ test ("Get", function(){ ...@@ -4706,13 +4785,9 @@ test ("Get", 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": "iget", "username": "iget",
...@@ -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"] o.spy(o, "value", o.fakeIndexA, "Check index file");
o.jio.get({"_id": "A"}, o.f);
o.tick(o);
o.fakeIndexB = {
"_id": "B",
"indexing": ["year"],
"free": [0],
"location": {
"removeAlso": 1
}, },
"author": { "database": [null, {"_id": "removeAlso", "year": "2525"}]
"Martin Mustermann": ["removeAlso"]
}
}
}; };
o.jio.get({"_id": "irem_indices.json"},function(err, response){ o.spy(o, "value", o.fakeIndexB, "Check index file");
o.actualIndex = response; o.jio.get({"_id": "B"}, o.f);
deepEqual(o.actualIndex, o.fakeIndex, "Check index file");
});
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
...@@ -4951,13 +5007,9 @@ test ("AllDocs", function () { ...@@ -4951,13 +5007,9 @@ test ("AllDocs", 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": "iall", "username": "iall",
...@@ -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")',
"filter": { "limit": [0, 5],
"limit":[0,5], "sort_on": [['year', 'descending']],
"sort_on":[['year','descending']], "select_list": ['title', 'year']
"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")',
"filter": { "limit": [0, 5],
"limit":[0,5], "sort_on": [['year','descending']],
"sort_on":[['year','descending']], "select_list": ['director', 'year']
"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":{
"filter": {
"sort_on":[['title','descending']], "sort_on":[['title','descending']],
"select_list":['title'] "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