Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
eugeneware committed Jul 29, 2013
0 parents commit e127248
Show file tree
Hide file tree
Showing 6 changed files with 630 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
*.swp
.DS_Store
node_modules/
4 changes: 4 additions & 0 deletions data/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Ignore everything in this directory
*
# Except this file
!.gitignore
116 changes: 116 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
var jsonquery = require('jsonquery'),
jsonqueryPlan = require('./jsonquery-plan'),
through = require('through');

module.exports = jsonqueryEngine;
function jsonqueryEngine() {
return {
query: query,
match: jsonquery.match,
plans: {
'property': propertyPlan
}
};
}

function keyfn(index) {
return index.key[index.key.length - 1];
}

function valfn(index) {
return index.key[index.key.length - 2];
}

function propertyPlan(idx, prop, op, value, negate) {
var db = this;
var ops = {
$gt: function (a, b) { return a > b; },
$gte: function (a, b) { return a >= b; },
$lt: function (a, b) { return a < b; },
$lte: function (a, b) { return a <= b; },
$mod: function (a, b) { return a % b[0] === b[1]; }
};

var negOps = {
$gt: '$lte',
$gte: '$lt',
$lt: '$gte',
$lte: '$gt'
};

if (negate) {
if (op in negOps) {
// transform to negated equivalent
op = negOps[op];
// don't negate predicate as transform already done
negate = !negate;
}
}

function indexFilterStream() {
return through(function (data) {
var val = valfn(data);
if (ops[op](val, value) === (!negate)) {
this.queue(data);
}
});
}

switch (op) {
case '$eq':
if (value instanceof RegExp) {
return db.indexes[idx].createIndexStream()
.pipe(through(function (data) {
var val = valfn(data);
if (value.test(val)) {
this.queue(data);
}
}));
} else {
return db.indexes[idx].createIndexStream({
start: [value, null],
end: [value, undefined]
});
}
break;

case '$gt':
case '$gte':
return db.indexes[idx].createIndexStream({
start: [value, null],
end: [undefined, undefined]
})
.pipe(indexFilterStream());

case '$lt':
case '$lte':
return db.indexes[idx].createIndexStream({
start: [null, null],
end: [value, undefined]
})
.pipe(indexFilterStream());

case '$mod':
return db.indexes[idx].createIndexStream()
.pipe(indexFilterStream());
}

return null;
}

function plan(prop, op, value, negate) {
var db = this;
var idx = db.indexes[prop];
if (idx && idx.type in db.query.engine.plans) {
return db.query.engine.plans[idx.type].call(db, prop, prop, op, value, negate);
} else if ((idx = db.indexes['*']) && idx.type in db.query.engine.plans) {
return db.query.engine.plans[idx.type].call(db, '*', prop, op, value, negate);
} else {
return null;
}
}

function query(q) {
var db = this;
return jsonqueryPlan(q, plan.bind(db));
}
185 changes: 185 additions & 0 deletions jsonquery-plan.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
var orStream = require('joiner-stream'),
andStream = require('and-stream');

function keyfn(index) {
return index.key[index.key.length - 1];
}

module.exports = optimizer;
function optimizer (query, plan) {
return optimizerMatch(query, false, plan);
}

function optimizerMatch (predicate, negate, plan) {
var tasks = [], idxStream;
// { k: v, k2: v2 }
// effectively an $and
var sources = [];
for (var n in predicate) {
var v = predicate[n];
if (n[0] === '$') {
idxStream = optimizerOperator(n, v, negate, plan);
if (idxStream) sources.push(idxStream);
} else if (v.constructor === Object) {
idxStream = optimizeValOpMatch(n, v, negate, plan);
if (idxStream) sources.push(idxStream);
} else {
idxStream = plan(n, '$eq', v, negate);
if (idxStream) sources.push(idxStream);
}
}

if (sources.length === 0) return null;
if (sources.length === 1) return sources[0];
var and = andStream(keyfn);
for (var i = 0; i < sources.length; i++) {
var source = sources[i];
source.pipe(and.stream());
}
return and;
}

function optimizerOperator (op, predicate, negate, plan) {
var i, task, idxStream;
switch (op) {
case '$or':
case '$and':
if (negate) {
if (op === '$or') op = '$and';
else op = '$or';
}
var opStream = (op === '$or') ?
orStream() : andStream(keyfn);
var sources = [];
for (i = 0, len = predicate.length; i < len; i++) {
var part = predicate[i];
idxStream = optimizerMatch(part, negate, plan);
if (idxStream) sources.push(idxStream);
}
if (sources.length === 0) return null;
if (sources.length === 1) return sources[0];
for (i = 0; i < sources.length; i++) {
var source = sources[i];
source.pipe(op === '$or' ? opStream : opStream.stream());
}
return opStream;

case '$not':
return optimizerMatch(predicate, !negate, plan);
}
}

