tingodb
Version:
Embedded Node.js database upward compatible with MongoDB
870 lines (847 loc) • 22.3 kB
JavaScript
var _ = require('lodash');
var tdb = null;
var expo = {};
module.exports = function (tdb_) {
tdb=tdb_;
return expo;
};
var ops = {
$range : function () {
this.op = "$range";
this.dump = function () {
return this._args[0].dump() +" in range (" +this._args[1].dump()+","+this._args[2].dump()+","+this._args[3].dump()+","+this._args[4].dump()+")";
},
this._index = function (index) {
if (index.order > 0)
return index.range(this._args[1]._get(), this._args[2]._get(), this._args[3]._get(), this._args[4]._get());
return index.range(this._args[2]._get(), this._args[1]._get(), this._args[4]._get(), this._args[3]._get());
};
},
$lt : function () {
this.op = "$lt";
this._index = function (index) {
if (index.order > 0)
return index.range(null, this._args[1]._get(), false, true);
return index.range(this._args[1]._get(), null, true, false);
};
this._ex = function (f) {
if (this.fields()[f]!=null)
return 1;
return -1;
};
this.dump = function () {
return this._args[0].dump() +" < " +this._args[1].dump();
};
this.native = function () {
return this._args[0].native() +" < " +this._args[1].native();
};
this.native3 = function () {
var s = "(function () {\n";
s+= "var v = "+this._args[0].native3()+"\n";
s+= "if (!v) return false;\n";
s+= "for (var i=0; i<v.length; i++ ) {\n";
s+= "if (v[i]<"+ this._args[1].native3()+") return true;\n";
s+= "}\nreturn false;})()";
return s;
};
},
$lte : function () {
this.op = "$lte";
this._index = function (index) {
if (index.order > 0)
return index.range(null, this._args[1]._get(), false, false);
return index.range(this._args[1]._get(), null, false, false);
};
this._ex = function (f) {
if (this.fields()[f]!=null)
return 1;
return -1;
};
this.dump = function () {
return this._args[0].dump() +" <= " +this._args[1].dump();
};
this.native = function () {
return this._args[0].native() +" <= " +this._args[1].native();
};
this.native3 = function () {
var s = "(function () {\n";
s+= "var v = "+this._args[0].native3()+"\n";
s+= "if (!v) return false;\n";
s+= "for (var i=0; i<v.length; i++ ) {\n";
s+= "if (v[i]<="+ this._args[1].native3()+") return true;\n";
s+= "}\nreturn false;})()";
return s;
};
},
$gt : function () {
this.op = "$gt";
this._index = function (index) {
if (index.order > 0)
return index.range(this._args[1]._get(), null, true, false);
return index.range(null, this._args[1]._get(), false, true);
};
this._ex = function (f) {
if (this.fields()[f]!=null)
return 1;
return -1;
};
this.dump = function () {
return this._args[0].dump() +" > " + this._args[1].dump();
};
this.native = function () {
return this._args[0].native() +" > " +this._args[1].native();
};
this.native3 = function () {
var s = "(function () {\n";
s+= "var v = "+this._args[0].native3()+"\n";
s+= "if (!v) return false;\n";
s+= "for (var i=0; i<v.length; i++ ) {\n";
s+= "if (v[i]>"+ this._args[1].native3()+") return true;\n";
s+= "}\nreturn false;})()";
return s;
};
},
$gte : function () {
this.op = "$gte";
this._get = function (obj) {
return this._args[0]._get(obj) >= this._args[1]._get(obj);
};
this._index = function (index) {
if (index.order > 0)
return index.range(this._args[1]._get(), null, false, false);
return index.range(null, this._args[1]._get(), false, false);
};
this._ex = function (f) {
if (this.fields()[f]!=null)
return 1;
return -1;
};
this.dump = function () {
return this._args[0].dump() +" >= " + this._args[1].dump();
};
this.native = function () {
return this._args[0].native() +" >= " +this._args[1].native();
};
this.native3 = function () {
var s = "(function () {\n";
s+= "var v = "+this._args[0].native3()+"\n";
s+= "if (!v) return false;\n";
s+= "for (var i=0; i<v.length; i++ ) {\n";
s+= "if (v[i]>="+ this._args[1].native3()+") return true;\n";
s+= "}\nreturn false;})()";
return s;
};
},
$exists : function () {
this.op = "$exists";
this._index = function (index) {
if (this._args[1]._get())
return index.values();
return index.nuls();
},
this._ex = function (f) {
if (this.fields()[f]!=null)
return 1;
return -1;
};
this.dump = function () {
return this._args[0].dump() + " exists " + this._args[1].dump();
};
this.native = function () {
var v = this._args[0].native();
return this._args[1]._get()?v:"!"+v;
};
this.native3 = function () {
var exists = this._args[1]._get();
var s = "(function () {\n";
s+= "var v = "+this._args[0].native3()+"\n";
s+= "if (!v) return "+!exists+";\n";
s+= "for (var i=0; i<v.length; i++ ) {\n";
if (exists)
s+= "if (!v[i]) return false;\n";
else
s+= "if (v[i]) return false;\n";
s+= "}\nreturn true;})()";
return s;
};
},
$eq : function () {
this.op = "$eq";
this._index = function (index) {
return index.match(this._args[1]._get());
},
this._ex = function (f) {
if (this.fields()[f]!=null)
return 1;
return -1;
};
this.dump = function () {
return this._args[0].dump() +" = " + this._args[1].dump();
};
this.native = function () {
return this._args[0].native() +" === " +this._args[1].native();
};
this.native3 = function () {
var s = "(function () {\n";
s+= "var v = "+this._args[0].native3()+"\n";
s+= "if (!v) return false;\n";
s+= "for (var i=0; i<v.length; i++ ) {\n";
s+= "if (v[i]==="+ this._args[1].native3()+") return true;\n";
s+= "}\nreturn false;})()";
return s;
};
},
$ne : function () {
this.op = "$ne";
this._index = function (index) {
var m = index.match(this._args[1]._get());
var a = index.all();
return _.difference(a,m);
},
this._ex = function (f) {
if (this.fields()[f]!=null)
return 1;
return -1;
};
this.dump = function () {
return this._args[0].dump() +" != " + this._args[1].dump();
};
this.native = function () {
return this._args[0].native() +" != " +this._args[1].native();
};
this.native3 = function () {
var s = "(function () {\n";
s+= "var v = "+this._args[0].native3()+"\n";
s+= "if (!v) return true;\n";
s+= "for (var i=0; i<v.length; i++ ) {\n";
s+= "if (v[i]=="+ this._args[1].native3()+") return false;\n";
s+= "}\nreturn true;})()";
return s;
};
},
$in : function () {
this.op = "$in";
this._index = function (index) {
var u = _(this._args.slice(1)).map(function (v) { return v._get(); }).uniq().value();
return _(u).map(function (v) { return index.match(v); }).flattenDeep().value();
},
this._ex = function (f) {
if (this.fields()[f]!=null)
return 1;
return -1;
};
this.dump = function () {
var s = this._args[0].dump()+" in [";
for (var i=1; i<this._args.length; i++) {
s+=this._args[i].dump();
if (i!=this._args.length-1)
s+=",";
}
return s+"]";
};
this.native = function () {
if (this._args.length==1)
return "true";
var s = "(function (obj) {\n";
s+="var v = "+this._args[0].native()+";\n";
s+="var args=[";
for (var i=1; i<this._args.length; i++) {
s+=JSON.stringify(this._args[i]._get());
if (i!=this._args.length-1)
s+=",";
}
s+="];\n";
s+="for (var i=0; i<args.length; i++) { if (args[i]==v) return true };\n";
s+="return false;\n";
s+="})(obj)";
return s;
};
this.native3 = function () {
if (this._args.length==1)
return "false";
var s = "(function (obj) {\n";
s+="var v = "+this._args[0].native3()+";\n";
s+= "if (!v) return false;\n";
s+="var args=[";
for (var i=1; i<this._args.length; i++) {
s+=JSON.stringify(this._args[i]._get());
if (i!=this._args.length-1)
s+=",";
}
s+="];\n";
s+= "for (var i=0; i<v.length; i++ ) {\n";
s+="for (var j=0; j<args.length; j++) { if (args[j]==v[i]) return true };\n";
s+="};return false;\n";
s+="})(obj)";
return s;
};
},
$nin : function () {
this.op = "$nin";
this._index = function (index) {
var u = _(this._args.slice(1)).map(function (v) { return v._get(); }).uniq().value();
var x = _(u).map(function (v) { return index.match(v); }).flattenDeep().value();
var a = index.all(null, true);
return _.difference(a, x);
},
this._ex = function (f) {
if (this.fields()[f]!=null)
return 1;
return -1;
};
this.dump = function () {
var s = this._args[0].dump()+" nin [";
for (var i=1; i<this._args.length; i++) {
s+=this._args[i].dump();
if (i!=this._args.length-1)
s+=",";
}
return s+"]";
};
this.native = function () {
if (this._args.length==1)
return "true";
var s = "(function (obj) {\n";
s+="var v = "+this._args[0].native()+";\n";
s+="var args=[";
for (var i=1; i<this._args.length; i++) {
s+=JSON.stringify(this._args[i]._get());
if (i!=this._args.length-1)
s+=",";
}
s+="];\n";
s+="for (var i=0; i<args.length; i++) { if (args[i]==v) return false };\n";
s+="return true;\n";
s+="})(obj)";
return s;
};
this.native3 = function () {
if (this._args.length==1)
return "true";
var s = "(function (obj) {\n";
s+="var v = "+this._args[0].native3()+";\n";
s+= "if (!v) return true;\n";
s+="var args=[";
for (var i=1; i<this._args.length; i++) {
s+=JSON.stringify(this._args[i]._get());
if (i!=this._args.length-1)
s+=",";
}
s+="];\n";
s+= "for (var i=0; i<v.length; i++ ) {\n";
s+="for (var j=0; j<args.length; j++) { if (args[j]==v[i]) return false };\n";
s+="};return true;\n";
s+="})(obj)";
return s;
};
},
$all : function () {
this.op = "$all";
this._ex = function (f) {
// check if our args do not have regexps,
// if so index can't be used
for (var i=1; i<this._args.length; i++) {
if (_.isRegExp(this._args[i]._get()))
return 0;
}
if (this.fields()[f]!=null)
return 1;
return -1;
};
this._index = function (index) {
var m = [];
for (var i=1; i<this._args.length; i++) {
if (i==1)
m = index.match(this._args[i]._get());
else
m = _.intersection(m,index.match(this._args[i]._get()));
}
return m;
},
this.native = function () {
if (this._args.length==1)
return "true";
var val,s = "(function (obj) {\n";
s+="var v = "+this._args[0].native()+";\n";
s+= "if (!v) return false;\n";
s+="var args=[";
for (var i=1; i<this._args.length; i++) {
val=this._args[i]._get();
if(val instanceof RegExp){
s+=val;
} else {
s+=JSON.stringify(val);
}
if (i!=this._args.length-1)
s+=",";
}
s+="];\n";
s+= "for (var i=0; i<args.length; i++ ) {\n";
s+= "if (args[i] instanceof RegExp)\n";
s+="for (var j=0; j<v.length; j++) { if (args[i].test(v[j])) break; }\n";
s+= "else\n";
s+="for (var j=0; j<v.length; j++) { if (v[j]==args[i]) break; };\n";
s+="if (j==v.length) return false;\n";
s+="};return true;\n";
s+="})(obj)";
return s;
},
this.native3 = function () {
if (this._args.length==1)
return "true";
var val,s = "(function (obj) {\n";
s+="var vv = "+this._args[0].native3()+";\n";
s+= "if (!vv) return false;\n";
s+= "if (!Array.isArray(vv[0])) vv=[vv];\n";
s+="var args=[";
for (var i=1; i<this._args.length; i++) {
val=this._args[i]._get();
if(val instanceof RegExp){
s+=val;
} else {
s+=JSON.stringify(val);
}
if (i!=this._args.length-1)
s+=",";
}
s+="];\n";
s+="for (var k=0; k<vv.length; k++) {\n";
s+="var v = vv[k];\n";
s+="for (var i=0; i<args.length; i++ ) {\n";
s+= "if (args[i] instanceof RegExp)\n";
s+="for (var j=0; j<v.length; j++) { if (args[i].test(v[j])) break; }\n";
s+= "else\n";
s+="for (var j=0; j<v.length; j++) { if (v[j]==args[i]) break; };\n";
s+="if (j==v.length) return false;\n";
s+="}};return true;\n";
s+="})(obj)";
return s;
};
},
$not : function () {
this.op = "$not";
this._index = function (index) {
var m = this._args[1]._index(index);
var a = index.all();
return _.difference(a,m);
},
this._ex = function (f) {
return this._args[1]._ex(f);
};
this.dump = function () {
return "!("+this._args[1].dump()+")";
};
this.native = function () {
return "!("+this._args[1].native()+")";
};
this.native3 = function () {
return "!("+this._args[1].native3()+")";
};
},
$and : function () {
this.op = "$and";
this._index = function (index) {
var ops = [];
for (var i=0; i<this._args.length; i++) {
ops.push(this._args[i]._index(index));
}
return _.intersection.apply(_,ops);
};
this.split = function (f) {
var s = new ops.$and();
var d = [];
var o = [];
var i = 0;
for (i=0; i<this._args.length; i++) {
if (this._args[i].op=="$and" || this._args[i].op=="$or" ) {
var sub = this._args[i].split(f);
if (this._args[i]._args.length>0) {
_.each(this._args[i]._args, function (a) {
o.push(a);
});
}
if (sub._args.length>0) {
_.each(sub._args, function (a) {
d.push(a);
});
}
} else {
var ex = this._args[i]._ex(f);
if (ex==1)
d.push(this._args[i]);
else
o.push(this._args[i]);
}
}
// special check for paired ops
var l=null,g=null;
for (i=0; i<d.length; i++) {
if (d[i].op=="$lt" || d[i].op=="$lte")
l = i;
if (d[i].op=="$gt" || d[i].op=="$gte")
g = i;
}
if (l!=null && g!=null) {
var r = new ops.$range();
r._args = [d[l]._args[0]];
r._args.push(d[g]._args[1]);
r._args.push(d[l]._args[1]);
r._args.push(new value(d[g].op=="$gte"?false:true));
r._args.push(new value(d[l].op=="$lte"?false:true));
d.splice(l,1);
d.splice(l<g?g:g-1,1);
d.push(r);
}
s._args = d;
this._args = o;
return s;
};
this._ex = function (f) {
var r = -1;
for (var i=0; i<this._args.length; i++) {
var ex = this._args[i]._ex(f);
if (ex==0)
return 0;
if (ex==1)
r=1;
}
return r;
};
this.dump = function () {
var s = "(";
for (var i=0; i<this._args.length; i++) {
s+=this._args[i].dump();
if (i!=this._args.length-1)
s+=",";
}
return s+")";
};
this.native = function () {
if (this._args.length==0)
return "true";
var s = "(";
for (var i=0; i<this._args.length; i++) {
s+=this._args[i].native();
if (i!=this._args.length-1)
s+=" && ";
}
return s+")";
};
this.native3 = function () {
if (this._args.length==0)
return "true";
var s = "(";
for (var i=0; i<this._args.length; i++) {
s+=this._args[i].native3();
if (i!=this._args.length-1)
s+=" && ";
}
return s+")";
};
},
$or : function () {
this.op = "$or";
this._ex = function () {
return 0;
};
this.dump = function () {
var s = "(";
for (var i=0; i<this._args.length; i++) {
s+=this._args[i].dump();
if (i!=this._args.length-1)
s+=" || ";
}
return s+")";
};
this.native = function () {
if (this._args.length==0)
return "true";
var s = "(";
for (var i=0; i<this._args.length; i++) {
s+=this._args[i].native();
if (i!=this._args.length-1)
s+=" || ";
}
return s+")";
};
this.native3 = function () {
if (this._args.length==0)
return "true";
var s = "(";
for (var i=0; i<this._args.length; i++) {
s+=this._args[i].native3();
if (i!=this._args.length-1)
s+=" || ";
}
return s+")";
};
},
$nor : function () {
this.op = "$nor";
this._ex = function () {
return 0;
};
this.dump = function () {
var s = "!(";
for (var i=0; i<this._args.length; i++) {
s+=this._args[i].dump();
if (i!=this._args.length-1)
s+=" || ";
}
return s+")";
};
this.native = function () {
if (this._args.length==0)
return "true";
var s = "!(";
for (var i=0; i<this._args.length; i++) {
s+=this._args[i].native();
if (i!=this._args.length-1)
s+=" || ";
}
return s+")";
};
this.native3 = function () {
if (this._args.length==0)
return "true";
var s = "!(";
for (var i=0; i<this._args.length; i++) {
s+=this._args[i].native3();
if (i!=this._args.length-1)
s+=" || ";
}
return s+")";
};
},
$where: function () {
this.op = "$where";
this._ex = function () {
return 0;
};
this.dump = function () {
return "where(" + this._args[0].dump() + ")";
};
this.native = this.native3 = function () {
var v = this._args[0]._get();
if (_.isFunction(v))
return '(' + v + ').call(obj)';
if (v instanceof tdb.Code) {
if (_.isFunction(v.code))
return '(function () { with (' + JSON.stringify(v.scope) + ') return (' + v.code + ').call(obj); })()';
return '(function () { with (' + JSON.stringify(v.scope) + ') return (' + v.code + '); }).call(obj)';
}
return '(function () { return (' + v + '); }).call(obj)';
};
},
$regex: function () {
this.op = "$regex";
this._ex = function () {
return 0;
};
this._get = function () {
var match = this._args[0].native();
match = match.replace(/"/g,"");
var opts = this._args.length>1?this._args[1].native():'';
opts = opts.replace(/"/g,"");
return new RegExp(match,opts);
};
this.dump = function () {
return this._args[0].dump() +" ~ " + this._args[1].dump();
};
this.native = function () {
var match = this._args[1].native();
match = match.replace(/\/+/g, "\\/").replace(/\\\\/g,"\\").replace(/^"/,"/").replace(/"$/,"/");
var opts = this._args.length>2?this._args[2].native():'';
opts = opts.replace(/"/g,"");
var s = '('+match+opts+').test('+this._args[0].native() +')';
return s;
};
this.native3 = function () {
var match = this._args[1].native();
match = match.replace(/\/+/g, "\\/").replace(/\\\\/g,"\\").replace(/^"/,"/").replace(/"$/,"/");
var opts = this._args.length>2?this._args[2].native():'';
opts = opts.replace(/"/g,"");
var s = "(function () {\n";
s+= "var v = "+this._args[0].native3()+"\n";
s+= "if (!v) return false;\n";
s+= "for (var i=0; i<v.length; i++ ) {\n";
s+= 'if (('+match+opts+').test(v[i])) return true;\n';
s+= "}\nreturn false;})()";
return s;
};
}
};
_.each(ops, function (op) {
op.prototype.fields = function () {
var f = {};
_.each(this._args, function (a) {
if (a.op=="f")
f[a.f]=1;
else if (a.op!="v") {
var s = a.fields();
_.each(s,function (n,k) {
f[k]=1;
});
}
});
return f;
};
});
var field = function (k) {
this.op = "f";
this.f = k;
this.dump = function () {
return k;
};
this.native = function () {
var path = k.split(".");
var s = "(";
var ps = "obj";
for (var i=0;i<path.length;i++) {
ps+="['"+path[i]+"']";
if (i!=path.length-1)
s+=ps+ " && ";
else
s+= "("+ps+" && "+ps+".valueOf()"+") )";
}
return s;
};
this.native3 = function () {
function steep(ps, path, i) {
ps+="['"+path[i]+"']";
if (path.length==i)
return "";
i++;
var s = "";
s+= "v"+i+"="+ps+"\n";
s+= "if (!v"+i+") return null;\n";
if (path.length!=i) {
s+= "if (Array.isArray(v"+i+")) {\n";
s+= "for (i"+i+"=0; i"+i+"<v"+i+".length; i"+i+"++) {\n";
s+= steep("v"+i+"[i"+i+"]",path,i);
s+= "}\n}\nelse\n{\n";
s+= steep("v"+i,path,i);
s+= "}\n";
} else
s+= "res.push(v"+i+".valueOf())\n";
return s;
}
var path = k.split(".");
if (path.length==1)
return "Array.isArray(obj['"+k+"'])?obj['"+k+"']:[obj['" + k + "'] && obj['"+k+"'].valueOf()]";
var s = "(function () {\n";
var ps = "obj";
s+="var res=[]\n";
for (var i=0; i<path.length; i++) {
s+="var v"+i+",i"+i+";\n";
}
s+=steep(ps,path,0);
return s+"\nreturn [].concat.apply([],res);})()";
};
};
var value = function (v) {
this.op = "v";
this.v = null;
if (_.isNumber(v) || _.isString(v) || _.isFunction(v) || _.isBoolean(v))
this.v = v;
else if (_.isObject(v) && v.valueOf)
this.v = v.valueOf();
else if (v !== null)
console.log("WARNING: Using unrecognized value in query",v);
this._get = function () {
return this.v;
};
this.dump = function () {
return this.v;
};
this.native3 = this.native = function () {
return _.isFunction(this.v) ? this.v.toString() : JSON.stringify(this.v);
};
};
function stree(query,ctx,opf) {
var args = [];
if (_.isArray(query)) {
_.each(query, function (v) {
var sub = stree(v);
_.each(sub,function (v) {
args.push(v);
});
});
} else if (_.isPlainObject(query)) {
var options = null;
_.each(query, function (v,k) {
var op = ops[k];
var n = null;
if (op!=null) {
n = new op();
n._args = stree(v,ctx,k);
if (ctx!=null)
n._args.splice(0,0,ctx);
if (options) {
n._args.push(options);
options = null;
}
args.push(n);
}
else if (k=="$options") {
if (args.length>0)
args[args.length-1]._args.push(new value(v));
options = new value(v);
}
else {
var sub;
if (_.isPlainObject(v)) {
sub = stree(v,new field(k));
if (sub.length==1)
args.push(sub[0]);
else if (sub.length>1) {
n = new ops["$and"];
n._args = sub;
args.push(n);
}
} else if (_.isRegExp(v)) {
n = new ops["$regex"]();
n._args = [new field(k), new value(v.source), new value((v.global?'g':'')+(v.ignoreCase?'i':'')+(v.multiline?'m':''))];
args.push(n);
}
else {
n = new ops["$eq"]();
n._args = [new field(k)];
sub = stree(v);
_.each(sub,function (v) {
n._args.push(v);
});
args.push(n);
}
}
});
} else {
if (_.isRegExp(query)) {
var v, n;
if (opf!="$regex") {
v = query;
n = new ops["$regex"]();
n._args = [new value(v.source), new value((v.global?'g':'')+(v.ignoreCase?'i':'')+(v.multiline?'m':''))];
if (ctx!=null)
n._args.splice(0,0,ctx);
args.push(n);
} else {
v = query.toString();
return [new value(v.substring(1,v.length-1))];
}
} else
return [new value(query)];
}
return args;
}
expo.matcher = function (query) {
var aq = [];
_.each(query, function (v,k) {
var o = {}; o[k]=v;
aq.push(o);
});
var wrap={$and:aq};
var res = stree(wrap);
return res[0];
};
expo.field = field;