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入门。
本文参考链接:https://blog.csdn.net/neweastsun/article/details/105035538