function optimizeValOpMatch (val, predicate, negate, plan) {
// { $in: [], $gt : 3 }
// effectively an $and
var idxStream, sources = [];
for (var n in predicate) {
var v = predicate[n];
if (n[0] === '$') {
idxStream = optimizeValOp(n, val, v, negate, plan);
if (idxStream) sources.push(idxStream);
}
}
if (sources.length === 0) return null;
if (sources.length === 1) return sources[0];
var and = andStream(keyfn);
for (var i = 0; i < sources.length; i++) {
var source = sources[i];
source.pipe(and.stream());
}
return and;
}

function optimizeValOp (op, val, args, negate, plan) {
var part, task, tasks, i, path, arg, and, stream, sources, source, idxStream;
switch (op) {
case '$in':
sources = [];
if (Array.isArray(args)) {
for (i = 0; i < args.length; i++) {
arg = args[i];
idxStream = plan(val, '$eq', arg, negate);
if (idxStream) sources.push(idxStream);
}
}
if (sources.length === 0) return null;
if (sources.length === 1) return sources[0];
var or = orStream();
for (i = 0; i < sources.length; i++) {
source = sources[i];
source.pipe(or);
}
return or;

case '$nin':
return plan(val, args, args, !negate);

case '$gt':
case '$gte':
case '$ne':
case '$lt':
case '$lte':
case '$mod':
return plan(val, op, args, negate);

case '$all': // { favorites: { $all: [50, 60] } }
sources = [];
for (i = 0, len = args.length; i < len; i++) {
arg = args[i];
idxStream = plan(val, '$eq', arg, negate);
if (idxStream) sources.push(idxStream);
}
if (sources.length === 0) return null;
if (sources.length === 1) return sources[0];
and = andStream(keyfn);
for (i = 0; i < sources.length; i++) {
source = sources[i];
source.pipe(and.stream());
}
return and;

case '$elemMatch':
sources = [];
for (part in args) {
var v = args[part];
var key = [val, part].join('.');
var p = {};
p[key] = v;
idxStream = optimizerMatch (p, negate, plan);
if (idxStream) sources.push(optimizerMatch (p, negate, plan));
}
if (sources.length === 0) return null;
if (sources.length === 1) return sources[0];
and = andStream(keyfn);
for (i = 0; i < sources.length; i++) {
source = sources[i];
source.pipe(and.stream());
}
return and;

case '$or':
case '$and':
if (negate) {
if (op === '$or') op = '$and';
else op = '$or';
}
sources = [];
for (i = 0, len = args.length; i < len; i++) {
arg = args[i];
idxStream = optimizeValOpMatch(val, arg, negate, plan);
if (idxStream) sources.push(idxStream);
}
if (sources.length === 0) return null;
if (sources.length === 1) return sources[0];
var opStream = (op === '$or') ?
orStream() : andStream(keyfn);
for (i = 0; i < sources.length; i++) {
source = sources[i];
source.pipe(op === '$or' ? opStream : opStream.stream());
}
return opStream;

case '$not':
return optimizeValOpMatch(val, args, !negate, plan);
}
}
38 changes: 38 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"name": "jsonquery-engine",
"version": "0.0.1",
"description": "level-queryengine plugin to query levelup/leveldb using indexes with the mongodb query syntax (through the jsonquery module)",
"main": "index.js",
"scripts": {
"test": "node_modules/.bin/mocha"
},
"repository": {
"type": "git",
"url": "https://github.com/eugeneware/jsonquery-engine.git"
},
"keywords": [
"levelup",
"leveldb",
"jsonquery",
"mongodb",
"query",
"index",
"engine",
"query-engine",
"indexes",
"indexing",
"search"
],
"author": "Eugene Ware <eugene@noblesamurai.com>",
"license": "BSD",
"dependencies": {
"jsonquery": "0.0.6",
"through": "~2.3.4"
},
"devDependencies": {
"chai": "~1.7.2",
"mocha": "~1.12.0",
"rimraf": "~2.2.2",
"after": "~0.8.1"
}
}
Loading

0 comments on commit e127248

Please sign in to comment.