使用脚本引擎的高级脚本

编辑

使用脚本引擎的高级脚本

编辑

ScriptEngine 是实现脚本语言的后端。它也可以用于编写需要使用脚本高级内部机制的脚本。例如,一个在评分时需要使用词频的脚本。

插件文档有更多关于如何编写插件以使 Elasticsearch 正确加载它的信息。要注册 ScriptEngine,你的插件应该实现 ScriptPlugin 接口并覆盖 getScriptEngine(Settings settings) 方法。

以下是一个自定义 ScriptEngine 的示例,它使用语言名称 expert_scripts。它实现了一个名为 pure_df 的单一脚本,该脚本可以用作搜索脚本,以将每个文档的分数覆盖为提供的术语的文档频率。

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 boolean needs_termStats() {
            return false; // Return true if the script needs term statistics via get_termStats()
        }

        @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,并将脚本名称指定为脚本源来执行该脚本

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)
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"
                }
            }
          }
        }
      ]
    }
  }
}