Lets you write a simple query and run it against any datastore, like ElasticSearch and/or MongoDB.
UniQL parses a simple query langauge into an Abstract Syntax Tree that can then be used to compile a query for a given datastore. This is useful if you want a level of abstraction between your queries and how your data is actually stored/searched. You can also use UniQL to execute the same query against multiple datastores at the same time.
For example:
const parse = require( 'uniql' );
const mongoCompile = require( 'uniql-mongodb' );
const esCompile = require( 'uniql-es' );
// parse a uniql query into an AST
const queryAST = parse( '( height <= 20 or ( favorites.color == "green" and height != 25 ) ) and firstname ~= "o.+"' );
// using that AST, compile a mongodb query
const mongoQuery = mongoCompile( queryAST );
console.log( util.inspect( mongoQuery, { depth: null } ) );
// using the same AST, compile an elasticsearch query
const esQuery = esCompile( queryAST );
console.log( util.inspect( esQuery, { depth: null } ) );
For MongoDB, the query generated is:
{ '$or':
[ { height: { '$lte': 20 } },
{ 'favorites.color': 'green', height: { '$ne': 25 } } ],
firstname: { '$regex': 'o.+' } }
For ElasticSearch, the same query is:
{ query:
{ filtered:
{ filter:
[ { bool:
{ must:
[ { bool:
{ should:
[ { range: { height: { lte: 20 } } },
{ bool:
{ must:
[ { term: { 'favorites.color': 'green' } },
{ bool: { must_not: { term: { height: 25 } } } } ] } } ] } },
{ bool: { must: { regexp: { firstname: 'o.+' } } } } ] } } ] } } }
All generated from one simple query:
( height <= 20 or ( favorites.color == "green" and height != 25 ) ) and firstname ~= "o.+"
Which produces the following AST:
{ type: '&&',
arguments:
[ { type: 'EXPRESSION',
arguments:
[ { type: '||',
arguments:
[ { type: '<=',
arguments:
[ { type: 'SYMBOL', arguments: [ 'height' ] },
{ type: 'NUMBER', arguments: [ '20' ] } ] },
{ type: 'EXPRESSION',
arguments:
[ { type: '&&',
arguments:
[ { type: '==',
arguments:
[ { type: 'SYMBOL', arguments: [ 'favorites.color' ] },
{ type: 'STRING', arguments: [ 'green' ] } ] },
{ type: '!=',
arguments:
[ { type: 'SYMBOL', arguments: [ 'height' ] },
{ type: 'NUMBER', arguments: [ '25' ] } ] } ] } ] } ] } ] },
{ type: 'MATCH',
arguments:
[ { type: 'SYMBOL', arguments: [ 'firstname' ] },
{ type: 'STRING', arguments: [ 'o.+' ] } ] } ] }
Using that AST, you can generate queries for various datastores.
- MongoDB
- ElasticSearch
- JavaScript
- !! Your compiler here! Submissions welcome! !!
Values | Description |
---|---|
43, -1.234 | Numbers |
true, false | Booleans |
null, undefined | Primitives |
"hello" | Strings |
foo, a.b.c | Symbols (usually a key or column name in your datastore) |
Operators | Description |
---|---|
x == y | Equality |
x != y | Ineqaulity |
x ~= "y" | Matching evaluated as a RegExp |
x < y | Less than |
x <= y | Less than or equal to |
x > y | Greater than |
x >= y | Greater than or equal to |
x or y | Boolean or |
x and y | Boolean and |
not x | Boolean not |
( x ) | Expression |
Operator precedence follows that of any sane language.
This is an early pass at this. We're very open to getting pull requests to help us improve.
Things we'd love to get to, but would also welcome PRs for:
- Maybe specifying the grammar using PEG would be cleaner/clearer
- Improvements/expansions to the query syntax
- Certain edge case support for things like unary not in the MongoDB driver
This was inspired by FiltrES which was in turn inspired by Filtrex