Painless 执行 API
编辑Painless 执行 API
编辑此功能处于技术预览阶段,可能会在未来的版本中更改或删除。Elastic 将努力解决任何问题,但技术预览版的功能不受官方 GA 功能的支持 SLA 约束。
Painless 执行 API 运行脚本并返回结果。
请求
编辑POST /_scripts/painless/_execute
描述
编辑使用此 API 构建和测试脚本,例如在为运行时字段定义脚本时。此 API 需要非常少的依赖项,如果您没有在集群上写入文档的权限,则特别有用。
该 API 使用多个上下文,这些上下文控制脚本的执行方式、运行时可用的变量以及返回类型。
每个上下文都需要一个脚本,但其他参数取决于您用于该脚本的上下文。
请求体
编辑-
script
-
(必需,对象)要执行的 Painless 脚本。
script
的属性-
emit
-
(必需)接受脚本估算的值。脚本可以多次调用
emit
方法以发出多个值。emit
方法仅适用于运行时字段上下文中使用的脚本。emit
方法不能接受null
值。如果引用的字段没有任何值,请不要调用此方法。emit
的签名emit
的签名取决于字段的type
。boolean
emit(boolean)
date
emit(long)
double
emit(double)
geo_point
emit(double lat, double lon)
ip
emit(String)
long
emit(long)
keyword
emit(String)
-
-
context
-
(可选,字符串)脚本应在其中运行的上下文。如果未指定上下文,则默认为
painless_test
。 -
context_setup
-
(必需,对象)
context
的其他参数。除
painless_test
之外的所有上下文都需要此参数,如果未为context
提供值,则为默认值。context_setup
的属性-
document
- (必需,字符串)临时在内存中索引且可从脚本访问的文档。
-
index
- (必需,字符串)包含与索引文档兼容的映射的索引。您可以通过在索引前加上远程集群别名来指定远程索引。例如,
remote1:my_index
表示您想针对“remote1”集群上的“my_index”索引执行 painless 脚本。如果您已配置了连接到该远程集群,则此请求将转发到“remote1”集群。
此端点的索引表达式中不接受通配符。表达式
*:myindex
将返回错误“No such remote cluster”,表达式logs*
或remote1:logs*
将返回错误“index not found”。 -
-
params
- (
Map
,只读)指定作为变量传递到脚本中的任何命名参数。 -
query
-
(可选,对象)
仅当将
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
、存储字段和 doc 值。
请求
编辑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 日开始撰写 Revelation Space
。在一年多一点的时间里写一本 585 页的书真是令人印象深刻!
{ "result" : [ "1999-02-19T00:00:00.000Z" ] }
double_field
编辑对 double
类型的数值数据使用 double_field
上下文。例如,假设您有包含 voltage
字段的传感器数据,该字段的值类似于 5.6。在索引数百万个文档之后,您发现型号为 QVKC92Q
的传感器将其电压低报了 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
。以下请求将 date
类型的 @timestamp
字段添加到索引映射
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.ENGLISH)); """ }, "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
字段,您想计算这些值之间的差值。
以下请求将包含两个类型均为 long
的字段的 measures
对象添加到映射
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
字段的日志数据,您想将其拆分为多个可以单独访问的子字段。
以下请求将 keyword
类型的 message
字段添加到映射
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" ] } }