Painless 脚本语言是新的,仍然被标记为实验性语言。 如果需要,语法或API可能会以非向后兼容的方式在将来更改。
Painless 是默认情况下 Elasticsearch 中提供的一种简单,安全的脚本语言。 它专门设计用于 Elasticsearch,可以安全地使用内联和存储的脚本,默认情况下启用。
Painless 语法类似于 Groovy。
您可以在任何地方使用 Painless 来在 Elasticsearch 中使用脚本。 如果您不设置lang参数是默认值,但如果要显式,则可以将lang参数设置为 Painless 。
为了说明 Painless 如何工作,我们将一些曲棍球统计数据加载到一个Elasticsearch索引中:
PUT hockey/player/_bulk?refresh
{"index":{"_id":1}}
{"first":"johnny","last":"gaudreau","goals":[9,27,1],"assists":[17,46,0],"gp":[26,82,1],"born":"1993/08/13"}
{"index":{"_id":2}}
{"first":"sean","last":"monohan","goals":[7,54,26],"assists":[11,26,13],"gp":[26,82,82],"born":"1994/10/12"}
{"index":{"_id":3}}
{"first":"jiri","last":"hudler","goals":[5,34,36],"assists":[11,62,42],"gp":[24,80,79],"born":"1984/01/04"}
{"index":{"_id":4}}
{"first":"micheal","last":"frolik","goals":[4,6,15],"assists":[8,23,15],"gp":[26,82,82],"born":"1988/02/17"}
{"index":{"_id":5}}
{"first":"sam","last":"bennett","goals":[5,0,0],"assists":[8,1,0],"gp":[26,1,0],"born":"1996/06/20"}
{"index":{"_id":6}}
{"first":"dennis","last":"wideman","goals":[0,26,15],"assists":[11,30,24],"gp":[26,81,82],"born":"1983/03/20"}
{"index":{"_id":7}}
{"first":"david","last":"jones","goals":[7,19,5],"assists":[3,17,4],"gp":[26,45,34],"born":"1984/08/10"}
{"index":{"_id":8}}
{"first":"tj","last":"brodie","goals":[2,14,7],"assists":[8,42,30],"gp":[26,82,82],"born":"1990/06/07"}
{"index":{"_id":39}}
{"first":"mark","last":"giordano","goals":[6,30,15],"assists":[3,30,24],"gp":[26,60,63],"born":"1983/10/03"}
{"index":{"_id":10}}
{"first":"mikael","last":"backlund","goals":[3,15,13],"assists":[6,24,18],"gp":[26,82,82],"born":"1989/03/17"}
{"index":{"_id":11}}
{"first":"joe","last":"colborne","goals":[3,18,13],"assists":[6,20,24],"gp":[26,67,82],"born":"1990/01/30"}
_
可以以Map命名的文档访问文档值。
例如,以下脚本计算玩家的总目标。 此示例使用强类型的int和for循环。
GET hockey/_search
{
"query": {
"function_score": {
"script_score": {
"script": {
"lang": "painless",
"inline": "int total = 0; for (int i = 0; i < doc['goals'].length; ++i) { total += doc['goals'][i]; } return total;"
}
}
}
}
}
或者,您可以使用脚本字段而不是功能分数来做同样的事情:
GET hockey/_search
{
"query": {
"match_all": {}
},
"script_fields": {
"total_goals": {
"script": {
"lang": "painless",
"inline": "int total = 0; for (int i = 0; i < doc['goals'].length; ++i) { total += doc['goals'][i]; } return total;"
}
}
}
}
以下示例使用 Painless 脚本通过组合的名字和名字对播放器进行排序。 使用 doc ['first'].value 和 doc ['last'].value 访问这些名称。
GET hockey/_search
{
"query": {
"match_all": {}
},
"sort": {
"_script": {
"type": "string",
"order": "asc",
"script": {
"lang": "painless",
"inline": "doc['first.keyword'].value + ' ' + doc['last.keyword'].value"
}
}
}
}
您也可以轻松更新字段。 您访问一个字段的原始源为 ctx._source.<field-name>
.
首先,我们通过提交以下请求来查看播放器的源数据:
GET hockey/_search
{
"stored_fields": [
"_id",
"_source"
],
"query": {
"term": {
"_id": 1
}
}
}
要将玩家1的姓氏更改为曲棍球,只需将 ctx._source.last
设置为新值即可:
POST hockey/player/1/_update
{
"script": {
"lang": "painless",
"inline": "ctx._source.last = params.last",
"params": {
"last": "hockey"
}
}
}
您还可以向文档添加字段。 例如,此脚本添加了一个包含播放器昵称,曲棍球的新字段
POST hockey/player/1/_update
{
"script": {
"lang": "painless",
"inline": "ctx._source.last = params.last; ctx._source.nick = params.nick",
"params": {
"last": "gaudreau",
"nick": "hockey"
}
}
}
日期与常规值有所不同。 这是一个返回每个玩家诞生年份的例子:
GET hockey/_search
{
"script_fields": {
"birth_year": {
"script": {
"inline": "doc.born.date.year"
}
}
}
}
这里的关键是不能直接编入 doc.born
,就像您正常的字段,你必须调用 doc.born.date 来获取一个 ReadableDateTime 。 从那里可以调用 getYear 和 getDayOfWeek 等方法。 在上面的例子中,getYear() 的一个快捷方式。
如果日期字段是列表,那么日期将始终返回第一个日期。 要访问所有日期,请使用 dates 而不是 date。
默认情况下,正则表达式被禁用,因为它们规避了 Painless 对长时间运行和内存饥饿脚本的保护。 更糟糕的是,即使 Painless 的正则表达式也可以具有令人吃惊的性能和堆栈深度行为。 它们仍然是一个惊人的强大工具,但是在默认情况下太可怕了。 要使他们自己设置_script.painless.regex.enabled:true_ 在elasticsearch.yml中。 我们非常希望有一个安全的替代实现,默认情况下可以启用,所以检查这个空间以备以后的开发!
Painless对正则表达式的本机支持具有语法结构:
/
中的模式只是Java正则表达式。 有关更多信息,请参阅“模式标志”一节。使用 find 操作符(=〜),您可以用“b”更新所有曲棍球选手:
POST hockey/player/_update_by_query
{
"script": {
"lang": "painless",
"inline": "if (ctx._source.last =~ /b/) {ctx._source.last += \"matched\"} else {ctx.op = 'noop'}"
}
}
使用匹配运算符(==〜),您可以更新名称以辅音开头的所有曲棍球运动员,并以元音结尾:
POST hockey/player/_update_by_query
{
"script": {
"lang": "painless",
"inline": "if (ctx._source.last ==~ /[^aeiou].*[aeiou]/) {ctx._source.last += \"matched\"} else {ctx.op = 'noop'}"
}
}
您可以直接使用 Pattern.matcher 获取 Matcher 实例,并删除其所有姓氏中的所有元音:
POST hockey/player/_update_by_query
{
"script": {
"lang": "painless",
"inline": "ctx._source.last = /[aeiou]/.matcher(ctx._source.last).replaceAll('')"
}
}
Matcher.replaceAll 只是调用 Java Matcher replaceAll 方法,所以它支持 $1 和 \1 替换:
POST hockey/player/_update_by_query
{
"script": {
"lang": "painless",
"inline": "ctx._source.last = /n([aeiou])/.matcher(ctx._source.last).replaceAll('$1')"
}
}
如果需要更多的替代控件,您可以使用构建替换的 Function<Matcher, String>
调用 CharAequence 上的 replaceAll 。 这不支持_$1_ 和 \1 访问替换,因为您已经有了匹配器的引用,可以使用 m.group(1) 获取它们。
在构建替换的函数内调用 Matcher.find 是粗鲁的,并且可能会破坏替换过程。
这将使曲棍球运动员姓氏中的所有元音大写:
POST hockey/player/_update_by_query
{
"script": {
"lang": "painless",
"inline": "ctx._source.last = ctx._source.last.replaceAll(/[aeiou]/, m -> m.group().toUpperCase(Locale.ROOT))"
}
}
或者您可以使用 CharSequence.replaceFirst 将其第一个元音以大写字母表示:
POST hockey/player/_update_by_query
{
"script": {
"lang": "painless",
"inline": "ctx._source.last = ctx._source.last.replaceFirst(/[aeiou]/, m -> m.group().toUpperCase(Locale.ROOT))"
}
}
注意:上面所有的 _update_by_query 示例可能真的可以用查询来限制他们拉回的数据。 虽然您可以使用脚本查询,但它不会像使用任何其他查询一样有效,因为脚本查询不能使用反向索引来限制他们必须检查的文档。
Painless 使用接收器,名称和方法进行方法调度。例如,通过首先获取 s 的类,然后用两个参数查找方法 foo 来解决 s.foo(a,b) 。这与使用参数的运行时类型的Groovy和使用编译时类型的参数的Java不同。
这样做的结果是,Painless 不支持像Java这样的重载方法,当从Java标准库中将类列入白名单时,会导致一些麻烦。例如,在Java和Groovy中,Matcher有两种方法:group(int) 和 group(String)。Painless 无法将这两种方法列入白名单,因为它们具有相同的名称和相同数量的参数。因此,它具有 group(int)_ 和 _namedGroup(String)。
我们对这种不同的调度方法有几个理由: