ES學習記錄11——搜尋模板(Search Template)
1. 搜尋模板
/_search/template
端點允許使用mustache language(繼承ES沙箱指令碼語言的一種語言,是護胡言亂語嗎……)為每個呈現搜尋請求,在執行前和用模板引數填充現有的模板:
curl -X GET "localhost:9200/_search/template" -H 'Content-Type: application/json' -d'
{
"source" : {
"query": { "match" : { "{{my_field}}" : "{{my_value}}" } },
"size" : "{{my_size}}"
} ,
"params" : {
"my_field" : "message",
"my_value" : "some message",
"my_size" : 5
}
}
'
執行這個請求時,ES卡住沒有響應,一直處於loading的狀態。
模板樣例
用單值填充查詢字串
curl -X GET "localhost:9200/_search/template" -H 'Content-Type: application/json' -d'
{
"source": {
"query": {
"term" : {
// 使用佔位符的方式{{xxx}}使用一個變數
"user": "{{query_string}}"
}
}
},
"params": {
// 定義上述使用到的變數
"query_string": "kimchy3"
}
}
'
轉換引數成JSON
{{#toJson}}parameter{{/toJson}}
函式可以用來將map或陣列的引數parameter
轉換成對應的JSON(下面是查詢使用者名稱為kimchy4
和kimchy3
curl -X GET "localhost:9200/_search/template" -H 'Content-Type: application/json' -d'
{
// 將變數users轉成對應的JSON
"source": "{ \"query\": { \"terms\": {{#toJson}}users{{/toJson}} }}",
"params": {
// 將users變數填充到上面的函式中
"users" : {
"user": [ "kimchy4", "kimchy3" ]
}
}
}
'
最終的轉換結果(但是實際是看不到的)為:
// 轉成JSON串
"query": {
"terms": {
"users": {
"user": [
"kimchy4",
"kimchy3"
]
}
}
}
連線陣列的值
{{#join}}array{{/join}}
函式可以用來將陣列array
內的元素連線為一個用逗號,
分隔的字串(twitter
索引中有kimchy1
…kimchy4
、kimchy,5
五個文件),下面是一個栗子:
curl -X GET "localhost:9200/_search/template" -H 'Content-Type: application/json' -d'
{
"source": {
"query": {
"match": {
"user": "{{#join}}user{{/join}}"
}
}
},
"params": {
"user": [ "kimchy", "5" ]
}
}
'
這樣最終傳入的user
變數為kimchy,5
(由陣列[ "kimchy", "5" ]
轉成kimchy,5
),即直接去匹配了。上述是使用預設的分隔符(即逗號,
)來連線陣列各個元素,當前這個分隔符也可以自定義,使用自定義分隔符函式{{#join delimiter='xx'}}date.formats{{/join delimiter='xx'}}
即可將預設的,
改為||
,比如:
curl -X GET "localhost:9200/_search/template" -H 'Content-Type: application/json' -d'
{
"source": {
"query": {
"range": {
"born": {
"gte" : "{{date.min}}",
"lte" : "{{date.max}}",
// 將原來逗號的分隔符改為雙豎槓||
"format": "{{#join delimiter='||'}}date.formats{{/join delimiter='||'}}"
}
}
}
},
"params": {
"date": {
"min": "2016",
"max": "31/12/2017",
"formats": ["dd/MM/yyyy", "yyyy"]
}
}
}
'
born
最終的轉換為:
"born" : {
"gte" : "2016",
"lte" : "31/12/2017",
"format" : "dd/MM/yyyy||yyyy"
}
預設值
預設值的寫法為:{{var}}{{^var}}default{{/var}}
,下面是小栗子:
curl -X GET "localhost:9200/_search/template" -H 'Content-Type: application/json' -d'
{
"source": {
"query": {
// 查詢範圍內獲贊數的文件
"range": {
// 指定按獲贊數進行篩選
"likes": {
// 獲贊數的下限
"gte": "{{start}}",
// 獲贊數的上限
"lte": "{{end}}{{^end}}20{{/end}}"
}
}
}
},
"params": {
...
}
}
'
如果params
中定義start
和end
兩個變數,比如:
"params": {
"start": 12,
"end": 13
}
那最終的查詢引數最終為:
"line_no": {
"gte": "12",
"lte": "13"
}
但如果params
中只定義了start
變數,沒有定義end
變數,如:
"params": {
"start": 12
}
那預設變數{{end}}{{^end}}20{{/end}}
生效,此時查詢引數為:
"line_no": {
"gte": "12",
"lte": "20"
}
條件語句(待處理)
條件語句不能使用JSON形式的模板,而是必須用字串的方式,下面是一個查詢行號的小栗子:
{
"query": {
"bool": {
"must": {
"match": {
// 將text引數填充
"line": "{{text}}"
}
},
// 這個過濾器只有params中指定了line_no才會建立,否則不會建立
"filter": {
{{#line_no}} // 這裡使用{{#xx}}..{{/xx}}包裹
"range": {
"line_no": {
{{#start}}
"gte": "{{start}}"
{{#end}},{{/end}}
{{/start}}
{{#end}}
"lte": "{{end}}"
{{/end}}
}
}
{{/line_no}}
}
}
},
"params": {
"text": "words to search for",
// 下面這些引數都是可選的
"line_no": { // 這個引數可省
"start": 10, // 可省
"end": 20 // 可省
}
}
}
上述的條件語句中其實就是使用{{#A}}..B..{{/A}}
將執行語句B包裹在內部,換成java裡面的寫法就是if(A != null) {B}
,即只有變數A不為空就執行B,否則不執行B。特別注意的是上述並不是有效的JSON格式(含有{{#line_on}}
這些標記),所以需要將整個配置寫成字串的形式,注意雙引號的轉義,簡單寫成:
"source": "{\"query\":{\"bool\":{\"must\":{\"match\":{\"line\":\"{{text}}\"}},\"filter\":{{{#line_no}}\"range\":{\"line_no\":{{{#start}}\"gte\":\"{{start}}\"{{#end}},{{/end}}{{/start}}{{#end}}\"lte\":\"{{end}}\"{{/end}}}}{{/line_no}}}}}}"
這裡執行遇到如下問題(待解決):
{
"error": {
"root_cause": [
{
"type": "general_script_exception",
"reason": "Failed to compile inline script [{\"query\":{\"bool\":{\"must\":{\"match\":{\"line\":\"{{text}}\"}},\"filter\":{{{#line_no}}\"range\":{\"line_no\":{{{#start}}\"gte\":\"{{start}}\"{{#end}},{{/end}}{{/start}}{{#end}}\"lte\":\"{{end}}\"{{/end}}}}{{/line_no}}}}}}] using lang [mustache]"
}
],
"type": "general_script_exception",
"reason": "Failed to compile inline script [{\"query\":{\"bool\":{\"must\":{\"match\":{\"line\":\"{{text}}\"}},\"filter\":{{{#line_no}}\"range\":{\"line_no\":{{{#start}}\"gte\":\"{{start}}\"{{#end}},{{/end}}{{/start}}{{#end}}\"lte\":\"{{end}}\"{{/end}}}}{{/line_no}}}}}}] using lang [mustache]",
"caused_by": {
"type": "mustache_exception",
"reason": "Improperly closed variable in query-template:1"
}
},
"status": 500
}
URL編碼
{{#url}}value{{/url}}
函式可以用來將HTML文字編碼為字串,比如:
curl -X GET "localhost:9200/_render/template" -H 'Content-Type: application/json' -d'
{
"source" : {
"query" : {
"term": {
"http_access_log": "{{#url}}{{host}}/{{page}}{{/url}}"
}
}
},
"params": {
"host": "https://www.elastic.co/",
"page": "learn"
}
}
'
最終上述請求的結果為:
{
"template_output": {
"query": {
"term": {
"http_access_log": "https%3A%2F%2Fwww.elastic.co%2F%2Flearn"
}
}
}
}
預註冊模板
通過儲存指令碼API可以註冊搜尋模板,比如:
// 註冊模板testtemplate,可以自定義
curl -X POST "localhost:9200/_scripts/testtemplate" -H 'Content-Type: application/json' -d'
{
"script": {
// 定義使用語言未mustache
"lang": "mustache",
"source": {
"query": {
"match": {
"title": "{{query_string}}"
}
}
}
}
}
'
取出上述的模板:
curl -X GET "localhost:9200/_scripts/testtemplate"
// 結果
{
"_id": "testtemplate",
"found": true,
"script": {
"lang": "mustache",
"source": "{\"query\":{\"match\":{\"title\":\"{{query_string}}\"}}}",
"options": {
"content_type": "application/json; charset=UTF-8"
}
}
}
驗證模板(關於模板驗證見下面的驗證模板):
curl -X POST "localhost:9200/_render/template/twittertemplate" -H 'Content-Type: application/json' -d'
{
"params": {
"query_string": "kimchy2"
}
}
'
// 結果
{
"template_output": {
"query": {
"match": {
"user": "kimchy2"
}
}
}
}
使用上述預定義的模板可以如下使用:
curl -X GET "localhost:9200/_search/template" -H 'Content-Type: application/json' -d'
{
// 這裡的id就是註冊模板的名字
"id": "<templateName>",
// 未模板注入引數
"params": {
"query_string": "search for these words"
}
}
'
刪除模板:
curl -X DELETE "localhost:9200/_scripts/<templatename>"
下面是一套完整的模板從註冊到使用的過程:
// 1. 註冊模板
curl -X POST "localhost:9200/_scripts/twittertemplate" -H 'Content-Type: application/json' -d'
{
"script": {
"lang": "mustache",
"source": {
"query": {
"match": {
"user": "{{query_string}}"
}
}
}
}
}
'
// 2. 使用模板
curl -X GET "localhost:9200/_search/template" -H 'Content-Type: application/json' -d'
{
"id": "twittertemplate",
"params": {
"query_string": "kimchy2"
}
}
'
// 結果
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 15,
"successful": 15,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 1,
"max_score": 0.6931472,
"hits": [
{
"_index": "twitter",
"_type": "_doc",
"_id": "2",
"_score": 0.6931472,
"_source": {
"user": "kimchy2",
"likes": 9,
"post_date": 1542197883,
"message": "trying out Elasticsearch"
}
}
]
}
}
驗證模板
模板可以帶著使用的引數出現在響應中,比如:
curl -X GET "localhost:9200/_render/template" -H 'Content-Type: application/json' -d'
{
"source": "{ \"query\": { \"terms\": {{#toJson}}statuses{{/toJson}} }}",
"params": {
"statuses" : {
"status": [ "pending", "published" ]
}
}
}
'
// 結果
{
"template_output": {
"query": {
"terms": {
"status": [
"pending",
"published"
]
}
}
}
}
通過結果看其實就是看一下轉換的結果(可以帶入引數檢視)。
得分說明
使用模板時也可以使用explain
引數開啟得分說明:
curl -X GET "localhost:9200/_search/template" -H 'Content-Type: application/json' -d'
{
"id": "twittertemplate",
"params": {
"query_string": "kimchy2"
},
"explain": true
}
'
// 結果
{
"took": 2,
"timed_out": false,
"_shards": {
"total": 15,
"successful": 15,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 1,
"max_score": 0.6931472,
"hits": [
{
"_shard": "[twitter][2]",
"_node": "bKeGC-Q-SXuyyGlcarDrMg",
"_index": "twitter",
"_type": "_doc",
"_id": "2",
"_score": 0.6931472,
"_source": {
"user": "kimchy2",
"likes": 9,
"post_date": 1542197883,
"message": "trying out Elasticsearch"
},
"_explanation": {
"value": 0.6931472,
"description": "weight(user:kimchy2 in 0) [PerFieldSimilarity], result of:",
"details": [
{
"value": 0.6931472,
"description": "score(doc=0,freq=1.0 = termFreq=1.0\n), product of:",
"details": [
{
"value": 0.6931472,
"description": "idf, computed as log(1 + (docCount - docFreq + 0.5) / (docFreq + 0.5)) from:",
"details": [
{
"value": 1,
"description": "docFreq",
"details": []
},
{
"value": 2,
"description": "docCount",
"details": []
}
]
},
{
"value": 1,
"description": "tfNorm, computed as (freq * (k1 + 1)) / (freq + k1) from:",
"details": [
{
"value": 1,
"description": "termFreq=1.0",
"details": []
},
{
"value": 1.2,
"description": "parameter k1",
"details": []
},
{
"value": 0,
"description": "parameter b (norms omitted for field)",
"details": []
}
]
}
]
}
]
}
}
]
}
}
配置
使用模板時也可以使用profile
引數:
curl -X GET "localhost:9200/_search/template" -H 'Content-Type: application/json' -d'
{
"id": "twittertemplate",
"params": {
"query_string": "kimchy2"
},
"profile": true
}
'
2. 多搜尋模板(Mutil Search Template)
多搜尋模板API可以使用_msearch/template
端點執行多個搜尋模板請求(和多搜尋請求API類似),形式如下:
header\n
body\n
header\n
body\n
header\n
:頭部header
和多搜尋API類似支援相同的index
、search_type
、preference
和routing
選項;
body\n
:體body
包括一個搜尋模板體請求並支援內聯、儲存和檔案模板,如下:
$ cat requests
{"index": "test"}
// 內聯搜尋模板請求
{"source": {"query": {"match": {"user" : "{{username}}" }}}, "params": {"username": "john"}}
{"source": {"query": {"{{query_type}}": {"name": "{{name}}" }}}, "params": {"query_type": "match_phrase_prefix", "name": "Smith"}}
{"index": "_all"}
// 基於儲存模板的搜尋模板請求
{"id": "template_1", "params": {"query_string": "search for these words" }}
$ curl -H "Content-Type: application/x-ndjson" -XGET localhost:9200/_msearch/template --data-binary "@requests"; echo