Skip to content

Commit

Permalink
Merge branch 'master' of http://github.com/kriszyp/rql
Browse files Browse the repository at this point in the history
  • Loading branch information
dvv committed Nov 30, 2010
2 parents 1a1b5d9 + f42a85c commit 704ddd7
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 36 deletions.
13 changes: 10 additions & 3 deletions README.md
Expand Up @@ -26,7 +26,9 @@ using chained operator calls in JavaScript. We could
write this query: write this query:


var Query = require("rql/query").Query; var Query = require("rql/query").Query;
new Query().eq("foo",3).forEach(...); var fooEq3Query = new Query().eq("foo",3);




The RQL grammar is based around standard URI delimiters. The standard rules for The RQL grammar is based around standard URI delimiters. The standard rules for
encoding strings with URL encoding (%xx) are observed. RQL also supersets FIQL. encoding strings with URL encoding (%xx) are observed. RQL also supersets FIQL.
Expand Down Expand Up @@ -77,11 +79,16 @@ we can do:


foo=string:3 foo=string:3


Any property can be nested by using a dot syntax. To search by the bar property of Any property can be nested by using an array of properties. To search by the bar property of
the object in the foo property we can do: the object in the foo property we can do:


foo.bar=3 (foo,bar)=3


We can also use slashes as shorthand for arrays, so we could equivalently write the nested
query:

foo/bar=3

Another common operator is sort. We can use the sort operator to sort by a specified property. Another common operator is sort. We can use the sort operator to sort by a specified property.
To sort by foo in ascending order: To sort by foo in ascending order:


