Painless 执行 API
编辑Painless 执行 API编辑
此功能处于技术预览阶段,可能会在未来版本中更改或删除。Elastic 将努力解决任何问题,但技术预览版中的功能不受官方 GA 功能的支持 SLA 的约束。
Painless 执行 API 运行脚本并返回结果。
请求编辑
POST /_scripts/painless/_execute
描述编辑
使用此 API 构建和测试脚本,例如在为 运行时字段 定义脚本时。此 API 需要很少的依赖项,如果您没有在集群上写入文档的权限,则此 API 特别有用。
该 API 使用多个*上下文*,这些上下文控制脚本的执行方式、运行时可用的变量以及返回类型。
每个上下文都需要一个脚本,但其他参数取决于您为该脚本使用的上下文。
请求正文编辑
-
脚本
-
(必需,对象)要执行的 Painless 脚本。
script
的属性-
emit
-
(必需)接受脚本评估中的值。脚本可以多次调用
emit
方法来发出多个值。emit
方法仅适用于 运行时字段上下文 中使用的脚本。emit
方法不能接受null
值。如果引用的字段没有任何值,请勿调用此方法。emit
的签名emit
的签名取决于字段的type
。布尔值
emit(boolean)
日期
emit(long)
双精度
emit(double)
地理点
emit(double lat, double lon)
IP
emit(String)
长整型
emit(long)
关键字
emit(String)
-
-
上下文
-
(可选,字符串)脚本应在其中运行的上下文。如果未指定上下文,则默认为
painless_test
。 -
context_setup
-
(必需,对象)
context
的其他参数。除
painless_test
外,所有上下文都需要此参数,如果未为context
提供值,则默认为painless_test
。context_setup
的属性-
文档
- (必需,字符串)在内存中临时索引并可从脚本访问的文档。
-
索引
- (必需,字符串)包含与索引文档兼容的映射的索引。您可以通过在索引前加上远程集群别名来指定远程索引。例如,
remote1:my_index
表示您要在“remote1”集群上的“my_index”索引上执行 painless 脚本。如果您已 配置了与该远程集群的连接,则此请求将被转发到“remote1”集群。
-
-
参数
- (
Map
,只读)指定作为变量传递给脚本的任何命名参数。 -
查询
-
(可选,对象)
仅当
score
被指定为脚本context
时,此参数才适用。使用此参数指定用于计算分数的查询。除了决定文档是否匹配之外,查询子句 还会在
_score
元数据字段中计算相关性分数。
测试上下文编辑
painless_test
上下文运行脚本时不带其他参数。唯一可用的变量是 params
,可用于访问用户定义的值。脚本的结果始终转换为字符串。
因为默认上下文是 painless_test
,所以您不需要指定 context
或 context_setup
。
请求编辑
POST /_scripts/painless/_execute { "script": { "source": "params.count / params.total", "params": { "count": 100.0, "total": 1000.0 } } }
响应编辑
{ "result": "0.1" }
过滤器上下文编辑
filter
上下文将脚本视为在 script
查询中运行。出于测试目的,必须提供一个文档,以便将其临时索引到内存中并可从脚本访问。更准确地说,此类文档的 _source
、存储字段和文档值可供正在测试的脚本使用。
请求编辑
PUT /my-index-000001 { "mappings": { "properties": { "field": { "type": "keyword" } } } }
POST /_scripts/painless/_execute { "script": { "source": "doc['field'].value.length() <= params.max_length", "params": { "max_length": 4 } }, "context": "filter", "context_setup": { "index": "my-index-000001", "document": { "field": "four" } } }
响应编辑
{ "result": true }
分数上下文编辑
score
上下文将脚本视为在 function_score
查询的 script_score
函数中运行。
请求编辑
PUT /my-index-000001 { "mappings": { "properties": { "field": { "type": "keyword" }, "rank": { "type": "long" } } } }
POST /_scripts/painless/_execute { "script": { "source": "doc['rank'].value / params.max_rank", "params": { "max_rank": 5.0 } }, "context": "score", "context_setup": { "index": "my-index-000001", "document": { "rank": 4 } } }
响应编辑
{ "result": 0.8 }
字段上下文编辑
字段上下文将脚本视为在搜索查询的 runtime_mappings
部分 中运行。您可以使用字段上下文来测试不同字段类型的脚本,然后在支持这些脚本的任何地方包含这些脚本,例如 运行时字段。
根据要返回的数据类型选择字段上下文。
boolean_field
编辑
当您想从脚本评估中返回 true
或 false
值时,请使用 boolean_field
字段上下文。布尔字段 接受 true
和 false
值,但也可以接受被解释为 true 或 false 的字符串。
假设您有关于有史以来排名前 100 位的科幻小说的数据。您想编写返回布尔响应的脚本,例如书籍是否超过特定页数,或者书籍是否在特定年份之后出版。
假设您的数据结构如下
PUT /my-index-000001 { "mappings": { "properties": { "name": { "type": "keyword" }, "author": { "type": "keyword" }, "release_date": { "type": "date" }, "page_count": { "type": "double" } } } }
然后,您可以在 boolean_field
上下文中编写一个脚本,指示书籍是否在 1972 年之前出版
POST /_scripts/painless/_execute { "script": { "source": """ emit(doc['release_date'].value.year < 1972); """ }, "context": "boolean_field", "context_setup": { "index": "my-index-000001", "document": { "name": "Dune", "author": "Frank Herbert", "release_date": "1965-06-01", "page_count": 604 } } }
因为《沙丘》出版于 1965 年,所以结果返回 true
{ "result" : [ true ] }
类似地,您可以编写一个脚本,确定作者的名字是否超过特定字符数。以下脚本对 author
字段进行操作,以确定作者的名字是否至少包含一个字符,但少于五个字符
POST /_scripts/painless/_execute { "script": { "source": """ int space = doc['author'].value.indexOf(' '); emit(space > 0 && space < 5); """ }, "context": "boolean_field", "context_setup": { "index": "my-index-000001", "document": { "name": "Dune", "author": "Frank Herbert", "release_date": "1965-06-01", "page_count": 604 } } }
因为 Frank
有五个字符,所以脚本评估的响应返回 false
{ "result" : [ false ] }
date_time
编辑
有几个选项可用于在 Painless 中使用日期时间。在本例中,您将根据特定作者的书籍发行日期和写作速度来估计他们何时开始写作。该示例进行了一些假设,但展示了如何编写一个在处理日期的同时结合其他信息的脚本。
将以下字段添加到您的索引映射以开始使用
PUT /my-index-000001 { "mappings": { "properties": { "name": { "type": "keyword" }, "author": { "type": "keyword" }, "release_date": { "type": "date" }, "page_count": { "type": "long" } } } }
以下脚本做出了一个令人难以置信的假设,即作者在写书时只是写每一页,而不进行研究或修改。此外,该脚本假设写一页的平均时间为八小时。
该脚本检索 author
并根据作者的感知写作速度(又一个大胆的假设)进行另一个奇妙的假设,以除以或乘以 pageTime
值。
该脚本从 pageTime
乘以 page_count
的计算结果中减去发行日期值(以毫秒为单位),以确定作者大约(基于众多假设)何时开始写这本书。
POST /_scripts/painless/_execute { "script": { "source": """ String author = doc['author'].value; long pageTime = 28800000; if (author == 'Robert A. Heinlein') { pageTime /= 2; } else if (author == 'Alastair Reynolds') { pageTime *= 2; } emit(doc['release_date'].value.toInstant().toEpochMilli() - pageTime * doc['page_count'].value); """ }, "context": "date_field", "context_setup": { "index": "my-index-000001", "document": { "name": "Revelation Space", "author": "Alastair Reynolds", "release_date": "2000-03-15", "page_count": 585 } } }
在这种情况下,作者是阿拉斯泰尔·雷诺兹。根据 2000-03-15
的发行日期,该脚本计算出作者在 1999 年 2 月 19 日开始写《启示空间》。在一年多的时间里写出一本 585 页的书真是令人印象深刻!
{ "result" : [ "1999-02-19T00:00:00.000Z" ] }
double_field
编辑
对 double
类型的数值数据使用 double_field
上下文。例如,假设您有传感器数据,其中包含一个值为 5.6 的 voltage
字段。在索引了数百万个文档后,您发现型号为 QVKC92Q
的传感器报告的电压偏低了 1.7 倍。您无需重新索引数据,而是可以使用运行时字段来修复它。
您需要将此值乘以 1.7,但仅限于型号匹配的传感器。
将以下字段添加到您的索引映射中。voltage
字段是 measures
对象的子字段。
PUT my-index-000001 { "mappings": { "properties": { "@timestamp": { "type": "date" }, "model_number": { "type": "keyword" }, "measures": { "properties": { "voltage": { "type": "double" } } } } } }
以下脚本匹配 model_number
等于 QVKC92Q
的任何文档,然后将 voltage
值乘以 1.7
。当您想选择特定文档并仅对与指定条件匹配的值进行操作时,此脚本非常有用。
POST /_scripts/painless/_execute { "script": { "source": """ if (doc['model_number'].value.equals('QVKC92Q')) {emit(1.7 * params._source['measures']['voltage']);} else{emit(params._source['measures']['voltage']);} """ }, "context": "double_field", "context_setup": { "index": "my-index-000001", "document": { "@timestamp": 1516470094000, "model_number": "QVKC92Q", "measures": { "voltage": 5.6 } } } }
结果包括计算出的电压,该电压是通过将原始值 5.6
乘以 1.7
得出的
{ "result" : [ 9.52 ] }
geo_point_field
编辑
地理点字段接受纬度-经度对。您可以通过多种方式定义地理点字段,并在文档中为您的脚本包含纬度和经度值。
如果您已经知道了一个地理点,那么在索引映射中明确说明 lat
和 lon
的位置会更简单。
PUT /my-index-000001/ { "mappings": { "properties": { "lat": { "type": "double" }, "lon": { "type": "double" } } } }
然后,您可以使用 geo_point_field
运行时字段上下文编写一个检索 lat
和 lon
值的脚本。
POST /_scripts/painless/_execute { "script": { "source": """ emit(doc['lat'].value, doc['lon'].value); """ }, "context": "geo_point_field", "context_setup": { "index": "my-index-000001", "document": { "lat": 41.12, "lon": -71.34 } } }
因为您正在使用地理点字段类型,所以响应包括格式为 coordinates
的结果。
{ "result" : [ { "coordinates" : [ -71.34, 41.12 ], "type" : "Point" } ] }
地理点字段的 emit 函数采用两个参数,顺序为 lat
在 lon
之前,但输出 GeoJSON 格式将 coordinates
的顺序排列为 [ lon, lat ]
。
ip_field
编辑
ip_field
上下文对于包含 ip
类型 IP 地址的数据非常有用。例如,假设您有一个来自 Apache 日志的 message
字段。此字段包含一个 IP 地址,但也包含您不需要的其他数据。
您可以将 message
字段作为 wildcard
添加到您的索引映射中,以接受您想放入该字段的几乎任何数据。
PUT /my-index-000001/ { "mappings": { "properties": { "message": { "type": "wildcard" } } } }
然后,您可以使用一个 grok 模式定义一个运行时脚本,该模式从 message
字段中提取结构化字段。
该脚本匹配 %{COMMONAPACHELOG}
日志模式,该模式了解 Apache 日志的结构。如果模式匹配,则脚本会发出与 IP 地址匹配的值。如果模式不匹配(clientip != null
),则脚本只返回字段值而不崩溃。
POST /_scripts/painless/_execute { "script": { "source": """ String clientip=grok('%{COMMONAPACHELOG}').extract(doc["message"].value)?.clientip; if (clientip != null) emit(clientip); """ }, "context": "ip_field", "context_setup": { "index": "my-index-000001", "document": { "message": "40.135.0.0 - - [30/Apr/2020:14:30:17 -0500] \"GET /images/hm_bg.jpg HTTP/1.0\" 200 24736" } } }
响应仅包含 IP 地址,忽略 message
字段中的所有其他数据。
{ "result" : [ "40.135.0.0" ] }
keyword_field
编辑
关键字字段通常用于排序、聚合和词条级查询。
假设您有一个时间戳。您想根据该值计算星期几并返回它,例如 Thursday
。以下请求将一个 @timestamp
字段(类型为 date
)添加到索引映射中
PUT /my-index-000001 { "mappings": { "properties": { "@timestamp": { "type": "date" } } } }
要根据您的时间戳返回等效的星期几,您可以在 keyword_field
运行时字段上下文中创建一个脚本
POST /_scripts/painless/_execute { "script": { "source": """ emit(doc['@timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT)); """ }, "context": "keyword_field", "context_setup": { "index": "my-index-000001", "document": { "@timestamp": "2020-04-30T14:31:43-05:00" } } }
该脚本对为 @timestamp
字段提供的值进行操作,以计算并返回星期几
{ "result" : [ "Thursday" ] }
long_field
编辑
假设您有传感器数据,其中包含一个 measures
对象。此对象包含一个 start
和 end
字段,您想计算这些值之间的差值。
以下请求将一个 measures
对象添加到映射中,该对象有两个字段,类型均为 long
PUT /my-index-000001/ { "mappings": { "properties": { "measures": { "properties": { "start": { "type": "long" }, "end": { "type": "long" } } } } } }
然后,您可以定义一个脚本,为 start
和 end
字段赋值并对其进行操作。以下脚本从 measures
对象中提取 end
字段的值,并从 start
字段中减去它
POST /_scripts/painless/_execute { "script": { "source": """ emit(doc['measures.end'].value - doc['measures.start'].value); """ }, "context": "long_field", "context_setup": { "index": "my-index-000001", "document": { "measures": { "voltage": "4.0", "start": "400", "end": "8625309" } } } }
响应包括脚本评估的计算值
{ "result" : [ 8624909 ] }
composite_field
编辑
假设您有日志数据,其中包含一个原始的 message
字段,您想将其拆分为多个可以单独访问的子字段。
以下请求将一个 message
字段(类型为 keyword
)添加到映射中
PUT /my-index-000001/ { "mappings": { "properties": { "message": { "type" : "keyword" } } } }
然后,您可以定义一个脚本,使用 grok 函数将此类消息字段拆分为子字段
POST /_scripts/painless/_execute { "script": { "source": "emit(grok(\"%{COMMONAPACHELOG}\").extract(doc[\"message\"].value));" }, "context": "composite_field", "context_setup": { "index": "my-index-000001", "document": { "timestamp":"2020-04-30T14:31:27-05:00", "message":"252.0.0.0 - - [30/Apr/2020:14:31:27 -0500] \"GET /images/hm_bg.jpg HTTP/1.0\" 200 24736" } } }
响应包括脚本发出的值
{ "result" : { "composite_field.timestamp" : [ "30/Apr/2020:14:31:27 -0500" ], "composite_field.auth" : [ "-" ], "composite_field.response" : [ "200" ], "composite_field.ident" : [ "-" ], "composite_field.httpversion" : [ "1.0" ], "composite_field.verb" : [ "GET" ], "composite_field.bytes" : [ "24736" ], "composite_field.clientip" : [ "252.0.0.0" ], "composite_field.request" : [ "/images/hm_bg.jpg" ] } }