如何编写脚本
编辑如何编写脚本
编辑在 Elasticsearch API 中支持脚本的任何地方,语法都遵循相同的模式;您需要指定脚本的语言,提供脚本逻辑(或源代码),并添加传递给脚本的参数。
"script": { "lang": "...", "source" | "id": "...", "params": { ... } }
编写您的第一个脚本
编辑Painless 是 Elasticsearch 的默认脚本语言。 它安全、高性能,并且为任何有少量编码经验的人提供了自然的语法。
Painless 脚本的结构是一个或多个语句,并且可以选择在开头有一个或多个用户定义的函数。 一个脚本必须始终至少有一个语句。
Painless 执行 API 提供了使用简单的用户定义参数测试脚本并接收结果的能力。 让我们从一个完整的脚本开始,并回顾它的组成部分。
首先,索引一个包含单个字段的文档,以便我们有一些数据可以使用
resp = client.index( index="my-index-000001", id="1", document={ "my_field": 5 }, ) print(resp)
response = client.index( index: 'my-index-000001', id: 1, body: { my_field: 5 } ) puts response
const response = await client.index({ index: "my-index-000001", id: 1, document: { my_field: 5, }, }); console.log(response);
PUT my-index-000001/_doc/1 { "my_field": 5 }
然后,我们可以构建一个操作该字段的脚本,并作为查询的一部分运行评估该脚本。 以下查询使用搜索 API 的 script_fields
参数来检索脚本估值。 这里发生了很多事情,但我们将分解这些组件以单独理解它们。 现在,您只需要了解此脚本获取 my_field
并对其进行操作。
resp = client.search( index="my-index-000001", script_fields={ "my_doubled_field": { "script": { "source": "doc['my_field'].value * params['multiplier']", "params": { "multiplier": 2 } } } }, ) print(resp)
response = client.search( index: 'my-index-000001', body: { script_fields: { my_doubled_field: { script: { source: "doc['my_field'].value * params['multiplier']", params: { multiplier: 2 } } } } } ) puts response
const response = await client.search({ index: "my-index-000001", script_fields: { my_doubled_field: { script: { source: "doc['my_field'].value * params['multiplier']", params: { multiplier: 2, }, }, }, }, }); console.log(response);
GET my-index-000001/_search { "script_fields": { "my_doubled_field": { "script": { "source": "doc['my_field'].value * params['multiplier']", "params": { "multiplier": 2 } } } } }
script
是一个标准的 JSON 对象,在 Elasticsearch 中的大多数 API 下定义脚本。 此对象需要 source
来定义脚本本身。 该脚本未指定语言,因此默认为 Painless。
在脚本中使用参数
编辑Elasticsearch 第一次看到新脚本时,它会编译该脚本并将编译后的版本存储在缓存中。 编译可能是一个繁重的过程。 不要在脚本中硬编码值,而是将它们作为命名的 params
传递。
例如,在前面的脚本中,我们可以直接硬编码值并编写一个看起来不那么复杂的脚本。我们可以只检索 my_field
的第一个值,然后将其乘以 2
"source": "return doc['my_field'].value * 2"
虽然它有效,但这种解决方案非常不灵活。 我们必须修改脚本源代码才能更改乘数,并且每次乘数更改时,Elasticsearch 都必须重新编译脚本。
不要硬编码值,而是使用命名的 params
使脚本灵活,还可以减少脚本运行时编译时间。 现在,您可以在不重新编译 Elasticsearch 脚本的情况下更改 multiplier
参数。
"source": "doc['my_field'].value * params['multiplier']", "params": { "multiplier": 2 }
默认情况下,您可以在 5 分钟内编译多达 150 个脚本。 对于 ingest 上下文,默认的脚本编译速率不受限制。
script.context.field.max_compilations_rate=100/10m
如果您在短时间内编译了太多唯一的脚本,Elasticsearch 将拒绝带有 circuit_breaking_exception
错误的新动态脚本。
缩短脚本
编辑使用 Painless 原生的语法能力,您可以减少脚本中的冗长,使其更短。 这是一个简单的脚本,我们可以使其更短
resp = client.search( index="my-index-000001", script_fields={ "my_doubled_field": { "script": { "lang": "painless", "source": "doc['my_field'].value * params.get('multiplier');", "params": { "multiplier": 2 } } } }, ) print(resp)
response = client.search( index: 'my-index-000001', body: { script_fields: { my_doubled_field: { script: { lang: 'painless', source: "doc['my_field'].value * params.get('multiplier');", params: { multiplier: 2 } } } } } ) puts response
const response = await client.search({ index: "my-index-000001", script_fields: { my_doubled_field: { script: { lang: "painless", source: "doc['my_field'].value * params.get('multiplier');", params: { multiplier: 2, }, }, }, }, }); console.log(response);
GET my-index-000001/_search { "script_fields": { "my_doubled_field": { "script": { "lang": "painless", "source": "doc['my_field'].value * params.get('multiplier');", "params": { "multiplier": 2 } } } } }
让我们看一下缩短版本的脚本,看看它比之前的迭代版本有哪些改进
resp = client.search( index="my-index-000001", script_fields={ "my_doubled_field": { "script": { "source": "field('my_field').get(null) * params['multiplier']", "params": { "multiplier": 2 } } } }, ) print(resp)
const response = await client.search({ index: "my-index-000001", script_fields: { my_doubled_field: { script: { source: "field('my_field').get(null) * params['multiplier']", params: { multiplier: 2, }, }, }, }, }); console.log(response);
GET my-index-000001/_search { "script_fields": { "my_doubled_field": { "script": { "source": "field('my_field').get(null) * params['multiplier']", "params": { "multiplier": 2 } } } } }
此版本的脚本删除了多个组件并显着简化了语法
lang
声明。 由于 Painless 是默认语言,如果您正在编写 Painless 脚本,则无需指定语言。return
关键字。 Painless 会自动使用脚本中的最后一个语句(如果可能)在需要返回值的脚本上下文中生成返回值。get
方法,该方法被括号[]
替换。 Painless 专门为Map
类型使用快捷方式,这允许我们使用括号而不是更长的get
方法。source
语句末尾的分号。 Painless 不要求块的最后一条语句使用分号。 但是,在其他情况下确实需要它们来消除歧义。
在 Elasticsearch 支持脚本的任何地方使用此缩写语法,例如在您创建 运行时字段 时。
存储和检索脚本
编辑您可以使用存储脚本 API从集群状态存储和检索脚本。 存储的脚本可减少编译时间并加快搜索速度。
与常规脚本不同,存储的脚本要求您使用 lang
参数指定脚本语言。
要创建脚本,请使用创建存储的脚本 API。 例如,以下请求创建一个名为 calculate-score
的存储脚本。
resp = client.put_script( id="calculate-score", script={ "lang": "painless", "source": "Math.log(_score * 2) + params['my_modifier']" }, ) print(resp)
response = client.put_script( id: 'calculate-score', body: { script: { lang: 'painless', source: "Math.log(_score * 2) + params['my_modifier']" } } ) puts response
const response = await client.putScript({ id: "calculate-score", script: { lang: "painless", source: "Math.log(_score * 2) + params['my_modifier']", }, }); console.log(response);
POST _scripts/calculate-score { "script": { "lang": "painless", "source": "Math.log(_score * 2) + params['my_modifier']" } }
您可以使用获取存储的脚本 API检索该脚本。
resp = client.get_script( id="calculate-score", ) print(resp)
response = client.get_script( id: 'calculate-score' ) puts response
const response = await client.getScript({ id: "calculate-score", }); console.log(response);
GET _scripts/calculate-score
要在查询中使用存储的脚本,请在 script
声明中包含脚本 id
resp = client.search( index="my-index-000001", query={ "script_score": { "query": { "match": { "message": "some message" } }, "script": { "id": "calculate-score", "params": { "my_modifier": 2 } } } }, ) print(resp)
response = client.search( index: 'my-index-000001', body: { query: { script_score: { query: { match: { message: 'some message' } }, script: { id: 'calculate-score', params: { my_modifier: 2 } } } } } ) puts response
const response = await client.search({ index: "my-index-000001", query: { script_score: { query: { match: { message: "some message", }, }, script: { id: "calculate-score", params: { my_modifier: 2, }, }, }, }, }); console.log(response);
GET my-index-000001/_search { "query": { "script_score": { "query": { "match": { "message": "some message" } }, "script": { "id": "calculate-score", "params": { "my_modifier": 2 } } } } }
要删除存储的脚本,请提交删除存储的脚本 API请求。
resp = client.delete_script( id="calculate-score", ) print(resp)
response = client.delete_script( id: 'calculate-score' ) puts response
const response = await client.deleteScript({ id: "calculate-score", }); console.log(response);
DELETE _scripts/calculate-score
使用脚本更新文档
编辑您可以使用更新 API使用指定的脚本更新文档。 脚本可以更新、删除或跳过修改文档。 更新 API 还支持传递部分文档,该文档将合并到现有文档中。
首先,让我们索引一个简单的文档
resp = client.index( index="my-index-000001", id="1", document={ "counter": 1, "tags": [ "red" ] }, ) print(resp)
response = client.index( index: 'my-index-000001', id: 1, body: { counter: 1, tags: [ 'red' ] } ) puts response
const response = await client.index({ index: "my-index-000001", id: 1, document: { counter: 1, tags: ["red"], }, }); console.log(response);
PUT my-index-000001/_doc/1 { "counter" : 1, "tags" : ["red"] }
要增加计数器,您可以提交带有以下脚本的更新请求
resp = client.update( index="my-index-000001", id="1", script={ "source": "ctx._source.counter += params.count", "lang": "painless", "params": { "count": 4 } }, ) print(resp)
response = client.update( index: 'my-index-000001', id: 1, body: { script: { source: 'ctx._source.counter += params.count', lang: 'painless', params: { count: 4 } } } ) puts response
const response = await client.update({ index: "my-index-000001", id: 1, script: { source: "ctx._source.counter += params.count", lang: "painless", params: { count: 4, }, }, }); console.log(response);
POST my-index-000001/_update/1 { "script" : { "source": "ctx._source.counter += params.count", "lang": "painless", "params" : { "count" : 4 } } }
类似地,您可以使用更新脚本将标签添加到标签列表中。 因为这只是一个列表,所以即使标签存在也会添加该标签
resp = client.update( index="my-index-000001", id="1", script={ "source": "ctx._source.tags.add(params['tag'])", "lang": "painless", "params": { "tag": "blue" } }, ) print(resp)
response = client.update( index: 'my-index-000001', id: 1, body: { script: { source: "ctx._source.tags.add(params['tag'])", lang: 'painless', params: { tag: 'blue' } } } ) puts response
const response = await client.update({ index: "my-index-000001", id: 1, script: { source: "ctx._source.tags.add(params['tag'])", lang: "painless", params: { tag: "blue", }, }, }); console.log(response);
POST my-index-000001/_update/1 { "script": { "source": "ctx._source.tags.add(params['tag'])", "lang": "painless", "params": { "tag": "blue" } } }
您还可以从标签列表中删除标签。 Java List
的 remove
方法在 Painless 中可用。 它采用您要删除的元素的索引。 为避免可能的运行时错误,您首先需要确保标签存在。 如果列表中包含重复的标签,则此脚本仅删除一个出现项。
resp = client.update( index="my-index-000001", id="1", script={ "source": "if (ctx._source.tags.contains(params['tag'])) { ctx._source.tags.remove(ctx._source.tags.indexOf(params['tag'])) }", "lang": "painless", "params": { "tag": "blue" } }, ) print(resp)
response = client.update( index: 'my-index-000001', id: 1, body: { script: { source: "if (ctx._source.tags.contains(params['tag'])) { ctx._source.tags.remove(ctx._source.tags.indexOf(params['tag'])) }", lang: 'painless', params: { tag: 'blue' } } } ) puts response
const response = await client.update({ index: "my-index-000001", id: 1, script: { source: "if (ctx._source.tags.contains(params['tag'])) { ctx._source.tags.remove(ctx._source.tags.indexOf(params['tag'])) }", lang: "painless", params: { tag: "blue", }, }, }); console.log(response);
POST my-index-000001/_update/1 { "script": { "source": "if (ctx._source.tags.contains(params['tag'])) { ctx._source.tags.remove(ctx._source.tags.indexOf(params['tag'])) }", "lang": "painless", "params": { "tag": "blue" } } }
您还可以从文档中添加和删除字段。 例如,此脚本添加字段 new_field
resp = client.update( index="my-index-000001", id="1", script="ctx._source.new_field = 'value_of_new_field'", ) print(resp)
response = client.update( index: 'my-index-000001', id: 1, body: { script: "ctx._source.new_field = 'value_of_new_field'" } ) puts response
const response = await client.update({ index: "my-index-000001", id: 1, script: "ctx._source.new_field = 'value_of_new_field'", }); console.log(response);
POST my-index-000001/_update/1 { "script" : "ctx._source.new_field = 'value_of_new_field'" }
相反,此脚本会删除字段 new_field
resp = client.update( index="my-index-000001", id="1", script="ctx._source.remove('new_field')", ) print(resp)
response = client.update( index: 'my-index-000001', id: 1, body: { script: "ctx._source.remove('new_field')" } ) puts response
const response = await client.update({ index: "my-index-000001", id: 1, script: "ctx._source.remove('new_field')", }); console.log(response);
POST my-index-000001/_update/1 { "script" : "ctx._source.remove('new_field')" }
除了更新文档之外,您还可以从脚本内部更改执行的操作。 例如,如果 tags
字段包含 green
,则此请求会删除该文档。 否则它不执行任何操作 (noop
)
resp = client.update( index="my-index-000001", id="1", script={ "source": "if (ctx._source.tags.contains(params['tag'])) { ctx.op = 'delete' } else { ctx.op = 'none' }", "lang": "painless", "params": { "tag": "green" } }, ) print(resp)
response = client.update( index: 'my-index-000001', id: 1, body: { script: { source: "if (ctx._source.tags.contains(params['tag'])) { ctx.op = 'delete' } else { ctx.op = 'none' }", lang: 'painless', params: { tag: 'green' } } } ) puts response
const response = await client.update({ index: "my-index-000001", id: 1, script: { source: "if (ctx._source.tags.contains(params['tag'])) { ctx.op = 'delete' } else { ctx.op = 'none' }", lang: "painless", params: { tag: "green", }, }, }); console.log(response);
POST my-index-000001/_update/1 { "script": { "source": "if (ctx._source.tags.contains(params['tag'])) { ctx.op = 'delete' } else { ctx.op = 'none' }", "lang": "painless", "params": { "tag": "green" } } }