Expand Down
55 changes: 33 additions & 22 deletions lib/js-array.js
Expand Up @@ -8,7 +8,9 @@
define(["exports", "./parser"], function(exports, parser){ define(["exports", "./parser"], function(exports, parser){


var parseQuery = parser.parseQuery; var parseQuery = parser.parseQuery;

var stringify = JSON.stringify || function(str){
return '"' + str.replace(/"/g, "\\\"") + '"';
};
exports.jsOperatorMap = { exports.jsOperatorMap = {
"eq" : "===", "eq" : "===",
"ne" : "!==", "ne" : "!==",
Expand All @@ -17,6 +19,7 @@ exports.jsOperatorMap = {
"lt" : "<", "lt" : "<",
"gt" : ">" "gt" : ">"
}; };
var negated = false;
exports.operators = { exports.operators = {
sort: function(){ sort: function(){
var terms = []; var terms = [];
Expand Down Expand Up @@ -45,15 +48,14 @@ exports.operators = {
"in": filter(function(value, values){ "in": filter(function(value, values){
return values.indexOf(value) > -1; return values.indexOf(value) > -1;
}), }),
notin: filter(function(value, values){ not: function(expression){
return values.indexOf(value) === -1; negated = true;
}), try{
contains: filter(function(values, value){ return expression.call(this);
return values.indexOf(value) > -1; }finally{
}), negated = false;
notcontains: filter(function(values, value){ }
return values.indexOf(value) === -1; },
}),
any: filter(function(array, value){ any: filter(function(array, value){
if(typeof value == "function"){ if(typeof value == "function"){
return array.some(function(i){ return array.some(function(i){
Expand Down Expand Up @@ -271,13 +273,14 @@ exports.operators = {
} }
}; };
exports.filter = filter; exports.filter = filter;
function filter(condition){ function filter(condition, not){
// convert to boolean right now
var filter = function(property, second){ var filter = function(property, second){
var args = arguments; var args = arguments;
var filtered = []; var filtered = [];
for(var i = 0, length = this.length; i < length; i++){ for(var i = 0, length = this.length; i < length; i++){
var item = this[i]; var item = this[i];
if(condition(evaluateProperty(item, property), second)){ if(!condition(evaluateProperty(item, property), second) == negated){
filtered.push(item); filtered.push(item);
} }
} }
Expand All @@ -299,15 +302,15 @@ function reducer(func){
} }
exports.evaluateProperty = evaluateProperty; exports.evaluateProperty = evaluateProperty;
function evaluateProperty(object, property){ function evaluateProperty(object, property){
if(property.indexOf(".") === -1){ if(property instanceof Array){
return object[decodeURIComponent(property)]; property.forEach(function(part){
}
else{
property.split(".").forEach(function(part){
object = object[decodeURIComponent(part)]; object = object[decodeURIComponent(part)];
}); });
return object; return object;
} }
else{
return object[decodeURIComponent(property)];
}
}; };
var conditionEvaluator = exports.conditionEvaluator = function(condition){ var conditionEvaluator = exports.conditionEvaluator = function(condition){
var jsOperator = exports.jsOperatorMap[term.name]; var jsOperator = exports.jsOperatorMap[term.name];
Expand All @@ -326,7 +329,6 @@ exports.query = query;
exports.missingOperator = function(operator){ exports.missingOperator = function(operator){
throw new Error("Operator " + operator + " is not defined"); throw new Error("Operator " + operator + " is not defined");
} }
function relay(){return this;} // TODO: may be control by options?
function query(query, options, target){ function query(query, options, target){
options = options || {}; options = options || {};
query = parseQuery(query, options.parameters); query = parseQuery(query, options.parameters);
Expand All @@ -344,9 +346,18 @@ function query(query, options, target){
var jsOperator = exports.jsOperatorMap[value.name]; var jsOperator = exports.jsOperatorMap[value.name];
if(jsOperator){ if(jsOperator){
// item['foo.bar'] ==> (item && item.foo && item.foo.bar && ...) // item['foo.bar'] ==> (item && item.foo && item.foo.bar && ...)
var path = String(value.args[0]); var path = value.args[0];
var item = path ? path.split('.').map(function(x,i,a){return 'item.'+a.slice(0,i+1).join('.')}).join('&&') : 'item'; if(path instanceof Array){
item = '('+item+')'; var item = "(";
var escaped = [];
for(var i = 0;i < path.length; i++){
escaped.push(stringify(path[i]));
item += (item.length > 1 ? "&&" : "") + "item[" + escaped.join("][") + ']';
}
}else{
var item = "(item[" + stringify(path) + "]";
}
item += ')';
if (jsOperator === '===' && value.args[1] instanceof RegExp){ if (jsOperator === '===' && value.args[1] instanceof RegExp){
// N.B. matching requires String // N.B. matching requires String
item = 'String('+item + '||"")'; item = 'String('+item + '||"")';
Expand All @@ -360,7 +371,7 @@ function query(query, options, target){
")})"; ")})";
} }
}else{ }else{
return typeof value === "number" ? value : JSON.stringify(value); return typeof value === "number" ? value : stringify(value);
} }
} }
var evaluator = eval("(function(target){return " + queryToJS(query) + ".call(target);})"); var evaluator = eval("(function(target){return " + queryToJS(query) + ".call(target);})");
Expand Down
10 changes: 8 additions & 2 deletions lib/parser.js
Expand Up @@ -43,9 +43,15 @@ function parse(/*String|Object*/query, parameters){
if(exports.jsonQueryCompatible){ if(exports.jsonQueryCompatible){
query = query.replace(/%3C=/g,"=le=").replace(/%3E=/g,"=ge=").replace(/%3C/g,"=lt=").replace(/%3E/g,"=gt="); query = query.replace(/%3C=/g,"=le=").replace(/%3E=/g,"=ge=").replace(/%3C/g,"=lt=").replace(/%3E/g,"=gt=");
} }
if(query.indexOf("/") > -1){ // performance guard
// convert slash delimited text to arrays
query = query.replace(/[\+\*\$\-:\w%\._]*\/[\+\*\$\-:\w%\._\/]*/g, function(slashed){
return "(" + slashed.replace(/\//g, ",") + ")";
});
}
// convert FIQL to normalized call syntax form // convert FIQL to normalized call syntax form
query = query.replace(/([\+\*\-:\w%\._]+)([<>!]?=(?:[\w]*=)?|>|<)(\([\+\*\$\-:\w%\._,]+\)|[\+\*\$\-:\w%\._]*|)/g, query = query.replace(/(\([\+\*\$\-:\w%\._,]+\)|[\+\*\$\-:\w%\._]*|)([<>!]?=(?:[\w]*=)?|>|<)(\([\+\*\$\-:\w%\._,]+\)|[\+\*\$\-:\w%\._]*|)/g,
//<--- property ---><------ operator -----><---------------- value ------------------> //<--------- property -----------><------ operator -----><---------------- value ------------------>
function(t, property, operator, value){ function(t, property, operator, value){
if(operator.length < 3){ if(operator.length < 3){
if(!operatorMap[operator]){ if(!operatorMap[operator]){
Expand Down
37 changes: 37 additions & 0 deletions test/js-array.js
@@ -0,0 +1,37 @@
var assert = require("assert"),
Query = require("../lib/query").Query,
executeQuery = require("../lib/js-array").executeQuery;

var data = [
{
"with.dot": "dotted",
"nested": {
"property": "value"
},
"price": 10,
"name": "ten",
"tags": ["fun", "even"]
},
{
"price": 5,
"name": "five",
"tags": ["fun"]
}];

exports.testFiltering = function() {
assert.equal(executeQuery("price=lt=10", {}, data).length, 1);
assert.equal(executeQuery("price=lt=11", {}, data).length, 2);
assert.equal(executeQuery("nested/property=value", {}, data).length, 1);
assert.equal(executeQuery("with.dot=dotted", {}, data).length, 1);
assert.equal(executeQuery("not(in(price,(5,10)))", {}, data).length, 0);
assert.equal(executeQuery("not(in(price,(5)))", {}, data).length, 1);
assert.equal(executeQuery("any(tags,even)", {}, data).length, 1);
assert.equal(executeQuery("any(tags,fun)", {}, data).length, 2);
assert.equal(executeQuery("all(tags,fun)", {}, data).length, 1);
assert.equal(executeQuery("all(tags,even)", {}, data).length, 0);
assert.equal(executeQuery("not(all(tags,even))", {}, data).length, 2);
};


if (require.main === module)
require("patr/runner").run(exports);
11 changes: 2 additions & 9 deletions tests/query.js → test/query.js
@@ -1,9 +1,7 @@
var assert = require("assert"), var assert = require("assert"),
Query = require("../lib/query").Query, Query = require("../lib/query").Query,
parseQuery = require("../lib/parser").parseQuery; parseQuery = require("../lib/parser").parseQuery;
executeQuery = require("../lib/js-array").executeQuery; exports.testJSArray = require("./js-array");

Array.prototype.match = function(query, params){return executeQuery(query, params || [], this);};


exports.testBehavior = function() { exports.testBehavior = function() {
//assert.error(parseQuery(), "parseQuery requires a string"); //assert.error(parseQuery(), "parseQuery requires a string");
Expand All @@ -22,6 +20,7 @@ var queryPairs = {
{"a(b,c)": {name:"and", args:[{name:"a", args:["b", "c"]}]}}, {"a(b,c)": {name:"and", args:[{name:"a", args:["b", "c"]}]}},
{"a((b),c)": {"name": "and", args:[{name:"a", args:[["b"], "c"]}]}}, {"a((b),c)": {"name": "and", args:[{name:"a", args:[["b"], "c"]}]}},
{"a((b,c),d)": {name:"and", args:[{name:"a", args:[["b", "c"], "d"]}]}}, {"a((b,c),d)": {name:"and", args:[{name:"a", args:[["b", "c"], "d"]}]}},
{"a(b/c,d)": {name:"and", args:[{name:"a", args:[["b", "c"], "d"]}]}},
{"a(b)&c(d(e))": {name:"and", args:[ {"a(b)&c(d(e))": {name:"and", args:[
{name:"a", args:["b"]}, {name:"a", args:["b"]},
{name:"c", args:[{name:"d", args:["e"]}]} {name:"c", args:[{name:"d", args:["e"]}]}
Expand Down Expand Up @@ -159,12 +158,6 @@ exports.testBindParameters = function() {
assert.deepEqual(parsed, {name: 'and', args: [{name: 'eq', args: ['id', 'a']}], cache: {id: 'a'}}); assert.deepEqual(parsed, {name: 'and', args: [{name: 'eq', args: ['id', 'a']}], cache: {id: 'a'}});
}; };


exports.testExecution = function() {
// TODO
// nested props: https://github.com/kriszyp/rql/issues/#issue/15
assert.deepEqual([{"path":[1,2,3]},{"path":[9,3,7]}].match("foo.cat=3"), []);
};

exports.testStringification = function() { exports.testStringification = function() {
// TODO // TODO
var parsed; var parsed;
Expand Down

0 comments on commit 704ddd7

Please sign in to comment.