Created
May 13, 2010 14:01
-
-
Save dvv/399858 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var exports = {}; | |
exports.jsonQueryCompatible = true; | |
var operatorMap = { | |
"=": "eq", | |
"==": "eq", | |
">": "gt", | |
">=": "ge", | |
"<": "lt", | |
"<=": "le", | |
"!=": "ne" | |
}; | |
var parseQuery = exports.parseQuery = function(/*String|Object*/query, parameters){ | |
var topTerms = []; | |
var term = {name:"and", args: topTerms}; | |
if(typeof query === "object"){ | |
for(var i in query){ | |
topTerms.push({ | |
property: i, | |
name: "eq", | |
args:[property[i]] | |
}); | |
} | |
return topTerms; | |
} | |
if(query.charAt(0) == "?"){ | |
throw new Error("Query must not start with ?"); | |
} | |
if(exports.jsonQueryCompatible){ | |
query = query.replace(/%3C=/g,"=le=").replace(/%3E=/g,"=ge=").replace(/%3C/g,"=lt=").replace(/%3E/g,"=gt="); | |
} | |
// convert FIQL to normalized call syntax form | |
query = query.replace(/([\+\*\-:\w%\._]+)(>|<|[<>!]?=([\w]*=)?)([\+\*\-:\w%\._]+|\([\+\*\-:\w%\._,]+\))/g, function(t, property, operator, operatorSuffix, value){ | |
if(operator.length < 3){ | |
if(!operatorMap[operator]){ | |
throw new Error("Illegal operator " + operator); | |
} | |
operator = operatorMap[operator]; | |
} | |
else{ | |
operator = operator.substring(1, operator.length - 1); | |
} | |
return operator + '(' + property + "," + value + ")"; | |
}); | |
if(query.charAt(0)=="?"){ | |
query = query.substring(1); | |
} | |
var leftoverCharacters = query.replace(/(\))|([&\|,])?([\+\*\$\-:\w%\._]*)(\(?)/g, | |
// <-closedParan->|<-delim-- propertyOrValue -----(> | | |
function(t, closedParan, delim, propertyOrValue, openParan){ | |
if(delim){ | |
if(delim === "&"){ | |
forceOperator("and"); | |
} | |
if(delim === "|"){ | |
forceOperator("or"); | |
} | |
} | |
if(openParan){ | |
var paths = propertyOrValue.split("."); | |
var newTerm = { | |
name: paths[paths.length - 1], | |
parent: term | |
}; | |
if(paths.length > 2){ | |
newTerm.property = paths.slice(0, -1); | |
} | |
else if(paths.length === 2){ | |
newTerm.property = paths[0]; | |
} | |
call(newTerm); | |
} | |
else if(closedParan){ | |
var isArray = !term.name; | |
term = term.parent; | |
if(!term){ | |
throw new URIError("Closing paranthesis without an opening paranthesis"); | |
} | |
if(isArray){ | |
term.args.push(term.args.pop().args); | |
} | |
} | |
else if(propertyOrValue){ | |
term.args.push(stringToValue(propertyOrValue, parameters)); | |
} | |
return ""; | |
}); | |
if(term.parent){ | |
throw new URIError("Opening paranthesis without a closing paranthesis"); | |
} | |
if(leftoverCharacters){ | |
// any extra characters left over from the replace indicates invalid syntax | |
throw new URIError("Illegal character in query string encountered " + leftoverCharacters); | |
} | |
function call(newTerm, parent){ | |
newTerm.args = []; | |
term.args.push(newTerm); | |
term = newTerm; | |
} | |
function forceOperator(operator){ | |
if(!term.name){ | |
term.name = operator; | |
} | |
else if(term.name !== operator){ | |
var last = term.args.pop(); | |
call({ | |
name:operator, | |
parent: term.parent | |
}); | |
term.args.push(last); | |
} | |
} | |
function removeParentProperty(obj) { | |
if(obj && obj.args){ | |
delete obj.parent; | |
obj.args.forEach(removeParentProperty); | |
} | |
return obj; | |
}; | |
removeParentProperty({args:topTerms}); | |
return topTerms; | |
}; | |
function throwMaxIterations(){ | |
throw new Error("Query has taken too much computation, and the user is not allowed to execute resource-intense queries. Increase maxIterations in your config file to allow longer running non-indexed queries to be processed."); | |
} | |
exports.maxIterations = 10000; | |
function stringToValue(string, parameters){ | |
switch(string){ | |
case "true": return true; | |
case "false": return false; | |
case "null": return null; | |
default: | |
// handle arrays | |
if (string.indexOf(',') > -1) { | |
var array = []; | |
string.split(',').forEach(function(x){ | |
array.push(stringToValue(x, parameters)); | |
}); | |
return array; | |
} | |
// handle scalars | |
var number = parseFloat(string, 10); | |
if(isNaN(number)){ | |
if(string.indexOf(":") > -1){ | |
var date = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(string); | |
if (date) { | |
return new Date(Date.UTC(+date[1], +date[2] - 1, +date[3], +date[4], | |
+date[5], +date[6])); | |
} | |
var parts = string.split(":",2); | |
switch(parts[0]){ | |
case "boolean" : return Boolean(parts[1]); | |
case "number" : return parseFloat(parts[1], 10); | |
case "string" : return decodeURIComponent(parts[1]); | |
case "date" : return new Date(stringToValue(parts[1])); | |
case "null" : return null; | |
default: | |
throw new URIError("Unknown type " + parts[0]); | |
} | |
} | |
if(string.charAt(0) == "$"){ | |
return parameters[parseInt(string.substring(1)) - 1]; | |
} | |
string = decodeURIComponent(string); | |
if(exports.jsonQueryCompatible){ | |
if(string.charAt(0) == "'" && string.charAt(string.length-1) == "'"){ | |
return JSON.parse('"' + string.substring(1,string.length-1) + '"'); | |
} | |
} | |
return string; | |
} | |
return number; | |
} | |
}; | |
function qqq(query){ | |
var search = {}; | |
if(typeof query === "string"){ | |
query = parseQuery(query); | |
//console.log("Q0:", query); | |
} | |
var options = {}; | |
search = walk('and', query); | |
return search; | |
function walk(name, terms) { | |
var search = {}; | |
terms.forEach(function(term){ | |
var func = term.name; | |
var args = term.args; | |
//console.log("WALK", func, args); | |
if (!term.name) | |
return null; | |
// well-known functions | |
// http://www.mongodb.org/display/DOCS/Querying | |
if (args[0] && typeof args[0] === 'object') { | |
var a = walk(func, args); | |
/*console.log("SNOOP", func, args, a); | |
var s = []; | |
for (var i in a) if (a.hasOwnProperty(i)) { | |
var x = {}; | |
x[i] = a[i]; | |
console.log("PUSH", x, i); | |
s.push(x); | |
}*/ | |
search['$'+func] = a; | |
} | |
// structured query syntax | |
// http://www.mongodb.org/display/DOCS/Advanced+Queries | |
else if (true) { | |
// mongo specialty | |
if (func == 'le') func = 'lte'; | |
else if (func == 'ge') func = 'gte'; | |
// valid funcs | |
// TODO: 'not' is specific | |
var valid_funcs = ['lt','lte','gt','gte','ne','in','nin','mod','all','size','exists','type','elemMatch']; | |
var requires_array = ['in','nin','all','mod']; | |
// | |
var key = args.shift(); | |
if (requires_array.indexOf(func) == -1) | |
args = args.join(); | |
// | |
if (func == 'ne' && args instanceof RegExp) { | |
func = 'not'; | |
} else if (func == 'eq') { | |
search[key] = args; | |
func = undefined; | |
} | |
if (valid_funcs.indexOf(func) > -1) { | |
func = '$'+func; | |
} | |
if (func !== undefined) { | |
if (search[key] === undefined) | |
search[key] = {}; | |
if (search[key] instanceof Object && !(search[key] instanceof Array)) | |
search[key][func] = args; | |
} | |
} | |
// TODO: add support for query expressions as Javascript | |
}); | |
return search; | |
} | |
} | |
var q = parseQuery('not(or(le(a,2),ge(a,1))),le(a,aaa),ne(b,%2fregexp%2f),ne(foo.bar.aaa,poopsie),le(foo.bar.aaa,woopsy),or(a=b,c=d),select(name)&sort(-date),in(c,1,2,true)&group(channel)'); | |
console.log(q); | |
var r = qqq(q); | |
console.log(r); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment