-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit e127248
Showing
6 changed files
with
630 additions
and
0 deletions.
There are no files selected for viewing
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
*.swp | ||
.DS_Store | ||
node_modules/ |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
# Ignore everything in this directory | ||
* | ||
# Except this file | ||
!.gitignore |
This file contains 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
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)); | ||
} |
This file contains 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
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); | ||
} | ||
} |
This file contains 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
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" | ||
} | ||
} |
Oops, something went wrong.