1.3.4 • Published 5 years ago
elastic-query-parser v1.3.4
BETA ELASTIC-QUERY-PARSER
Parse query to elasticsearch query DSL
Usage
With elasticsearch schema
const OrderElasticSchema = {
type : 'object',
properties : {
shop_id : { type : 'long' },
id : { type : 'long' },
customer : {
type : 'object',
properties : {
id : { type : 'long' },
email : { type : 'string' },
name : { type : 'string' },
phone : { type : 'string' }
}
},
line_items : {
type : 'nested',
properties : {
id : { type : 'long' },
barcode : { type : 'string' },
quantity : { type : 'long' }
}
},
created_at : { type : 'date' },
updated_at : { type : 'date' },
status : { type : 'string' },
private_field : { type : 'long' },
order_number : { type : 'string' },
location_id : { type : 'integer' },
is_deleted : { type : 'boolean' }
}
};
You can create a parser
const parse = Parser({
schema : OrderElasticSchema,
required : ['shop_id'],
blackList : ['private_field'],
whiteList : ['*'],
alias : {
barcode : 'line_items.barcode'
},
defaults : {
page : 1,
limit : 20,
sort : 'created_at_asc',
is_deleted : false
},
deniedValues : ['', null, undefined],
custom : {
keyword : (value) => { return { "query" : { "must" : [{ "match" : {
"fields" : ["customer.name", "customer.phone", "customer.email"],
"query" : value
}}]}}
}}
});
That parse query object to elasticsearch filter
it ('should parse query to elasticsearch query DSL successfully', async () => {
let query = {
'shop_id' : '100000001',
'created_at_gte' : '2019-04-01T03:15:00.000Z',
'created_at_lte' : '2019-04-30T03:15:00.000Z',
'updated_at_from_date' : '2019-04-01T03:15:00.000Z',
'updated_at_to_date' : '2019-04-30T03:15:00.000Z',
'customer.id' : '10000',
'customer.name_like' : 'hoang',
'barcode' : 'HEO',
'status_ne' : 'NEW',
'location_id_in' : '1000,2000',
'keyword' : '0969728159',
// pagination
'page' : '2',
'limit' : '20',
'sort' : 'created_at_asc,id_desc',
'fields' : '-customer',
};
let { errors, page, queryDSL, fields, skip, limit, sort } = parse(query);
let expectedQueryDSL = {
"query": {
"bool": {
"must" : [
{
"query_string": {
"fields": ["customer.name"],
"query": "hoang",
"minimum_should_match": "100%",
"analyzer": "search_whitespace"
}
},
{
"match": {
"fields": ["customer.name", "customer.phone", "customer.email"],
"query": "0969728159"
}
}
],
"filter": {
"bool": {
"must": [
{
"term": {
"is_deleted": false
}
},
{
"term": {
"shop_id": 100000001
}
},
{
"range": {
"created_at": {
"gte": "2019-04-01T03:15:00.000Z"
}
}
},
{
"range": {
"created_at": {
"lte": "2019-04-30T03:15:00.000Z"
}
}
},
{
"range": {
"updated_at": {
"gte": new Date("2019-03-31T17:00:00.000Z")
}
}
},
{
"range": {
"updated_at": {
"lte": new Date("2019-04-30T16:59:59.999Z")
}
}
},
{
"term": {
"customer.id": 10000
}
},
{
"nested": {
"path": "line_items",
"filter": {
"term": {
"line_items.barcode": "HEO"
}
}
}
},
{
"terms": {
"location_id": [
1000,
2000
]
}
}
],
"must_not": [
{
"term": {
"status": "NEW"
}
}
]
}
}
}
}
};
assert.equal(errors, null);
assert.deepEqual(queryDSL, expectedQueryDSL);
assert.deepEqual(fields, { private_field : 0, customer : 0 });
assert.equal(page, 2);
assert.equal(skip, 20);
assert.equal(limit, 20);
assert.deepEqual(sort, ['created_at:asc', 'id:desc', '_score:desc']);
});
And prevent wrong query
it ('should parse query to elasticsearch query DSL fail when mis required field and use wrong operator', () => {
let query = {
'customer.name_gte' : 'hoang',
'private_field_gt' : '10',
'unknown_field_lt' : '0'
};
let { errors, filter } = parse(query);
let expectedErrors = [
{
code : 'ERR_WRONG_OPERATOR',
field : 'customer.name',
type : 'string',
operator : 'gte',
message : `Can't use operator gte on customer.name has type string`
},
{
code : 'ERR_UNAVAILABLE_FIELD',
field : 'private_field',
message : `Can't search on field private_field`
},
{
code : 'ERR_INVALID_FIELD',
field : 'unknown_field',
message : `Invalid field unknown_field`
},
{
code : 'ERR_REQUIRED',
field : 'shop_id',
message : 'shop_id is required'
}
];
assert.deepEqual(errors, expectedErrors);
});
Support permission on operators : equal, ne, in, nin
it ('should return not permission error with operator equal', () => {
let query = {
shop_id : 1000001,
location_id : '2000',
};
let { errors, filter } = parse(query, {
permission : {
location_id : [1000, 3000],
}
});
assert.deepEqual(errors, [{
code : 'ERR_NOT_PERMISSION',
field : 'location_id',
value : 2000,
message : `Can't see item has location_id = 2000`
}]);
});
it ('should return not permission error with operator in', () => {
let query = {
shop_id : 1000001,
location_id_in : '1000,2000',
};
let { errors, filter } = parse(query, {
permission : {
location_id : [1000, 3000],
}
});
assert.deepEqual(errors, [{
code : 'ERR_NOT_PERMISSION',
field : 'location_id',
value : 2000,
message : `Can't see item has location_id = 2000`
}]);
});
it ('should auto assign field has permission to filter that not exists in query', () => {
let query = {
shop_id : 100000001
};
let { errors, queryDSL } = parse(query, {
permission : {
location_id : [1000, 3000],
}
});
assert.deepEqual(queryDSL, {
"query" : {
"bool" : {
"filter" : {
"bool": {
"must": [
{
"term" : {
"is_deleted" : false
}
},
{
"term": {
"shop_id": 100000001
}
},
{
"terms" : {
"location_id" : [1000, 3000]
}
}
]
}
}
}
}
})
});
Support aggregations
it ('should generate simple elasticsearch aggregation and flatten function', () => {
let xQuery = {
shop_id : 100000001,
created_at_gte : "2019-04-01",
created_at_lte : "2019-05-30",
$group : [
{ month : {
date_histogram : {
field : 'created_at',
interval : 'month',
format : 'MM/yyyy',
}
}},
{ location : 'location_id' }
],
$metrics : [
{ total : { count : '*' }},
{ revenue : { sum : 'total_price' }}
]
};
let { queryDSL, flatten } = parse(xQuery);
assert.deepEqual(queryDSL, {
"query": {
"bool": {
"filter": {
"bool" : {
"must": [
{
"term" : {
"is_deleted" : false
}
},
{
"term": {
"shop_id": 100000001
}
},
{
"range": {
"created_at": {
"gte": "2019-04-01",
}
},
},
{
"range": {
"created_at": {
"lte": "2019-05-30",
}
}
}
]
}
}
}
},
"size": 0,
"aggs": {
"month": {
"date_histogram": {
"field": "created_at",
"interval": "month",
"time_zone": "+07:00",
"format": "MM/yyyy",
"order": {
"_key": "desc"
}
},
"aggs": {
"location": {
"terms": {
"field": "location_id"
},
"aggs": {
"revenue" : {
"sum" : {
"field" : "total_price"
}
}
}
}
}
}
}
});
let simple_es_res = {
"took": 11,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"hits": {
"total": 135,
"max_score": 0,
"hits": []
},
"aggregations": {
"month": {
"buckets": [
{
"key_as_string": "05/2019",
"key": 1556643600000,
"doc_count": 17,
"location": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "482663",
"doc_count": 17,
"revenue": {
"value": 12100000
}
}
]
}
},
{
"key_as_string": "04/2019",
"key": 1554051600000,
"doc_count": 118,
"location": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "482663",
"doc_count": 105,
"revenue": {
"value": 226919200
}
},
{
"key": "483179",
"doc_count": 8,
"revenue": {
"value": 4794000
}
},
{
"key": "482668",
"doc_count": 5,
"revenue": {
"value": 9100000
}
}
]
}
}
]
}
}
};
let expected_flatten_items = [
{ month : '05/2019', location : '482663', total : 17 , revenue : 12100000 },
{ month : '04/2019', location : '482663', total : 105, revenue : 226919200 },
{ month : '04/2019', location : '483179', total : 8 , revenue : 4794000 },
{ month : '04/2019', location : '482668', total : 5 , revenue : 9100000 },
];
let expected_flatten_rows = [
['05/2019','482663', 17 , 12100000 ],
['04/2019','482663', 105, 226919200],
['04/2019','483179', 8 , 4794000 ],
['04/2019','482668', 5 , 9100000 ],
];
let { items, cols, rows } = flatten(simple_es_res, { genRows : true });
assert.deepEqual(cols, ['month', 'location', 'total', 'revenue']);
assert.deepEqual(items, expected_flatten_items);
assert.deepEqual(rows, expected_flatten_rows);
});
Generate tracing info
it ('should generate tracing info', () => {
let query = {
'shop_id' : '100000001',
'created_at_gte' : '2019-04-01T03:15:00.000Z',
'created_at_lte' : '2019-04-30T03:15:00.000Z',
'updated_at_from_date' : '2019-04-01T03:15:00.000Z',
'updated_at_to_date' : '2019-04-30T03:15:00.000Z',
'customer.id' : '10000',
'customer.name_like' : 'hoang',
'barcode' : 'HEO',
'status_ne' : 'NEW',
'location_id_in' : '1000,2000',
'tags_all' : 'food,sea',
'keyword' : '0969728159',
};
let { trace } = parse(query, { isTrace : true });
assert.deepEqual(trace, {
'is_deleted' : { field : 'is_deleted' , alias : undefined, operator : '' , raw_value : false , type : 'boolean', value : false , filter : { must : { term : { is_deleted : false } } } },
'shop_id' : { field : 'shop_id' , alias : undefined, operator : '' , raw_value : '100000001' , type : 'long' , value : 100000001 , filter : { must : { term : { shop_id : 100000001 } } } },
'created_at_gte' : { field : 'created_at' , alias : undefined, operator : 'gte' , raw_value : '2019-04-01T03:15:00.000Z' , type : 'date' , value : '2019-04-01T03:15:00.000Z' , filter : { must : { range : { created_at : { gte : '2019-04-01T03:15:00.000Z' } }}} },
'created_at_lte' : { field : 'created_at' , alias : undefined, operator : 'lte' , raw_value : '2019-04-30T03:15:00.000Z' , type : 'date' , value : '2019-04-30T03:15:00.000Z' , filter : { must : { range : { created_at : { lte : '2019-04-30T03:15:00.000Z' } }}} },
'updated_at_from_date' : { field : 'updated_at' , alias : undefined, operator : 'from_date' , raw_value : '2019-04-01T03:15:00.000Z' , type : 'date' , value : new Date("2019-03-31T17:00:00.000Z") , filter : { must : { range : { updated_at : { gte : new Date('2019-03-31T17:00:00.000Z') } }}} },
'updated_at_to_date' : { field : 'updated_at' , alias : undefined, operator : 'to_date' , raw_value : '2019-04-30T03:15:00.000Z' , type : 'date' , value : new Date("2019-04-30T16:59:59.999Z") , filter : { must : { range : { updated_at : { lte : new Date('2019-04-30T16:59:59.999Z') } }}} },
'customer.id' : { field : 'customer.id' , alias : undefined, operator : '' , raw_value : '10000' , type : 'long' , value : 10000 , filter : { must : { term : { 'customer.id' : 10000 }}} },
'customer.name_like' : { field : 'customer.name' , alias : undefined, operator : 'like' , raw_value : 'hoang' , type : 'string' , value : 'hoang' , query : { must : { query_string : { fields : ["customer.name"], query : "hoang", minimum_should_match : "100%", analyzer : "search_whitespace" }}}},
'barcode' : { field : 'line_items.barcode', alias : 'barcode', operator : '' , raw_value : 'HEO' , type : 'string' , value : 'HEO' , filter : { must : { term : { 'line_items.barcode' : 'HEO' } }} },
'status_ne' : { field : 'status' , alias : undefined, operator : 'ne' , raw_value : 'NEW' , type : 'string' , value : 'NEW' , filter : { must_not : { term : { status : 'NEW' }}} },
'location_id_in' : { field : 'location_id' , alias : undefined, operator : 'in' , raw_value : '1000,2000' , type : 'integer', value : [1000,2000] , filter : { must : { terms : { location_id : [1000, 2000] } }} },
'tags_all' : { field : 'tags' , alias : undefined, operator : 'all' , raw_value : 'food,sea' , type : 'string' , value : ['food', 'sea'] , filter : { must : [ { term : { tags : 'food' }}, { term : { tags : 'sea' }}] } },
'keyword' : { field : undefined , alias : undefined, operator : 'custom' , raw_value : '0969728159' , type : undefined, value : '0969728159' , query : { must : [{ match : { fields : ['customer.name', 'customer.phone', 'customer.email'], query : '0969728159' }}]} },
});
});
Full API documents is coming soon ...
Testing
npm test
1.3.4
5 years ago
1.3.3
5 years ago
1.3.2
5 years ago
1.3.1
5 years ago
1.3.0
5 years ago
1.2.11
5 years ago
1.2.10
5 years ago
1.2.9
5 years ago
1.2.8
5 years ago
1.2.7
5 years ago
1.2.6
5 years ago
1.2.5
5 years ago
1.2.4
5 years ago
1.2.3
5 years ago
1.2.2
5 years ago
1.2.1
5 years ago
1.1.2
5 years ago
1.1.1
5 years ago
1.1.0
5 years ago
1.0.10
5 years ago
1.0.9
5 years ago
1.0.8
5 years ago
1.0.7
5 years ago
1.0.6
5 years ago
1.0.5
5 years ago
1.0.4
5 years ago
1.0.3
5 years ago
1.0.2
5 years ago
1.0.1
5 years ago
1.0.0
5 years ago