Skip to content

Instantly share code, notes, and snippets.

@dvv
Created May 13, 2010 14:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dvv/399858 to your computer and use it in GitHub Desktop.
Save dvv/399858 to your computer and use it in GitHub Desktop.
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