Browse Source

Use `typed_keys` parameter to prefix suggester names by type in search responses (#23080)

This pull request reuses the typed_keys parameter added in #22965, but this time it applies it to suggesters. When set to true, the suggester names in the search response will be prefixed with a prefix that reflects their type.
Tanguy Leroux 8 years ago
parent
commit
e2e5937455
19 changed files with 253 additions and 24 deletions
  1. 1 1
      core/src/main/java/org/elasticsearch/rest/action/search/RestMultiSearchAction.java
  2. 2 1
      core/src/main/java/org/elasticsearch/rest/action/search/RestSearchAction.java
  3. 7 3
      core/src/main/java/org/elasticsearch/search/aggregations/InternalAggregation.java
  4. 25 3
      core/src/main/java/org/elasticsearch/search/suggest/Suggest.java
  5. 8 1
      core/src/main/java/org/elasticsearch/search/suggest/completion/CompletionSuggestion.java
  6. 8 1
      core/src/main/java/org/elasticsearch/search/suggest/phrase/PhraseSuggestion.java
  7. 8 1
      core/src/main/java/org/elasticsearch/search/suggest/term/TermSuggestion.java
  8. 2 2
      docs/reference/aggregations/misc.asciidoc
  9. 2 0
      docs/reference/search/suggesters.asciidoc
  10. 84 0
      docs/reference/search/suggesters/misc.asciidoc
  11. 2 1
      modules/lang-mustache/src/main/java/org/elasticsearch/script/mustache/RestMultiSearchTemplateAction.java
  12. 1 1
      modules/lang-mustache/src/main/java/org/elasticsearch/script/mustache/RestSearchTemplateAction.java
  13. 18 2
      modules/lang-mustache/src/test/resources/rest-api-spec/test/lang_mustache/60_typed_keys.yaml
  14. 1 1
      rest-api-spec/src/main/resources/rest-api-spec/api/msearch.json
  15. 1 1
      rest-api-spec/src/main/resources/rest-api-spec/api/msearch_template.json
  16. 1 1
      rest-api-spec/src/main/resources/rest-api-spec/api/search.json
  17. 1 1
      rest-api-spec/src/main/resources/rest-api-spec/api/search_template.json
  18. 15 3
      rest-api-spec/src/main/resources/rest-api-spec/test/msearch/20_typed_keys.yaml
  19. 66 0
      rest-api-spec/src/main/resources/rest-api-spec/test/suggest/40_typed_keys.yaml

+ 1 - 1
core/src/main/java/org/elasticsearch/rest/action/search/RestMultiSearchAction.java

@@ -52,7 +52,7 @@ import static org.elasticsearch.rest.RestRequest.Method.POST;
 
 public class RestMultiSearchAction extends BaseRestHandler {
 
-    private static final Set<String> RESPONSE_PARAMS = Collections.singleton("typed_keys");
+    private static final Set<String> RESPONSE_PARAMS = Collections.singleton(RestSearchAction.TYPED_KEYS_PARAM);
 
     private final boolean allowExplicitIndex;
 

+ 2 - 1
core/src/main/java/org/elasticsearch/rest/action/search/RestSearchAction.java

@@ -54,7 +54,8 @@ import static org.elasticsearch.search.suggest.SuggestBuilders.termSuggestion;
 
 public class RestSearchAction extends BaseRestHandler {
 
-    private static final Set<String> RESPONSE_PARAMS = Collections.singleton("typed_keys");
+    public static final String TYPED_KEYS_PARAM = "typed_keys";
+    private static final Set<String> RESPONSE_PARAMS = Collections.singleton(TYPED_KEYS_PARAM);
 
     public RestSearchAction(Settings settings, RestController controller) {
         super(settings);

+ 7 - 3
core/src/main/java/org/elasticsearch/search/aggregations/InternalAggregation.java

@@ -25,6 +25,7 @@ import org.elasticsearch.common.io.stream.StreamOutput;
 import org.elasticsearch.common.util.BigArrays;
 import org.elasticsearch.common.xcontent.ToXContent;
 import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.rest.action.search.RestSearchAction;
 import org.elasticsearch.script.ScriptService;
 import org.elasticsearch.search.aggregations.pipeline.PipelineAggregator;
 import org.elasticsearch.search.aggregations.support.AggregationPath;
@@ -164,9 +165,12 @@ public abstract class InternalAggregation implements Aggregation, ToXContent, Na
 
     @Override
     public final XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
-        // Concatenates the type and the name of the aggregation (ex: top_hits#foo)
-        String name = params.paramAsBoolean("typed_keys", false) ? String.join(TYPED_KEYS_DELIMITER, getType(), getName()) : getName();
-        builder.startObject(name);
+        if (params.paramAsBoolean(RestSearchAction.TYPED_KEYS_PARAM, false)) {
+            // Concatenates the type and the name of the aggregation (ex: top_hits#foo)
+            builder.startObject(String.join(TYPED_KEYS_DELIMITER, getType(), getName()));
+        } else {
+            builder.startObject(getName());
+        }
         if (this.metaData != null) {
             builder.field(CommonFields.META);
             builder.map(this.metaData);

+ 25 - 3
core/src/main/java/org/elasticsearch/search/suggest/Suggest.java

@@ -30,6 +30,8 @@ import org.elasticsearch.common.xcontent.ToXContentObject;
 import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.common.xcontent.XContentFactory;
 import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.rest.action.search.RestSearchAction;
+import org.elasticsearch.search.aggregations.InternalAggregation;
 import org.elasticsearch.search.suggest.Suggest.Suggestion.Entry;
 import org.elasticsearch.search.suggest.Suggest.Suggestion.Entry.Option;
 import org.elasticsearch.search.suggest.completion.CompletionSuggestion;
@@ -149,7 +151,7 @@ public class Suggest implements Iterable<Suggest.Suggestion<? extends Entry<? ex
     public void writeTo(StreamOutput out) throws IOException {
         out.writeVInt(suggestions.size());
         for (Suggestion<?> command : suggestions) {
-            out.writeVInt(command.getType());
+            out.writeVInt(command.getWriteableType());
             command.writeTo(out);
         }
     }
@@ -206,6 +208,8 @@ public class Suggest implements Iterable<Suggest.Suggestion<? extends Entry<? ex
      */
     public static class Suggestion<T extends Suggestion.Entry> implements Iterable<T>, Streamable, ToXContent {
 
+        private static final String NAME = "suggestion";
+
         public static final int TYPE = 0;
         protected String name;
         protected int size;
@@ -223,10 +227,23 @@ public class Suggest implements Iterable<Suggest.Suggestion<? extends Entry<? ex
             entries.add(entry);
         }
 
-        public int getType() {
+        /**
+         * Returns a integer representing the type of the suggestion. This is used for
+         * internal serialization over the network.
+         */
+        public int getWriteableType() { // TODO remove this in favor of NamedWriteable
             return TYPE;
         }
 
+        /**
+         * Returns a string representing the type of the suggestion. This type is added to
+         * the suggestion name in the XContent response, so that it can later be used by
+         * REST clients to determine the internal type of the suggestion.
+         */
+        protected String getType() {
+            return NAME;
+        }
+
         @Override
         public Iterator<T> iterator() {
             return entries.iterator();
@@ -338,7 +355,12 @@ public class Suggest implements Iterable<Suggest.Suggestion<? extends Entry<? ex
 
         @Override
         public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
-            builder.startArray(name);
+            if (params.paramAsBoolean(RestSearchAction.TYPED_KEYS_PARAM, false)) {
+                // Concatenates the type and the name of the suggestion (ex: completion#foo)
+                builder.startArray(String.join(InternalAggregation.TYPED_KEYS_DELIMITER, getType(), getName()));
+            } else {
+                builder.startArray(getName());
+            }
             for (Entry<?> entry : entries) {
                 entry.toXContent(builder, params);
             }

+ 8 - 1
core/src/main/java/org/elasticsearch/search/suggest/completion/CompletionSuggestion.java

@@ -57,6 +57,8 @@ import static org.elasticsearch.search.suggest.Suggest.COMPARATOR;
  */
 public final class CompletionSuggestion extends Suggest.Suggestion<CompletionSuggestion.Entry> {
 
+    private static final String NAME = "completion";
+
     public static final int TYPE = 4;
 
     public CompletionSuggestion() {
@@ -165,10 +167,15 @@ public final class CompletionSuggestion extends Suggest.Suggestion<CompletionSug
     }
 
     @Override
-    public int getType() {
+    public int getWriteableType() {
         return TYPE;
     }
 
+    @Override
+    protected String getType() {
+        return NAME;
+    }
+
     @Override
     protected Entry newEntry() {
         return new Entry();

+ 8 - 1
core/src/main/java/org/elasticsearch/search/suggest/phrase/PhraseSuggestion.java

@@ -31,6 +31,8 @@ import java.io.IOException;
  * Suggestion entry returned from the {@link PhraseSuggester}.
  */
 public class PhraseSuggestion extends Suggest.Suggestion<PhraseSuggestion.Entry> {
+
+    private static final String NAME = "phrase";
     public static final int TYPE = 3;
 
     public PhraseSuggestion() {
@@ -41,10 +43,15 @@ public class PhraseSuggestion extends Suggest.Suggestion<PhraseSuggestion.Entry>
     }
 
     @Override
-    public int getType() {
+    public int getWriteableType() {
         return TYPE;
     }
 
+    @Override
+    protected String getType() {
+        return NAME;
+    }
+
     @Override
     protected Entry newEntry() {
         return new Entry();

+ 8 - 1
core/src/main/java/org/elasticsearch/search/suggest/term/TermSuggestion.java

@@ -40,6 +40,8 @@ import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constru
  */
 public class TermSuggestion extends Suggestion<TermSuggestion.Entry> {
 
+    private static final String NAME = "term";
+
     public static final Comparator<Suggestion.Entry.Option> SCORE = new Score();
     public static final Comparator<Suggestion.Entry.Option> FREQUENCY = new Frequency();
     public static final int TYPE = 1;
@@ -96,10 +98,15 @@ public class TermSuggestion extends Suggestion<TermSuggestion.Entry> {
     }
 
     @Override
-    public int getType() {
+    public int getWriteableType() {
         return TYPE;
     }
 
+    @Override
+    protected String getType() {
+        return NAME;
+    }
+
     @Override
     protected Comparator<Option> sortComparator() {
         switch (sort) {

+ 2 - 2
docs/reference/aggregations/misc.asciidoc

@@ -118,8 +118,8 @@ GET /twitter/tweet/_search?typed_keys
 // CONSOLE
 // TEST[setup:twitter]
 
-In the response, the aggregations names will be changed to respectively `date_histogram:tweets_over_time` and
-`top_hits:top_users`, reflecting the internal types of each aggregation:
+In the response, the aggregations names will be changed to respectively `date_histogram#tweets_over_time` and
+`top_hits#top_users`, reflecting the internal types of each aggregation:
 
 [source,js]
 --------------------------------------------------

+ 2 - 0
docs/reference/search/suggesters.asciidoc

@@ -147,3 +147,5 @@ include::suggesters/phrase-suggest.asciidoc[]
 include::suggesters/completion-suggest.asciidoc[]
 
 include::suggesters/context-suggest.asciidoc[]
+
+include::suggesters/misc.asciidoc[]

+ 84 - 0
docs/reference/search/suggesters/misc.asciidoc

@@ -0,0 +1,84 @@
+[[returning-suggesters-type]]
+=== Returning the type of the suggester
+
+Sometimes you need to know the exact type of a suggester in order to parse its results. The `typed_keys` parameter
+ can be used to change the suggester's name in the response so that it will be prefixed by its type.
+
+Considering the following example with two suggesters `term` and `phrase`:
+
+[source,js]
+--------------------------------------------------
+POST _search?typed_keys
+{
+  "suggest": {
+    "text" : "some test mssage",
+    "my-first-suggester" : {
+      "term" : {
+        "field" : "message"
+      }
+    },
+    "my-second-suggester" : {
+      "phrase" : {
+        "field" : "message"
+      }
+    }
+  }
+}
+--------------------------------------------------
+// CONSOLE
+// TEST[setup:twitter]
+
+In the response, the suggester names will be changed to respectively `term#my-first-suggester` and
+`phrase#my-second-suggester`, reflecting the types of each suggestion:
+
+[source,js]
+--------------------------------------------------
+{
+  "suggest": {
+    "term#my-first-suggester": [ <1>
+      {
+        "text": "some",
+        "offset": 0,
+        "length": 4,
+        "options": []
+      },
+      {
+        "text": "test",
+        "offset": 5,
+        "length": 4,
+        "options": []
+      },
+      {
+        "text": "mssage",
+        "offset": 10,
+        "length": 6,
+        "options": [
+          {
+            "text": "message",
+            "score": 0.8333333,
+            "freq": 4
+          }
+        ]
+      }
+    ],
+    "phrase#my-second-suggester": [ <2>
+      {
+        "text": "some test mssage",
+        "offset": 0,
+        "length": 16,
+        "options": [
+          {
+            "text": "some test message",
+            "score": 0.030227963
+          }
+        ]
+      }
+    ]
+  },
+  ...
+}
+--------------------------------------------------
+// TESTRESPONSE[s/\.\.\./"took": "$body.took", "timed_out": false, "_shards": "$body._shards", "hits": "$body.hits"/]
+
+<1> The name `my-first-suggester` now contains the `term` prefix.
+<2> The name `my-second-suggester` now contains the `phrase` prefix.

+ 2 - 1
modules/lang-mustache/src/main/java/org/elasticsearch/script/mustache/RestMultiSearchTemplateAction.java

@@ -28,6 +28,7 @@ import org.elasticsearch.rest.RestController;
 import org.elasticsearch.rest.RestRequest;
 import org.elasticsearch.rest.action.RestToXContentListener;
 import org.elasticsearch.rest.action.search.RestMultiSearchAction;
+import org.elasticsearch.rest.action.search.RestSearchAction;
 
 import java.io.IOException;
 import java.util.Collections;
@@ -38,7 +39,7 @@ import static org.elasticsearch.rest.RestRequest.Method.POST;
 
 public class RestMultiSearchTemplateAction extends BaseRestHandler {
 
-    private static final Set<String> RESPONSE_PARAMS = Collections.singleton("typed_keys");
+    private static final Set<String> RESPONSE_PARAMS = Collections.singleton(RestSearchAction.TYPED_KEYS_PARAM);
 
     private final boolean allowExplicitIndex;
 

+ 1 - 1
modules/lang-mustache/src/main/java/org/elasticsearch/script/mustache/RestSearchTemplateAction.java

@@ -45,7 +45,7 @@ import static org.elasticsearch.rest.RestRequest.Method.POST;
 
 public class RestSearchTemplateAction extends BaseRestHandler {
 
-    private static final Set<String> RESPONSE_PARAMS = Collections.singleton("typed_keys");
+    private static final Set<String> RESPONSE_PARAMS = Collections.singleton(RestSearchAction.TYPED_KEYS_PARAM);
 
     private static final ObjectParser<SearchTemplateRequest, Void> PARSER;
     static {

+ 18 - 2
modules/lang-mustache/src/test/resources/rest-api-spec/test/lang_mustache/60_typed_keys.yaml

@@ -48,6 +48,11 @@ setup:
               test_missing:
                 missing:
                   field: "{{missing_field}}"
+            suggest:
+              term_suggest:
+                text: "{{suggest_text}}"
+                term:
+                  field: "{{suggest_field}}"
 
   - match: { acknowledged: true }
 
@@ -60,9 +65,12 @@ setup:
           params:
             bool_value: true
             missing_field: name
+            suggest_field: name
+            suggest_text: Hamilt
 
-  - match: { hits.total: 3 }
-  - match: { aggregations.missing#test_missing.doc_count: 1 }
+  - match:    { hits.total: 3 }
+  - match:    { aggregations.missing#test_missing.doc_count: 1 }
+  - is_true:  suggest.term#term_suggest
 
 ---
 "Multisearch template with typed_keys parameter":
@@ -81,6 +89,11 @@ setup:
                 histogram:
                   field:    "{{histo.field}}"
                   interval: "{{histo.interval}}"
+            suggest:
+              phrase_suggester:
+                text: "{{keywords}}"
+                phrase:
+                  field: name
 
   - match: { acknowledged: true }
 
@@ -112,8 +125,11 @@ setup:
               histo:
                 field: float
                 interval: 5
+              keywords: Ruht
+
   - match:  { responses.0.hits.total: 1 }
   - match:  { responses.0.aggregations.global#test_global.doc_count: 5 }
   - match:  { responses.0.aggregations.global#test_global.ip_range#test_ip_range.buckets.0.doc_count: 5 }
   - match:  { responses.1.hits.total: 2 }
   - match:  { responses.1.aggregations.histogram#test_histogram.buckets.0.doc_count: 1 }
+  - is_true:  responses.1.suggest.phrase#phrase_suggester

+ 1 - 1
rest-api-spec/src/main/resources/rest-api-spec/api/msearch.json

@@ -27,7 +27,7 @@
         },
         "typed_keys": {
           "type" : "boolean",
-          "description" : "Specify whether aggregation names should be prefixed by their respective types in the response"
+          "description" : "Specify whether aggregation and suggester names should be prefixed by their respective types in the response"
         }
       }
     },

+ 1 - 1
rest-api-spec/src/main/resources/rest-api-spec/api/msearch_template.json

@@ -23,7 +23,7 @@
         },
         "typed_keys": {
           "type" : "boolean",
-          "description" : "Specify whether aggregation names should be prefixed by their respective types in the response"
+          "description" : "Specify whether aggregation and suggester names should be prefixed by their respective types in the response"
         }
       }
     },

+ 1 - 1
rest-api-spec/src/main/resources/rest-api-spec/api/search.json

@@ -149,7 +149,7 @@
         },
         "typed_keys": {
           "type" : "boolean",
-          "description" : "Specify whether aggregation names should be prefixed by their respective types in the response"
+          "description" : "Specify whether aggregation and suggester names should be prefixed by their respective types in the response"
         },
         "version": {
           "type" : "boolean",

+ 1 - 1
rest-api-spec/src/main/resources/rest-api-spec/api/search_template.json

@@ -57,7 +57,7 @@
         },
         "typed_keys": {
           "type" : "boolean",
-          "description" : "Specify whether aggregation names should be prefixed by their respective types in the response"
+          "description" : "Specify whether aggregation and suggester names should be prefixed by their respective types in the response"
         }
       }
     },

+ 15 - 3
rest-api-spec/src/main/resources/rest-api-spec/test/msearch/20_typed_keys.yaml

@@ -21,19 +21,20 @@ setup:
                    type: float
                 name:
                    type: keyword
-
+                title:
+                   type: completion
   - do:
      bulk:
         refresh: true
         body:
           - '{"index": {"_index": "test-0", "_type": "user"}}'
-          - '{"row": 1, "index_start_at": 56, "integer": 38, "float": 12.5713, "name": "Ruth", "bool": true}'
+          - '{"row": 1, "index_start_at": 56, "integer": 38, "float": 12.5713, "name": "Ruth", "bool": true, "title": "doctor"}'
           - '{"index": {"_index": "test-0", "_type": "user"}}'
           - '{"row": 2, "index_start_at": 57, "integer": 42, "float": 15.3393, "name": "Jackie", "surname": "Bowling", "bool": false}'
           - '{"index": {"_index": "test-1", "_type": "user"}}'
           - '{"row": 3, "index_start_at": 58, "integer": 29, "float": 19.0517, "name": "Stephanie", "bool": true}'
           - '{"index": {"_index": "test-1", "_type": "user"}}'
-          - '{"row": 4, "index_start_at": 59, "integer": 19, "float": 19.3717, "surname": "Hamilton", "bool": true}'
+          - '{"row": 4, "index_start_at": 59, "integer": 19, "float": 19.3717, "surname": "Hamilton", "bool": true, "title": "commandant"}'
           - '{"index": {"_index": "test-2", "_type": "user"}}'
           - '{"row": 5, "index_start_at": 60, "integer": 0, "float": 17.3349, "name": "Natalie", "bool": false}'
 
@@ -43,12 +44,20 @@ setup:
       msearch:
         typed_keys: true
         body:
+          # Testing aggegrations
           - index: test-*
           - {query: {match: {bool: true} }, size: 0, aggs: {test_filter: {filter: {range:{integer: {gte: 20} } } } } }
           - index: test-1
           - {query: {match_all: {} }, size: 0, aggs: {test_range: {range: {field: float, ranges: [ {to: 19.2499999}, {from: 19.25} ] } } } }
           - index: test-*
           - {query: {bool: {filter: {range: {row: {lt: 5}}} } }, size: 0, aggs: {test_percentiles: {percentiles: {field: float} } } }
+          # Testing suggesters
+          - index: test-*
+          - {query: {match_all: {} }, size: 0, suggest: {term_suggester: {text: Natalie, term: {field: name } } } }
+          - index: test-*
+          - {query: {match_all: {} }, size: 0, suggest: {completion_suggester: {prefix: doc, completion: {field: title } } } }
+          - index: test-*
+          - {query: {match_all: {} }, size: 0, suggest: {phrase_suggester: {text: Ruht, phrase: {field: name } } } }
 
   - match:    { responses.0.hits.total: 3 }
   - match:    { responses.0.aggregations.filter#test_filter.doc_count : 2 }
@@ -59,6 +68,9 @@ setup:
   - match:    { responses.1.aggregations.range#test_range.buckets.1.doc_count : 1 }
   - match:    { responses.2.hits.total: 4 }
   - is_true:  responses.2.aggregations.tdigest_percentiles#test_percentiles.values
+  - is_true:  responses.3.suggest.term#term_suggester
+  - is_true:  responses.4.suggest.completion#completion_suggester
+  - is_true:  responses.5.suggest.phrase#phrase_suggester
 
 ---
 "Multisearch test with typed_keys parameter for sampler and significant terms":

+ 66 - 0
rest-api-spec/src/main/resources/rest-api-spec/test/suggest/40_typed_keys.yaml

@@ -0,0 +1,66 @@
+setup:
+  - skip:
+      version: " - 5.3.99"
+      reason: typed_keys parameter was added in 5.4.0
+
+  - do:
+      indices.create:
+          index: test
+          body:
+            settings:
+              number_of_replicas: 0
+            mappings:
+              test:
+                properties:
+                  title:
+                    type: keyword
+                  suggestions:
+                    type: completion
+                    contexts:
+                        -
+                          "name" : "format"
+                          "type" : "category"
+
+  - do:
+     bulk:
+        refresh: true
+        index: test
+        type: test
+        body:
+          - '{"index": {}}'
+          - '{"title": "Elasticsearch in Action", "suggestions": {"input": "ELK in Action", "contexts": {"format": "ebook"}}}'
+          - '{"index": {}}'
+          - '{"title": "Elasticsearch - The Definitive Guide", "suggestions": {"input": ["Elasticsearch in Action"]}}'
+
+---
+"Test typed keys parameter for suggesters":
+
+  - do:
+      search:
+        typed_keys: true
+        body:
+          query:
+            match_all: {}
+          suggest:
+            text: "Elastic"
+            term_suggester:
+              term:
+                field: title
+            completion_suggester:
+              prefix: "Elastic"
+              completion:
+                field: suggestions
+            context_suggester:
+              prefix: "Elastic"
+              completion:
+                field: suggestions
+                contexts:
+                  format: "ebook"
+            phrase_suggester:
+              phrase:
+                field: title
+
+  - is_true: suggest.term#term_suggester
+  - is_true: suggest.completion#completion_suggester
+  - is_true: suggest.completion#context_suggester
+  - is_true: suggest.phrase#phrase_suggester