Elasticsearch Painless Script入門教程
前面幾篇文章已經陸續提到了Elasticsearch 腳本,但總感覺不夠系統,本文帶你係統地學習下Painless Script。
Painless 腳本介紹
自Elasticsearch 5.x 引入Painless,使得Elasticsearch擁有了安全、可靠、高性能腳本的解決方案。Painless是Elastic開發並做了專門的優化,相較之前的腳本更快、安全、易使用、可靠。
Painless腳本的目標是使編寫腳本對用戶來說無痛,特別是對於來自Java或Groovy環境的用戶。可能你還不熟悉Elasticsearch腳本,讓我們從基礎開始。
下面我們簡要介紹Painless,並示例展示如何使用腳本搜索、更新數據。
1.1 變量和數據類型
Painless中變量可以聲明爲基本數據類型、引用類型、字符串、void
(不返回值)、數組以及動態類型。其支持下面基本類型:
byte, short, char, int, long, float, double, boolean.聲明變量與java類似:
int i = 0; double a; boolean g = true;
引用類型也和java類似,除了不支持修飾符,但支持繼承。變量通過new關鍵字初始化,例如聲明a
作爲ArrayList,變量b
類型爲Map:
ArrayList a = new ArrayList();
Map b;
Map g = [:];
List q = [1, 2, 3];
List和Map與數組類似,除了初始化時不需要new關鍵字,但它們是引用類型不是數組。
字符串類型可以使用直接量賦值,或使用new關鍵字初始化。
String a = "a";
String foo = new String("bar");
數組類型支持一維和多維,初始值爲null。與引用類型一樣,使用new關鍵字,併爲每個維度設置中括號。例如:
int[] x = new int[2];
x[0] = 3;
x[1] = 4;
數組大小可以是顯示的,如:int[] a = new int[2]
。或者創建時直接賦值:
int[] b = new int[] {1,2,3,4,5};
與Java和Groovy中的數組一樣,數組數據類型在聲明和初始化時必須有一個基本類型、字符串,甚至是與之關聯的動態def類型。
def是Painless支持的動態類型,它所做的是模仿它在運行時分配任何類型的行爲。所以,當定義一個變量時:
def a = 1;
def b = "foo";
elasticsearch會推斷a
是int類型,值爲1; b
是字符串類型,值爲“foo”。
數組也可以通過def聲明,舉例:
def[][] h = new def[2][2];
def[] f = new def[] {4, "s", 5.7, 2.8C};
有了這些變量,讓我們來看看條件語句和運算符。
1.2. 條件語句和運算符
如果你熟悉Java,Groovy,或其他高級語言,那麼條件和操作符都類似。Painless包含完整的操作符列表,除了它們的優先級和結合性之外,這些操作符與其他高級語言幾乎兼容。列表中的大多數操作符都與Java和Groovy語言兼容,操作符優先級可以用括號提升。例如: int t = 5+(5*5)
與其他語言一樣,Painless支持if else
,但不支持else if
或switch
。舉例:
if (doc['foo'].value = 5) {
doc['foo'].value *= 10;
}
else {
doc['foo'].value += 10;
}
Painless也有Elvis操作符?:
,其和Kotlin、Groovy。舉例:
x ?: y
如果x不null則評估返回左邊表達式,如果x爲null評估返回右邊表達式。Elvis操作不能和基本類型一起工作,所以這裏最好使用def
類型。
1.3. 方法
雖然Painless從Java語言中獲得很多強大功能,但並不是Java標準庫(Java運行時環境,JRE)中的每個類或方法都可用。Elasticsearch有Painless的類和方法參考列表。列表不僅包括JRE中有效的方法和類,還包括Elasticsearch 和 Painless中可用的方法。
1.4 Painless循環
Painless支持while, do...while, for
循環,以及控制流語句,如break和continue,這些都是可用。下面示例中for循環與其他語言非常類似:
def total = 0;
for (def i = 0; i < doc['scores'].length; i++) {
total += doc['scores'][i];
}
return total;
使用下面的形式也可以:
def total = 0;
for (def score : doc['scores']) {
total += score;
}
return total;
好了,我們已經看了Painless語言的基本規範,下面開始通過腳本實現一些查詢。
2. 腳本應用
2.1. 準備數據
示例數據sat考試分數,可以在這裏下載sat.json文件,在命令行執行下列命令:
curl -H 'Content-Type: application/json' -XPOST 'localhost:9200/sat/_doc/_bulk?pretty' --data-binary @sat.json
批量導入json文件需要有兩點注意:
1.每一行都是一個json對象;
2.第一行包括索引名稱,第二行是插入數據。
如果需要登錄,則需要在命令中加入-u username:password
參數。
2.2. 使用腳本搜索
下面示例使用def
演示Painless的動態類型。語句如下:
GET sat/_search
{
"script_fields": {
"some_scores": {
"script": {
"lang": "painless",
"source": "def scores = 0; scores = doc['AvgScrRead'].value + doc['AvgScrWrit'].value; return scores;"
}
}
}
}
在script
中可以定義語言類型lang
的值,默認爲Painless
。另外可以定義script的source
。
上面示例中使用_search
API和script_fields
命令。該命令可以創建新的字段存儲腳本中定義的scores值,我們簡單命名爲some_scores
,然後在source中定義腳本:
def scores = 0;
scores = doc['AvgScrRead'].value + doc['AvgScrWrit'].value;
return scores;
你注意到上面示例中的腳本沒有任何換行符,這是因爲Elasticsearch中腳本必須是單行字符串。其實該示例可以不使用Painless實現,也可以使用lucene表達式實現,這裏僅爲說明腳本的作用。
看下部分返回結果:
"hits" : [
{
"_index" : "sat",
"_type" : "_doc",
"_id" : "8-gOAnEBs8Ix-l1KQQ_6",
"_score" : 1.0,
"fields" : {
"some_scores" : [
961
]
}
}
]
上面腳本在索引中每個文檔上執行,上面結果顯示fields
中通過script_fields
命令創建了新的字段some_scores
。
下面實現另一個查詢,搜索AvgScrRead
成績小於350,並且AvgScrMath
成績大於350,腳本如下:
doc['AvgScrRead'].value < 350 && doc['AvgScrMath'].value > 350
查詢語句:
GET sat/_search
{
"query": {
"script": {
"script": {
"source": "doc['AvgScrRead'].value < 350 && doc['AvgScrMath'].value > 350",
"lang": "painless"
}
}
}
}
下面我們查詢四個成績即總分和其他三科成績,我們使用腳本定義數組保存三科成績及總成績:
def sat_scores = [];
def score_names = ['AvgScrRead', 'AvgScrWrit', 'AvgScrMath'];
for (int i = 0; i < score_names.length; i++) {
sat_scores.add(doc[score_names[i]].value)
}
def temp = 0;
for (def score : sat_scores) {
temp += score;
}
sat_scores.add(temp);
return sat_scores;
我們定義 sat_scores
數組存儲SAT分數 (AvgScrRead, AvgScrWrit, AvgScrMath) 及計算的總成績。創建另一個數組scores_names
存儲SAT中包括成績的三個字段名稱。如果未來索引中字段名稱變了,則需要修改數組的值。使用for循環遍歷scores_names
數組,在sat_scores數組加入相應的值,接着循環sat_scores
通過temp
遍歷累計成績,最後在sat_scores
中增加總成績。當然這個示例也可以通過一個循環實現。
完成查詢示例代碼:
GET sat/_search
{
"query": {
"script": {
"script": {
"source": "doc['AvgScrRead'].value < 350 && doc['AvgScrMath'].value > 350",
"lang": "painless"
}
}
},
"script_fields": {
"scores": {
"script": {
"source": "def sat_scores = []; def scores = ['AvgScrRead', 'AvgScrWrit', 'AvgScrMath']; for (int i = 0; i < scores.length; i++) {sat_scores.add(doc[scores[i]].value)} def temp = 0; for (def score : sat_scores) {temp += score;} sat_scores.add(temp); return sat_scores;",
"lang": "painless"
}
}
}
}
返回結果類似這樣:
"hits" : [
{
"_index" : "sat",
"_type" : "_doc",
"_id" : "q-gOAnEBs8Ix-l1KQhAC",
"_score" : 1.0,
"fields" : {
"scores" : [
349,
345,
352,
1046
]
}
}
]
上面查詢並沒有存儲結果,如果存儲需要使用_update
或 _update_by_query
API更新每個文檔。下面我們看看如何更新查詢結果。
2.3. 使用腳本更新
實現之前,我們先創建另一個字段存儲SAT分數數組。可以通過Elasticsearch的_update_by_query
API增加新的字段All_Scores
,一開始使用空數組進行初始化:
POST sat/_update_by_query
{
"script": {
"source": "ctx._source.All_Scores = []",
"lang": "painless"
}
}
到此我們給所有文檔增加了新的字段,接着需要更新該字段,我們使用腳本更新在字段All_Scores
:
def scores = ['AvgScrRead', 'AvgScrWrit', 'AvgScrMath'];
for (int i = 0; i < scores.length; i++) {
ctx._source.All_Scores.add(ctx._source[scores[i]]);
}
def temp = 0;
for (def score : ctx._source.All_Scores) {
temp += score;
}
ctx._source.All_Scores.add(temp);
使用_update
或_update_by_query
API,不能使用doc
變量。Elasticsearch提供了ctx
變量和_source
文檔,可以訪問文檔中字段。故可以更新All_Scores
數組,存儲SAT成績及總成績。
完整腳本如下:
POST sat/_update_by_query
{
"script": {
"source": "def scores = ['AvgScrRead', 'AvgScrWrit', 'AvgScrMath']; for (int i = 0; i < scores.length; i++) { ctx._source.All_Scores.add(ctx._source[scores[i]])} def temp = 0; for (def score : ctx._source.All_Scores) {temp += score;}ctx._source.All_Scores.add(temp);",
"lang": "painless"
}
}
如果僅更新單個文檔,也可以使用類似腳本實現。但需要指明文檔的id
,下面示例給 UOgOAnEBs8Ix-l1KQhEK
文檔的AvgScrMath
字段值加上10:
POST sat/_update/UOgOAnEBs8Ix-l1KQhEK
{
"script": {
"source": "ctx._source.AvgScrMath += 10",
"lang": "painless"
}
}
3. 總結
本文我們介紹了Elasticsearch中Painless腳本語言的基本規範,通過一些示例說明如何使用。使用了Painless API方法,如HashMap和循環,同時也看了一些腳本查詢、更新功能,希望這對你來說是最好的Painless入門。