elastic-muto
Version:
Easy expressive search queries for Elasticsearch
118 lines (109 loc) • 3.26 kB
JavaScript
;
const debug = require('debug')('elastic-muto');
const isNil = require('lodash.isnil');
const isEmpty = require('lodash.isempty');
const isString = require('lodash.isstring');
const isFunction = require('lodash.isfunction');
const cosmiconfig = require('cosmiconfig');
const mutoParser = require('./muto-parser');
const qryBuilderDef = require('./query-builder-def');
const explorer = cosmiconfig('muto', {
packageProp: false,
rc: false,
js: 'muto.config.js',
argv: false,
sync: true
});
const qryBuilder = Object.assign({}, qryBuilderDef);
try {
debug('Loading user config using cosmiconfig(sync)');
const result = explorer.load('.');
if (!isNil(result)) {
debug('Successfully parsed config -', result);
Object.assign(qryBuilder, result.config);
} else {
debug('muto.config.js not found');
}
} catch (err) {
console.error('Failed to parse config!');
console.error(err);
}
/**
* Parses given expression and generates an `elastic-builder` query object.
*
* @example
* // Pass expression as string
* const qry = muto.parse(
* '["discount"] is false or (["psngr_cnt"] > 81 and ["booking_mode"] contains "Airport")'
* )
*
* // OR
* // Pass conditions using helper classes
* const qry = muto.parse(
* muto.where()
* .or(muto.cn('discount').is(false))
* .or(
* muto.where()
* .and(muto.cn('psngr_cnt', 'gt', 81))
* .and('["booking_mode"] contains "Airport"')
* )
* );
*
* qry.toJSON()
* {
* "bool": {
* "should": [
* { "term": { "discount": false } },
* {
* "bool": {
* "must": [
* {
* "range": { "psngr_cnt": { "gt": 81 } }
* },
* {
* "match": { "booking_mode": "Airport" }
* }
* ]
* }
* }
* ]
* }
* }
*
* @param {string|Where|Condition} expr
* @param {Array} [notAnalysedFields]
* @returns {Object} `elastic-builder` `BoolQuery` object generated by
* parsing the expression
*/
module.exports = function parse(expr, notAnalysedFields) {
if (isEmpty(expr)) {
throw new Error('Expression cannot be empty!');
}
let strExpr;
if (isString(expr)) strExpr = expr;
else if (isFunction(expr.build)) strExpr = expr.build();
else strExpr = expr.toString();
debug("Parsing expression '%s'", strExpr);
/*
Wrap expression in ().
This handles the edge case of
(ConditionA and ConditionB) or (ConditionC and ConditionD)
Was not able to handle in PEG.js due to
GrammarError: Possible infinite loop when parsing (left recursion: Expression -> Expression).
(expr) will be parsed the same as ((expr))
So even if expression is already wrapped, it is okay.
*/
try {
return mutoParser.parse(
`(${strExpr})`,
Object.assign(
{ notAnalysedFields: new Set(notAnalysedFields), debug },
qryBuilder
)
);
} catch (err) {
console.error('Failed to parse expression', strExpr);
err.expression = strExpr;
throw err;
}
};