スクリプトエンジンを使用した高度なスクリプト

ScriptEngine は、スクリプト言語を実装するためのバックエンドです。また、スクリプトの高度な内部を使用する必要があるスクリプトを書くためにも使用できます。たとえば、スコアリング中に用語頻度を使用したいスクリプトです。

プラグインの ドキュメント には、Elasticsearch が適切にプラグインをロードするための書き方に関する詳細情報があります。ScriptEngine を登録するには、プラグインが ScriptPlugin インターフェースを実装し、getScriptEngine(Settings settings) メソッドをオーバーライドする必要があります。

以下は、言語名 expert_scripts を使用するカスタム ScriptEngine の例です。これは、提供された用語の文書頻度に基づいて各文書のスコアを上書きする検索スクリプトとして使用できる pure_df という単一のスクリプトを実装しています。

Java

  1. private static class MyExpertScriptEngine implements ScriptEngine {
  2. @Override
  3. public String getType() {
  4. return "expert_scripts";
  5. }
  6. @Override
  7. public <T> T compile(
  8. String scriptName,
  9. String scriptSource,
  10. ScriptContext<T> context,
  11. Map<String, String> params
  12. ) {
  13. if (context.equals(ScoreScript.CONTEXT) == false) {
  14. throw new IllegalArgumentException(getType()
  15. + " scripts cannot be used for context ["
  16. + context.name + "]");
  17. }
  18. // we use the script "source" as the script identifier
  19. if ("pure_df".equals(scriptSource)) {
  20. ScoreScript.Factory factory = new PureDfFactory();
  21. return context.factoryClazz.cast(factory);
  22. }
  23. throw new IllegalArgumentException("Unknown script name "
  24. + scriptSource);
  25. }
  26. @Override
  27. public void close() {
  28. // optionally close resources
  29. }
  30. @Override
  31. public Set<ScriptContext<?>> getSupportedContexts() {
  32. return Set.of(ScoreScript.CONTEXT);
  33. }
  34. private static class PureDfFactory implements ScoreScript.Factory,
  35. ScriptFactory {
  36. @Override
  37. public boolean isResultDeterministic() {
  38. // PureDfLeafFactory only uses deterministic APIs, this
  39. // implies the results are cacheable.
  40. return true;
  41. }
  42. @Override
  43. public LeafFactory newFactory(
  44. Map<String, Object> params,
  45. SearchLookup lookup
  46. ) {
  47. return new PureDfLeafFactory(params, lookup);
  48. }
  49. }
  50. private static class PureDfLeafFactory implements LeafFactory {
  51. private final Map<String, Object> params;
  52. private final SearchLookup lookup;
  53. private final String field;
  54. private final String term;
  55. private PureDfLeafFactory(
  56. Map<String, Object> params, SearchLookup lookup) {
  57. if (params.containsKey("field") == false) {
  58. throw new IllegalArgumentException(
  59. "Missing parameter [field]");
  60. }
  61. if (params.containsKey("term") == false) {
  62. throw new IllegalArgumentException(
  63. "Missing parameter [term]");
  64. }
  65. this.params = params;
  66. this.lookup = lookup;
  67. field = params.get("field").toString();
  68. term = params.get("term").toString();
  69. }
  70. @Override
  71. public boolean needs_score() {
  72. return false; // Return true if the script needs the score
  73. }
  74. @Override
  75. public ScoreScript newInstance(DocReader docReader)
  76. throws IOException {
  77. DocValuesDocReader dvReader = DocValuesDocReader) docReader); PostingsEnum postings = dvReader.getLeafReaderContext() .reader().postings(new Term(field, term;
  78. if (postings == null) {
  79. /*
  80. * the field and/or term don't exist in this segment,
  81. * so always return 0
  82. */
  83. return new ScoreScript(params, lookup, docReader) {
  84. @Override
  85. public double execute(
  86. ExplanationHolder explanation
  87. ) {
  88. if(explanation != null) {
  89. 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.");
  90. }
  91. return 0.0d;
  92. }
  93. };
  94. }
  95. return new ScoreScript(params, lookup, docReader) {
  96. int currentDocid = -1;
  97. @Override
  98. public void setDocument(int docid) {
  99. /*
  100. * advance has undefined behavior calling with
  101. * a docid <= its current docid
  102. */
  103. if (postings.docID() < docid) {
  104. try {
  105. postings.advance(docid);
  106. } catch (IOException e) {
  107. throw new UncheckedIOException(e);
  108. }
  109. }
  110. currentDocid = docid;
  111. }
  112. @Override
  113. public double execute(ExplanationHolder explanation) {
  114. if(explanation != null) {
  115. 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.");
  116. }
  117. if (postings.docID() != currentDocid) {
  118. /*
  119. * advance moved past the current doc, so this
  120. * doc has no occurrences of the term
  121. */
  122. return 0.0d;
  123. }
  124. try {
  125. return postings.freq();
  126. } catch (IOException e) {
  127. throw new UncheckedIOException(e);
  128. }
  129. }
  130. };
  131. }
  132. }
  133. }

スクリプトの langexpert_scripts として指定し、スクリプトの名前をスクリプトソースとして指定することで、スクリプトを実行できます:

Python

  1. resp = client.search(
  2. query={
  3. "function_score": {
  4. "query": {
  5. "match": {
  6. "body": "foo"
  7. }
  8. },
  9. "functions": [
  10. {
  11. "script_score": {
  12. "script": {
  13. "source": "pure_df",
  14. "lang": "expert_scripts",
  15. "params": {
  16. "field": "body",
  17. "term": "foo"
  18. }
  19. }
  20. }
  21. }
  22. ]
  23. }
  24. },
  25. )
  26. print(resp)

Js

  1. const response = await client.search({
  2. query: {
  3. function_score: {
  4. query: {
  5. match: {
  6. body: "foo",
  7. },
  8. },
  9. functions: [
  10. {
  11. script_score: {
  12. script: {
  13. source: "pure_df",
  14. lang: "expert_scripts",
  15. params: {
  16. field: "body",
  17. term: "foo",
  18. },
  19. },
  20. },
  21. },
  22. ],
  23. },
  24. },
  25. });
  26. console.log(response);

コンソール

  1. POST /_search
  2. {
  3. "query": {
  4. "function_score": {
  5. "query": {
  6. "match": {
  7. "body": "foo"
  8. }
  9. },
  10. "functions": [
  11. {
  12. "script_score": {
  13. "script": {
  14. "source": "pure_df",
  15. "lang" : "expert_scripts",
  16. "params": {
  17. "field": "body",
  18. "term": "foo"
  19. }
  20. }
  21. }
  22. }
  23. ]
  24. }
  25. }
  26. }