• 6
    Like
  • 0
    Comment

この記事は、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のクエリを作れるツールの紹介でした。
提案や意見等あれば是非イシューもしくはプルリクエストを送ってください。お待ちしております!