如何在elasticsearch中调用存储的无痛脚本函数

Rom*_*man 1 function elasticsearch elasticsearch-painless

我正在尝试使用一个例子

https://www.elastic.co/guide/en/elasticsearch/reference/6.4/modules-scripting-using.html

我创建了一个函数并保存了它。

POST http://localhost:9200/_scripts/calculate-score
{
  "script": {
    "lang": "painless",
    "source": "ctx._source.added + params.my_modifier"
  }
}
Run Code Online (Sandbox Code Playgroud)

尝试调用保存的函数

POST http://localhost:9200/users/user/_search
{
  "query": {
    "script": {
      "script": {
        "id": "calculate-score",
        "params": {
          "my_modifier": 2
        }
      }
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

它返回一个错误:Variable [ctx] is not defined。我尝试使用doc['added']但收到相同的错误。请帮助我了解如何调用该函数。

Nik*_*iev 6

您应该尝试使用doc['added'].value,让我向您解释原因和方法。简而言之,因为无痛脚本语言相当简单但晦涩难懂。

为什么ES找不到ctx变量?

它找不到ctx变量的原因是因为这个无痛脚本在“过滤器上下文”中运行,而这样的变量在过滤器上下文中不可用。(如果你很好奇,从 ES 6.4 开始有18 种类型的无痛上下文)。

在过滤器上下文中,只有两个变量可用:

params (地图,只读)

作为查询的一部分传入的用户定义的参数。

doc (地图,只读)

包含当前文档的字段,其中每个字段都是一个值列表。

doc['added'].value在您的情况下使用它应该足够了:

POST /_scripts/calculate-score
{
  "script": {
    "lang": "painless",
    "source": "doc['added'].value + params.my_modifier"
  }
}
Run Code Online (Sandbox Code Playgroud)

应该,因为如果我们尝试执行它会出现另一个问题(就像你所做的那样):

      "type": "script_exception",
      "reason": "runtime error",
      "script_stack": [
        "doc['added'].value + params.my_modifier",
        "^---- HERE"
      ],
      "script": "calculate-score",
      "lang": "painless",
      "caused_by": {
        "type": "class_cast_exception",
        "reason": "cannot cast def [long] to boolean"
      }
Run Code Online (Sandbox Code Playgroud)

由于其上下文,此脚本应返回boolean

返回

boolean

返回true是否应作为查询结果返回当前文档,false否则返回。

在这一点上,我们可以理解为什么您尝试执行的脚本对 Elasticsearch 没有多大意义:它应该判断文档是否与脚本查询匹配。如果脚本返回一个整数,Elasticsearch 将不知道它是true还是false

如何使存储的脚本在过滤器上下文中工作?

例如,我们可以使用以下脚本:

POST /_scripts/calculate-score1
{
  "script": {
    "lang": "painless",
    "source": "doc['added'].value > params.my_modifier"
  }
}
Run Code Online (Sandbox Code Playgroud)

现在我们可以访问脚本:

POST /users/user/_search
{
  "query": {
    "script": {
      "script": {
        "id": "calculate-score1",
        "params": {
          "my_modifier": 2
        }
      }
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

它将返回所有added大于 2 的文档:

"hits": [
  {
    "_index": "users",
    "_type": "user",
    "_id": "1",
    "_score": 1,
    "_source": {
      "name": "John Doe",
      "added": 40
    }
  }
]
Run Code Online (Sandbox Code Playgroud)

这次脚本返回了 aboolean并且 Elasticsearch 设法使用了它。

如果您好奇,range查询可以完成相同的工作,而无需编写脚本。

为什么我必须把它放在.value后面doc['added']

如果您尝试doc['added']直接访问,您可能会注意到错误消息有所不同:

POST /_scripts/calculate-score
{
  "script": {
    "lang": "painless",
    "source": "doc['added'] + params.my_modifier"
  }
}

      "type": "script_exception",
      "reason": "runtime error",
      "script_stack": [
        "doc['added'] + params.my_modifier",
        "                     ^---- HERE"
      ],
      "script": "calculate-score",
      "lang": "painless",
      "caused_by": {
        "type": "class_cast_exception",
        "reason": "Cannot apply [+] operation to types [org.elasticsearch.index.fielddata.ScriptDocValues.Longs] and [java.lang.Integer]."
      }
Run Code Online (Sandbox Code Playgroud)

无痛再次向我们展示了它的晦涩之处:当访问'added'文档的字段时,我们获得了一个实例org.elasticsearch.index.fielddata.ScriptDocValues.Longs,Java 虚拟机拒绝将其添加到一个整数(我们不能在这里责怪 Java)。

所以我们必须实际调用.getValue()方法,它在无痛翻译中就是简单的.value.

如果我想更改文档中的该字段怎么办?

如果您想在added某个文档的字段中添加 2并保存更新的文档怎么办?更新 API可以做到这一点。

它在update context 中运行,实际上已经ctx定义了变量,而后者又可以通过ctx['_source'].

我们可能会创建一个新脚本:

POST /_scripts/add-some
{
  "script": {
    "lang": "painless",
    "source": "ctx['_source']['added'] += params.my_modifier"
  }
}
Run Code Online (Sandbox Code Playgroud)

现在我们可以使用它:

POST /users/user/1/_update
{
    "script" : {
        "id": "add-some",
        "params" : {
            "my_modifier" : 2
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

为什么文档中的示例不起作用?

显然,因为这是错误的。此脚本(来自此文档页面):

POST _scripts/calculate-score
{
  "script": {
    "lang": "painless",
    "source": "Math.log(_score * 2) + params.my_modifier"
  }
}
Run Code Online (Sandbox Code Playgroud)

稍后在过滤器上下文中执行(在搜索请求中,在script查询中),而且,正如我们现在所知,没有_score可用的变量。

当运行允许摆动文档的相关性分数的查询时,此脚本仅在分数上下文中才有意义。funtion_score

最后说明

我想说的是,一般情况下,建议避免使用脚本,因为它们的性能很差。