Commit 220c1965 authored by Sven Franck's avatar Sven Franck

Complex Queries: added files, updated localstorage, makefile, tests

parent 94eb65b2
OUT = jio.js # dir
UGLY_OUT = jio.min.js JIO_DIR = src/jio
STORAGE_DIR = src/jio.storage
QUERIES_DIR = src/queries
JSCC_DIR = ~/modules/jscc
# files
JIO = jio.js
JIO_MIN = jio.min.js
COMPLEX = complex_queries.js
COMPLEX_MIN = complex_queries.min.js
PARSER_PAR = $(QUERIES_DIR)/parser.par
PARSER_OUT = $(QUERIES_DIR)/parser.js
# using rhino
JSCC_CMD = rhino $(JSCC_DIR)/jscc.js -t $(JSCC_DIR)/driver_web.js_
# npm install jslint # npm install jslint
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
# npm install uglify-js # 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)
FILE_DIR = src/jio
STORAGE_DIR = src/jio.storage auto: compile build lint
build: concat uglify
# The order is important! # The order is important!
CONCAT_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
STORAGE_NAMES = * CONCAT_STORAGE_NAMES = *
CONCAT_QUERIES_NAMES = begin parser-begin parser parser-end serializer query 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_FILES = $(CONCAT_NAMES:%=$(FILE_DIR)/%.js) CONCAT_QUERIES_FILES = $(CONCAT_QUERIES_NAMES:%=$(QUERIES_DIR)/%.js)
LINT_FILES = $(LINT_NAMES:%=$(FILE_DIR)/%.js) $(STORAGE_NAMES:%=$(STORAGE_DIR)/%.js) CONCAT_JIO_FILES = $(CONCAT_JIO_NAMES:%=$(JIO_DIR)/%.js)
LINT_FILES = $(LINT_NAMES:%=$(JIO_DIR)/%.js) $(CONCAT_STORAGE_NAMES:%=$(STORAGE_DIR)/%.js)
auto: build lint # build parser.js
build: concat uglify compile:
$(JSCC_CMD) -o $(PARSER_OUT) $(PARSER_PAR)
# concat source FILES to build jio.js # concat source files into jio.js and complex-queries.js
concat: concat:
cat $(CONCAT_FILES) > "$(OUT)" cat $(CONCAT_JIO_FILES) > "$(JIO)"
cat $(CONCAT_QUERIES_FILES) > "$(COMPLEX)"
# uglify jio.js to build jio.min.js # uglify into jio.min.js and complex.min.js
uglify: uglify:
$(UGLIFY_CMD) "$(OUT)" > "$(UGLY_OUT)" $(UGLIFY_CMD) "$(JIO)" > "$(JIO_MIN)"
$(UGLIFY_CMD) "$(COMPLEX)" > "$(COMPLEX_MIN)"
# lint all files in FILES and STORAGE_FILES # lint all files in JIO and STORAGE and QUERIES DIR
# command: jslint [options] file # command: jslint [options] file
# [options] are defined at the top of the source file: # [options] are defined at the top of the source file:
# Example: # Example:
...@@ -40,5 +60,8 @@ clean: ...@@ -40,5 +60,8 @@ clean:
find -name '*~' -delete find -name '*~' -delete
realclean: realclean:
rm -f "$(OUT)" rm -f "$(JIO)"
rm -f "$(UGLY_OUT)" rm -f "$(JIO_MIN)"
rm -f "$(COMPLEX)"
rm -f "$(COMPLEX_MIN)"
rm -f "$(PARSER_OUT)"
\ No newline at end of file
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Complex Queries Example</title>
<style type="text/css" media="screen">
table, textarea, input {
width: 100%;
}
textarea {
height: 10em;
}
</style>
</head>
<body>
<table>
<tr>
<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>
</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>Result (Query String):<br /><textarea id="result">[{&quot;1&quot;:&quot;abc&quot;,&quot;2&quot;:&quot;def&quot;}]</textarea></td>
</tr>
<tr>
<td><label for="wildcard">Wildcard char: </label></td>
<td><input type="text" id="wildcard" name="wildcard" value="%" /></td>
</tr>
<tr>
<td><label for="sort_on">Sort on: </label></td>
<td><input type="text" id="sort_on" name="sort_on" value="[[&quot;1&quot;,&quot;ascending&quot;],[&quot;2&quot;,&quot;descending&quot;]]" /></td>
</tr>
<tr>
<td><label for="select_list">Select_list: </label></td>
<td><input type="text" id="select_list" name="select_list" value="[&quot;1&quot;,&quot;2&quot;]" /></td>
</tr>
<tr>
<td><label for="limit">Limit: </label></td>
<td><input type="text" id="limit" name="limit" value="[0,100]" /></td>
</tr>
</table>
<button onclick="parse()">Parse</button>
<button onclick="serialize()">Serialize</button>
<button onclick="query()">Query</button>
<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/sjcl/sjcl.min.js"></script>
<script type="text/javascript" src="../jio.min.js"></script>
<script type="text/javascript" src="../complex_queries.min.js"></script>
<script type="text/javascript"
src="http://code.jquery.com/jquery-1.8.2.min.js"></script>
<script type="text/javascript">
<!--
var parse = function () {
$('#obj').attr('value',JSON.stringify(jIO.ComplexQueries.parse($('#str').attr('value'))));
};
var serialize = function () {
$('#str').attr('value',jIO.ComplexQueries.serialize(JSON.parse($('#obj').attr('value'))));
};
var query = function () {
$('#result').attr('value',JSON.stringify(
jIO.ComplexQueries.query(
{
query:$('#str').attr('value'),
filter:{
sort_on:JSON.parse($('#sort_on').attr('value')),
limit:JSON.parse($('#limit').attr('value')),
select_list:JSON.parse($('#select_list').attr('value'))
},
wildcard_character:$('#wildcard').attr('value')
}, JSON.parse($('#list').attr('value'))
)
));
};
// -->
</script>
</body>
</html>
...@@ -321,41 +321,71 @@ jIO.addStorageType('local', function (spec, my) { ...@@ -321,41 +321,71 @@ 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, s, j, file, items = 0, all_doc_response = {}; var i, j, file, items = 0,
s = new RegExp(priv.localpath + '\\/.*$'),
all_doc_response = {},
query_object = [], query_syntax, query_response = [];
all_doc_response.rows = []; query_syntax = command.getOption('query');
if (query_syntax === undefined) {
all_doc_response.rows = [];
for (i in localStorage) { for (i in localStorage) {
if (localStorage.hasOwnProperty(i)) { if (localStorage.hasOwnProperty(i)) {
// filter non-documents // filter non-documents
s = new RegExp(priv.localpath + '\\/.*$'); if (s.test(i)) {
if (s.test(i)) { items += 1;
items += 1; j = i.split('/').slice(-1)[0];
j = i.split('/').slice(-1)[0];
file = { value: {} }; file = { value: {} };
file.id = j; file.id = j;
file.key = j; file.key = j;
if (command.getOption('include_docs')) { if (command.getOption('include_docs')) {
file.doc = JSON.parse(localStorage.getItem(i)); file.doc = JSON.parse(localStorage.getItem(i));
}
all_doc_response.rows.push(file);
} }
all_doc_response.rows.push(file);
} }
} }
} all_doc_response.total_rows = items;
all_doc_response.total_rows = items; if (items > 0) {
that.success(all_doc_response);
if (items > 0) { return;
that.success(all_doc_response); }
} else { } else {
that.error({ // create complex query object from returned results
"status": 404, for (i in localStorage) {
"statusText": "Not Found", if (localStorage.hasOwnProperty(i)) {
"error": "not_found", if (s.test(i)) {
"message": "No documents found", items += 1;
"reason": "No documents found" j = i.split('/').slice(-1)[0];
}); query_object.push(JSON.parse(localStorage.getItem(i)));
}
}
}
query_response = jIO.ComplexQueries.query({
query: query_syntax.query,
filter: {
sort_on: query_syntax.filter.sort_on,
limit: query_syntax.filter.limit,
select_list: query_syntax.filter.select_list
},
wildcard_character: query_syntax.wildcard_character
},
query_object
);
if (items > 0) {
that.success(query_response);
return;
}
} }
that.error({
"status": 404,
"statusText": "Not Found",
"error": "not_found",
"message": "No documents found",
"reason": "No documents found"
});
}; };
return that; return that;
......
(function (scope) {
"use strict";
Object.defineProperty(scope, "ComplexQueries", {
configurable: false,
enumerable: false,
writable: false,
value: {}
});
Object.defineProperty(scope.ComplexQueries, "parse", {
configurable: false,
enumerable: false,
writable: false,
value: function (string) {
return result;
}
});
This diff is collapsed.
/~ Token definitions ~/
! ' |\t' ;
' |\t' WHITESPACE ;
'\(' LEFT_PARENTHESE ;
'\)' RIGHT_PARENTHESE ;
'AND' AND ;
'OR' OR ;
'NOT' NOT ;
'[^><= :\(\)"][^ :\(\)"]*:' COLUMN ;
'"(\\.|[^\\"])*"' STRING ;
'[^><= :\(\)"][^ :\(\)"]*' WORD ;
'(>=?|<=?|!?=)' OPERATOR ;
##
/~ Grammar specification ~/
begin: search_text [* result = %1; *];
search_text
: and_expression [* %% = %1; *]
| and_expression search_text [* %% = mkComplexQuery('OR',[%1,%2]); *]
| and_expression OR search_text [* %% = mkComplexQuery('OR',[%1,%3]); *]
;
and_expression
: boolean_expression [* %% = %1 ; *]
| boolean_expression AND and_expression [* %% = mkComplexQuery('AND',[%1,%3]); *]
;
boolean_expression
: NOT expression [* %% = mkNotQuery(%2); *]
| expression [* %% = %1; *]
;
expression
: LEFT_PARENTHESE search_text RIGHT_PARENTHESE [* %% = %2; *]
| COLUMN expression [* simpleQuerySetId(%2,%1.split(':').slice(0,-1).join(':')); %% = %2; *]
| value [* %% = %1; *]
;
value
: OPERATOR string [* %2.operator = %1 ; %% = %2; *]
| string [* %% = %1; *]
;
string
: WORD [* %% = mkSimpleQuery('',%1); *]
| STRING [* %% = mkSimpleQuery('',%1.split('"').slice(1,-1).join('"')); *]
;
[*
var arrayExtend = function () {
var j,i,newlist=[],listoflists = arguments;
for (j=0; j<listoflists.length; ++j) {
for (i=0; i<listoflists[j].length; ++i) {
newlist.push(listoflists[j][i]);
}
}
return newlist;
};
var mkSimpleQuery = function (id,value,operator) {
return {type:'simple',operator:'=',id:id,value:value};
};
var mkNotQuery = function (query) {
if (query.operator === 'NOT') {
return query.query_list[0];
}
return {type:'complex',operator:'NOT',query_list:[query]};
};
var mkComplexQuery = function (operator,query_list) {
var i,query_list2 = [];
for (i=0; i<query_list.length; ++i) {
if (query_list[i].operator === operator) {
query_list2 = arrayExtend(query_list2,query_list[i].query_list);
} else {
query_list2.push(query_list[i]);
}
}
return {type:'complex',operator:operator,query_list:query_list2};
};
var simpleQuerySetId = function (query, id) {
var i;
if (query.type === 'complex') {
for (i = 0; i < query.query_list.length; ++i) {
simpleQuerySetId (query.query_list[i],id);
}
return true;
}
if (query.type === 'simple' && !query.id) {
query.id = id;
return true;
}
return false;
};
var error_offsets = [];
var error_lookaheads = [];
var error_count = 0;
var result;
if ( ( error_count = __##PREFIX##parse( string, error_offsets, error_lookaheads ) ) > 0 ) {
var i;
for (i = 0; i < error_count; ++i) {
throw new Error ( "Parse error near \"" +
string.substr ( error_offsets[i] ) +
"\", expecting \"" +
error_lookaheads[i].join() + "\"" );
}
}
*]
Object.defineProperty(scope.ComplexQueries,"query",{
configurable:false,enumerable:false,writable:false,
value: function (query, object_list) {
var wildcard_character = typeof query.wildcard_character === 'string' ?
query.wildcard_character : '%',
operator_actions = {
'=': function (value1, value2) {
value1 = '' + value1;
return value1.match (convertToRegexp (
value2, wildcard_character
)) || false && true;
},
'!=': function (value1, value2) {
value1 = '' + value1;
return !(value1.match (convertToRegexp (
value2, wildcard_character
)));
},
'<': function (value1, value2) { return value1 < value2; },
'<=': function (value1, value2) { return value1 <= value2; },
'>': function (value1, value2) { return value1 > value2; },
'>=': function (value1, value2) { return value1 >= value2; },
'AND': function (item, query_list) {
var i;
for (i=0; i<query_list.length; ++i) {
if (! itemMatchesQuery (item, query_list[i])) {
return false;
}
}
return true;
},
'OR': function (item, query_list) {
var i;
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 {
return false;
}
} else {
return true;
}
}
},
select = function (list, select_list) {
var i;
if (select_list.length === 0) {
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;
}
},
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;
};
},
mergeList = function (list, list_to_merge, index) {
var i,j;
for (i = index,j = 0; i < list_to_merge.length + index; ++i, ++j) {
list[i] = list_to_merge[j];
}
},
sort = function (list, sort_list) {
var i, tmp, key, asc, sortAndMerge = function() {
sort(tmp,sort_list.slice(1));
mergeList(list,tmp,i-tmp.length);
tmp = [list[i]];
};
if (list.length < 2) {
return;
}
if (sort_list.length === 0) {
return;
}
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') {
if (typeof limit_list[1] !== 'undefined') {
if (list.length > limit_list[1] + limit_list[0]) {
list.length = limit_list[1] + limit_list[0];
}
list.splice(0,limit_list[0]);
} else {
list.length = limit_list[0];
}
}
},
////////////////////////////////////////////////////////////
result_list = [], result_list_tmp = [], j;
object_list = object_list || [];
for (j=0; j<object_list.length; ++j) {
if ( itemMatchesQuery (
object_list[j], scope.ComplexQueries.parse (query.query)
)) {
result_list.push(object_list[j]);
}
}
if (query.filter) {
select(result_list,query.filter.select_list || []);
sort(result_list,query.filter.sort_on || []);
limit(result_list,query.filter.limit || []);
}
return result_list;
}
});
Object.defineProperty(scope.ComplexQueries,"serialize",{
configurable:false,enumerable:false,writable:false,value:function(query){
var str_list = [], i;
if (query.type === 'complex') {
str_list.push ( '(' );
for (i=0; i<query.query_list.length; ++i) {
str_list.push( scope.ComplexQueries.serialize(query.query_list[i]) );
str_list.push( query.operator );
}
str_list.length --;
str_list.push ( ')' );
return str_list.join(' ');
} else if (query.type === 'simple') {
return query.id + (query.id?': ':'') + query.operator + ' "' + query.value + '"';
}
return query;
}
});
...@@ -1141,6 +1141,29 @@ test ("AllDocs", function(){ ...@@ -1141,6 +1141,29 @@ test ("AllDocs", function(){
o.jio.allDocs({"include_docs":true}, o.f); o.jio.allDocs({"include_docs":true}, o.f);
o.tick(o); o.tick(o);
// complex queries
o.thisShouldBeTheAnswer4 = [
{"title": "Inception", "year": 2010},
{"title": "The Dark Knight", "year": 2008},
{"title": "Lord of the Rings - Return of the King", "year": 2003},
{"title": "Lord Of the Rings - Fellowship of the Ring", "year": 2001},
{"title": "Fight Club", "year": 1999}
];
o.spy(o, "value", o.thisShouldBeTheAnswer4,
"allDocs (complex queries year >= 1980, all query options)");
o.jio.allDocs({
"query":{
"query":'(year: >= "1980")',
"filter": {
"limit":[0,5],
"sort_on":[['year','descending']],
"select_list":['title','year']
},
"wildcard_character":'%'
}
}, o.f);
o.tick(o);
o.jio.stop(); o.jio.stop();
}); });
...@@ -3438,7 +3461,7 @@ test ("AllDocs", function () { ...@@ -3438,7 +3461,7 @@ test ("AllDocs", function () {
o.spy(o, "value", o.thisShouldBeTheAnswer2, "allDocs (include_docs)"); o.spy(o, "value", o.thisShouldBeTheAnswer2, "allDocs (include_docs)");
o.jio.allDocs({"include_docs":true}, o.f); o.jio.allDocs({"include_docs":true}, o.f);
o.tick(o); o.tick(o);
/*
// complex queries // complex queries
o.thisShouldBeTheAnswer3 = {"nothing here":"yet"} o.thisShouldBeTheAnswer3 = {"nothing here":"yet"}
o.spy(o, "value", o.thisShouldBeTheAnswer3, o.spy(o, "value", o.thisShouldBeTheAnswer3,
...@@ -3455,7 +3478,7 @@ test ("AllDocs", function () { ...@@ -3455,7 +3478,7 @@ test ("AllDocs", function () {
} }
}, o.f); }, o.f);
o.tick(o); o.tick(o);
*/
o.jio.stop(); o.jio.stop();
}); });
/* /*
......
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