サマリ
dynamic templateを使えば、mappingを動的に決められます。
決め方は次の3つがあります。
match_mapping_type
:デフォルトのmappingをもとに、mappingを割り当てます。match
,unmatch
:フィールド名から、mappingを割り当てます。path_match
,path_unmatch
:フィールドのパスから、mappingを割り当てます。
https://www.elastic.co/guide/en/elasticsearch/reference/current/dynamic-templates.html
型からmappingを決める
elasticsearchはJSONの型から、mappingをどう設定するかデフォルトで決まっています。
この設定を上書きするのが、 match_mapping_type
です。
次の例では、 string
, long
とmappingされるフィールドの場合、 text
, integer
とmappingされるようにしたものです。text
はさらに、マルチフィールドになるようにしています。
PUT demo-index { "mappings": { "dynamic_templates": [ { "integers": { "match_mapping_type": "long", "mapping": { "type": "integer" } } }, { "strings": { "match_mapping_type": "string", "mapping": { "type": "text", "fields": { "raw": { "type": "keyword", "ignore_above": 256 } } } } } ] } }
実際にdocumentを保存して、mappingがどうなるか確認してみました。
PUT demo-index/_doc/1 { "demo-integer": 1, "demo-string": "Hello, Elasticsearch" } GET demo-index/_mapping // レスポンス { "demo-index": { "mappings": { "dynamic_templates": [ { "integers": { "match_mapping_type": "long", "mapping": { "type": "integer" } } }, { "strings": { "match_mapping_type": "string", "mapping": { "fields": { "raw": { "ignore_above": 256, "type": "keyword" } }, "type": "text" } } } ], "properties": { "demo-integer": { "type": "integer" }, "demo-string": { "type": "text", "fields": { "raw": { "type": "keyword", "ignore_above": 256 } } } } } } }
demo-integer
はintegerに、 demo-string
はtextに、それぞれmappingされていることがわかります。これらのフィールドに対するmappingは事前に無かったのでdynamic templateによりmappingが動的に決定されたことになります。
フィールド名からmappingを決める
match
, unmatch
を使うと、フィールド名から型を決定できます。
match
を使って、 long_
から始まるフィールドをlong型にmappingするよう設定してみました。
PUT demo-index { "mappings": { "dynamic_templates": [ { "longs_as_string": { "match": "long_*", "mapping": { "type": "long" } } } ] } }
この場合、文字列でドキュメントを作成したとしても、フィールド名が一致する場合はlong型にmappingされます。
PUT demo-index/_doc/1 { "long_number": "1" <-① } GET demo-index/_mapping // レスポンス { "demo-index": { "mappings": { "dynamic_templates": [ { "longs_as_string": { "match": "long_*", "mapping": { "type": "long" } } } ], "properties": { "long_number": { "type": "long" <-② } } } } }
① long_
から始まるフィールド名 long_number
で、文字列の値を保存します。
② long_number
が、long型にmappingされています。
ドキュメントを取得する場合には、文字列として返されます。あくまでもmappingをlong型にしているのであり、値を変換しているのではない、ということですかね。
GET demo-index/_doc/1/_source // レスポンス { "long_number": "1" }
mappingでlong型になっているので、rangeで検索できます。
GET demo-index/_search { "query": { "range": { "long_number": { "gte": 0, "lte": 2 } } } } // レスポンス { "took": 2, "timed_out": false, "_shards": { "total": 1, "successful": 1, "skipped": 0, "failed": 0 }, "hits": { "total": { "value": 1, "relation": "eq" }, "max_score": 1, "hits": [ { "_index": "demo-index", "_type": "_doc", "_id": "1", "_score": 1, "_source": { "long_number": "1" } } ] } }
では、数値ではない値を指定するとどうなるのでしょうか?
PUT demo-index/_doc/2 { "long_number": "abc" } // レスポンス { "error": { "root_cause": [ { "type": "mapper_parsing_exception", "reason": "failed to parse field [long_number] of type [long] in document with id '2'. Preview of field's value: 'abc'" } ], "type": "mapper_parsing_exception", "reason": "failed to parse field [long_number] of type [long] in document with id '2'. Preview of field's value: 'abc'", "caused_by": { "type": "illegal_argument_exception", "reason": "For input string: \"abc\"" } }, "status": 400 }
ステータス400で、エラーが返ってきました。 error.reason
に値をパースできないと書いてあります。long値としてパースできる文字列でなければドキュメントを保存することができなくなるようですね。
パスからmappingを決める
ドキュメントのJSONパスからmappingを決めることもできます。
以下は、 *.middle
を除く name.*
のパスに一致するフィールドをtext型にmappingする例です。 path_match
と path_unmatch
を使ってそれらの条件を指定しています。
PUT demo-index { "mappings": { "dynamic_templates": [ { "full_name": { "path_match": "name.*", <--① "path_unmatch": "*.middle", <--② "mapping": { "type": "keyword" } } } ] } }
① 一致する条件をパスで指定します。
② 一致しない条件をパスで指定します。
ドキュメントを保存してmappingの状態を確認してみました。
PUT demo-index/_doc/1 { "name": { "first": "John", "middle": "Winston", "last": "Lennon" } } GET demo-index/_mapping // レスポンス { "demo-index": { "mappings": { "dynamic_templates": [ { "full_name": { "path_match": "name.*", "path_unmatch": "*.middle", "mapping": { "type": "keyword" } } } ], "properties": { "name": { "properties": { "first": { "type": "keyword" <--① }, "last": { "type": "keyword" <--① }, "middle": { "type": "text", <--② "fields": { "keyword": { "type": "keyword", "ignore_above": 256 } } } } } } } } }
① dynamic_templateで指定した、パスに一致しているフィールドのmappingが、keywordになっています。
② 一方、path_unmatch
で明示的に除いているパスに一致しているフィールドのmappingは、Elasticsearchのデフォルトのままになっています。
ここで、 path_match
に一致するがkeywordとして解釈できない値(例えばJSONオブジェクト)を与えるとどうなるのでしょうか。
PUT demo-index/_doc/2 { "name": { "first": "John", "middle": "Winston", "last": "Lennon", "other": { "nickname": "JWL" } } } // レスポンス { "error": { "root_cause": [ { "type": "mapper_parsing_exception", "reason": "failed to parse field [name.other] of type [keyword] in document with id '2'. Preview of field's value: '{nickname=JWL}'" } ], "type": "mapper_parsing_exception", "reason": "failed to parse field [name.other] of type [keyword] in document with id '2'. Preview of field's value: '{nickname=JWL}'", "caused_by": { "type": "illegal_state_exception", "reason": "Can't get text on a START_OBJECT at 6:18" } }, "status": 400 }
エラーが返され保存できませんでした。 error.reason
に、 name.other
の値をkeyword型にパースできないと書かれています。これはフィールド名でmappingを決めたときにも同じようなエラーがありましたが、パスでmappingを決める時も同様にパースできない値が含まれているとエラーになるようですね。今回のようにワイルドカードを使って一定の範囲のパスに対してmappingを決める場合には注意が必要です。