スクリプトエンジンを使用した高度なスクリプト
ScriptEngine
は、スクリプト言語を実装するためのバックエンドです。また、スクリプトの高度な内部を使用する必要があるスクリプトを書くためにも使用できます。たとえば、スコアリング中に用語頻度を使用したいスクリプトです。
プラグインの ドキュメント には、Elasticsearch が適切にプラグインをロードするための書き方に関する詳細情報があります。ScriptEngine
を登録するには、プラグインが ScriptPlugin
インターフェースを実装し、getScriptEngine(Settings settings)
メソッドをオーバーライドする必要があります。
以下は、言語名 expert_scripts
を使用するカスタム ScriptEngine
の例です。これは、提供された用語の文書頻度に基づいて各文書のスコアを上書きする検索スクリプトとして使用できる pure_df
という単一のスクリプトを実装しています。
Java
private static class MyExpertScriptEngine implements ScriptEngine {
@Override
public String getType() {
return "expert_scripts";
}
@Override
public <T> T compile(
String scriptName,
String scriptSource,
ScriptContext<T> context,
Map<String, String> params
) {
if (context.equals(ScoreScript.CONTEXT) == false) {
throw new IllegalArgumentException(getType()
+ " scripts cannot be used for context ["
+ context.name + "]");
}
// we use the script "source" as the script identifier
if ("pure_df".equals(scriptSource)) {
ScoreScript.Factory factory = new PureDfFactory();
return context.factoryClazz.cast(factory);
}
throw new IllegalArgumentException("Unknown script name "
+ scriptSource);
}
@Override
public void close() {
// optionally close resources
}
@Override
public Set<ScriptContext<?>> getSupportedContexts() {
return Set.of(ScoreScript.CONTEXT);
}
private static class PureDfFactory implements ScoreScript.Factory,
ScriptFactory {
@Override
public boolean isResultDeterministic() {
// PureDfLeafFactory only uses deterministic APIs, this
// implies the results are cacheable.
return true;
}
@Override
public LeafFactory newFactory(
Map<String, Object> params,
SearchLookup lookup
) {
return new PureDfLeafFactory(params, lookup);
}
}
private static class PureDfLeafFactory implements LeafFactory {
private final Map<String, Object> params;
private final SearchLookup lookup;
private final String field;
private final String term;
private PureDfLeafFactory(
Map<String, Object> params, SearchLookup lookup) {
if (params.containsKey("field") == false) {
throw new IllegalArgumentException(
"Missing parameter [field]");
}
if (params.containsKey("term") == false) {
throw new IllegalArgumentException(
"Missing parameter [term]");
}
this.params = params;
this.lookup = lookup;
field = params.get("field").toString();
term = params.get("term").toString();
}
@Override
public boolean needs_score() {
return false; // Return true if the script needs the score
}
@Override
public ScoreScript newInstance(DocReader docReader)
throws IOException {
DocValuesDocReader dvReader = DocValuesDocReader) docReader); PostingsEnum postings = dvReader.getLeafReaderContext() .reader().postings(new Term(field, term;
if (postings == null) {
/*
* the field and/or term don't exist in this segment,
* so always return 0
*/
return new ScoreScript(params, lookup, docReader) {
@Override
public double execute(
ExplanationHolder explanation
) {
if(explanation != null) {
explanation.set("An example optional custom description to explain details for this script's execution; we'll provide a default one if you leave this out.");
}
return 0.0d;
}
};
}
return new ScoreScript(params, lookup, docReader) {
int currentDocid = -1;
@Override
public void setDocument(int docid) {
/*
* advance has undefined behavior calling with
* a docid <= its current docid
*/
if (postings.docID() < docid) {
try {
postings.advance(docid);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
currentDocid = docid;
}
@Override
public double execute(ExplanationHolder explanation) {
if(explanation != null) {
explanation.set("An example optional custom description to explain details for this script's execution; we'll provide a default one if you leave this out.");
}
if (postings.docID() != currentDocid) {
/*
* advance moved past the current doc, so this
* doc has no occurrences of the term
*/
return 0.0d;
}
try {
return postings.freq();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
};
}
}
}
スクリプトの lang
を expert_scripts
として指定し、スクリプトの名前をスクリプトソースとして指定することで、スクリプトを実行できます:
Python
resp = client.search(
query={
"function_score": {
"query": {
"match": {
"body": "foo"
}
},
"functions": [
{
"script_score": {
"script": {
"source": "pure_df",
"lang": "expert_scripts",
"params": {
"field": "body",
"term": "foo"
}
}
}
}
]
}
},
)
print(resp)
Js
const response = await client.search({
query: {
function_score: {
query: {
match: {
body: "foo",
},
},
functions: [
{
script_score: {
script: {
source: "pure_df",
lang: "expert_scripts",
params: {
field: "body",
term: "foo",
},
},
},
},
],
},
},
});
console.log(response);
コンソール
POST /_search
{
"query": {
"function_score": {
"query": {
"match": {
"body": "foo"
}
},
"functions": [
{
"script_score": {
"script": {
"source": "pure_df",
"lang" : "expert_scripts",
"params": {
"field": "body",
"term": "foo"
}
}
}
}
]
}
}
}