Commit 02e546dd authored by Tristan Cavelier's avatar Tristan Cavelier

Add complex queries to jIO

parent e5f9d354
<!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="../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>
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": []
}
auto: parser grunt
grunt:
make -C ../../grunt/*_gruntQueries
clean:
rm -f ./*~
(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;
}
});
/~ 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;
}
});
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