Edited at

JavaScriptでElasticsearchのクエリを作る

More than 1 year has passed since last update.

この記事は、Elastic stack Advent Calendar 2016の13日目の記事です。

作ったツールをこの記事で紹介しようと思います。

GitHub: es-builder

このツールを使ってJavaScriptでElasticsearchのクエリを簡単に作成できます。

特に作成できるのが、Query DSL経由で作る部分です (bodyのquery項目)。


Motivation

弊社で開発しているサービスでElasticsearchを導入することになったことで、クエリ作成も含めてNode.jsで公式のクライアントを使って、検索ロジックを実装しました。

簡単なクエリなら公式クライアントで十分かもしれませんが、複雑なクエリを作るならツールが必要となると思います。直接JSONでかくと、間違えやすいし、かなり冗長になります。

elastic.jsを使おうと思いましたが、2年ぐらいメンテされなくて、Query DSL 2.xに互換性がありません。他に良さそうなツールもありましたが、ネストクエリができなかったりだったので、結局自分で作ることにしました。


基本の使い方

下記のJSONクエリを

 {

"bool": {
"must": [{
"term": {
"name": "承太郎"
}
}, {
"match": {
"description": {
"query": "やれやれだぜ"
}
}
}],
"must_not": {
"term": {
"location": "杜王町"
}
}
}
}

es-builderで作りましょう。

const eb = require('es-builder');

const elasticsearch = require('elasticsearch');

const client = new elasticsearch.Client({
host: 'localhost:9200'
});

const query = eb.QueryBuilder()
.query(eb.TermQuery('name', '承太郎'))
.query(eb.MatchQuery('description', 'やれやれだぜ'))
.queryMustNot(eb.TermQuery('location', '杜王町'));

作られたqueryインスタンスをstringifyすると、上記と同じJSONが作られます。

JSON.stringify(query);

// {
// "bool": {
// "must": [{
// "term": {
// ...
// ...
// }
// }

内部的にElasticsearchのライブラリに渡されるJSのオブジェクトのbodyJSONにコンバートしてくれるので、 JSON.stringifyせずにそのまま作ったqueryインスタンスを渡してもいいです!

client.search({

index: 'user',
type: 'stand_user',
body: {
query: query
}
}, function(err, resp) {
// result of the search here
...
});

上記に作ったクエリは固定していましたが、インプット等で可変なクエリのJSONを作るのは複雑になると思います。その場合もes-builderを使うと楽になります。


ネストクエリ

ネストクエリも作成可能です。

const eb = require('es-builder');

const query = eb.QueryBuilder();

// フィルターを追加する
query
.filter(eb.TermsQuery('name', ['ジョセフ', 'ジョナサン']))
.filter(eb.ExistsQuery('age'));

// bool queryを作る
const boolQuery = eb.BoolQuery()
.should(eb.RangeQuery('age').gt(20).lt(40))
.should(eb.PrefixQuery('surname', 'ジョ'));

// ネストする
query.filter(boolQuery);

// queryインスタンスはstringifyすると
// JSON.stringify(query)
// {
// "bool": {
// "filter": {
// bool: {
// "must": [{
// "terms": {
// "name": {
// "value": ["ジョセフ", "ジョナサン"]
// }
// }
// }, {
// "exists": {
// "field": "age"
// }
// }, {
// "bool": {
// "should": [{
// "range": {
// "age": { "gt": 20, "lt": 40 }
// }
// }, {
// "prefix": {
// "surname": {
// "value": "ジョ"
// }
// }
// }]
// }
// }]
// }
// }
// }
// }


REPL

global moduleとしてインストールすれば、REPL経由でクエリを作成できます。

REPLの場合だと全クエリはexposeされたので、eb.なしでそのまま使えます。

$ npm install -g es-builder

...

$ es-builder

es-builder> query = QueryBuilder().query(TermQuery('name', 'カービィ')).query(MatchQuery('description', 'まるくて、ふわふわしてる')).queryMustNot(TermQuery('name', 'ワドルディ'));

es-builder> query.stringified
'{"bool":{"must":[{"term":{"name":{"value":"カービィ"}}},{"match":{"description":{"query":"まるくて、ふわふわしてる"}}}],"must_not":{"term":{"name":{"value":"ワドルディ"}}}}}'

es-builder> .exit

作られたクエリをコピーして、curlでもで使えます。

$ curl -XGET 'http://localhost:9200/games/dreamland/_search' -d'

{
"query" : {"bool":{"must":[{"term":{"name":{"value":"カービィ"}}},{"match":{"description":{"query":"まるくて、ふわふわしてる"}}}],"must_not":{"term":{"name":{"value":"ワドルディ"}}}}}
}'


おわりに

es-builderを使って、JavascriptでElasticsearchのクエリを作れるツールの紹介でした。

提案や意見等あれば是非イシューもしくはプルリクエストを送ってください。お待ちしております!