diff options
19 files changed, 253 insertions, 24 deletions
diff --git a/core/src/main/java/org/elasticsearch/rest/action/search/RestMultiSearchAction.java b/core/src/main/java/org/elasticsearch/rest/action/search/RestMultiSearchAction.java index 8913fc637b..f27ba5018e 100644 --- a/core/src/main/java/org/elasticsearch/rest/action/search/RestMultiSearchAction.java +++ b/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; diff --git a/core/src/main/java/org/elasticsearch/rest/action/search/RestSearchAction.java b/core/src/main/java/org/elasticsearch/rest/action/search/RestSearchAction.java index fe2522f31d..bf8308202b 100644 --- a/core/src/main/java/org/elasticsearch/rest/action/search/RestSearchAction.java +++ b/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); diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/InternalAggregation.java b/core/src/main/java/org/elasticsearch/search/aggregations/InternalAggregation.java index 6dfd6fcc79..6af896426a 100644 --- a/core/src/main/java/org/elasticsearch/search/aggregations/InternalAggregation.java +++ b/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); diff --git a/core/src/main/java/org/elasticsearch/search/suggest/Suggest.java b/core/src/main/java/org/elasticsearch/search/suggest/Suggest.java index ec7294f900..f1875564f8 100644 --- a/core/src/main/java/org/elasticsearch/search/suggest/Suggest.java +++ b/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); } diff --git a/core/src/main/java/org/elasticsearch/search/suggest/completion/CompletionSuggestion.java b/core/src/main/java/org/elasticsearch/search/suggest/completion/CompletionSuggestion.java index 1d8da83271..e33e421f77 100644 --- a/core/src/main/java/org/elasticsearch/search/suggest/completion/CompletionSuggestion.java +++ b/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,11 +167,16 @@ 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(); } diff --git a/core/src/main/java/org/elasticsearch/search/suggest/phrase/PhraseSuggestion.java b/core/src/main/java/org/elasticsearch/search/suggest/phrase/PhraseSuggestion.java index 23949f1a09..e673ccb128 100644 --- a/core/src/main/java/org/elasticsearch/search/suggest/phrase/PhraseSuggestion.java +++ b/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,11 +43,16 @@ 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(); } diff --git a/core/src/main/java/org/elasticsearch/search/suggest/term/TermSuggestion.java b/core/src/main/java/org/elasticsearch/search/suggest/term/TermSuggestion.java index b0586b7287..68aed8b80a 100644 --- a/core/src/main/java/org/elasticsearch/search/suggest/term/TermSuggestion.java +++ b/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,11 +98,16 @@ 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) { case SCORE: diff --git a/docs/reference/aggregations/misc.asciidoc b/docs/reference/aggregations/misc.asciidoc index a3d9f61671..5998c0c12c 100644 --- a/docs/reference/aggregations/misc.asciidoc +++ b/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] -------------------------------------------------- diff --git a/docs/reference/search/suggesters.asciidoc b/docs/reference/search/suggesters.asciidoc index a79f0ac701..2cfd2a4f04 100644 --- a/docs/reference/search/suggesters.asciidoc +++ b/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[] diff --git a/docs/reference/search/suggesters/misc.asciidoc b/docs/reference/search/suggesters/misc.asciidoc new file mode 100644 index 0000000000..6866df24c0 --- /dev/null +++ b/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. diff --git a/modules/lang-mustache/src/main/java/org/elasticsearch/script/mustache/RestMultiSearchTemplateAction.java b/modules/lang-mustache/src/main/java/org/elasticsearch/script/mustache/RestMultiSearchTemplateAction.java index 835eea7fbf..91d89d1253 100644 --- a/modules/lang-mustache/src/main/java/org/elasticsearch/script/mustache/RestMultiSearchTemplateAction.java +++ b/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; diff --git a/modules/lang-mustache/src/main/java/org/elasticsearch/script/mustache/RestSearchTemplateAction.java b/modules/lang-mustache/src/main/java/org/elasticsearch/script/mustache/RestSearchTemplateAction.java index f1c1b15b2f..a1abe5ad9a 100644 --- a/modules/lang-mustache/src/main/java/org/elasticsearch/script/mustache/RestSearchTemplateAction.java +++ b/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 { diff --git a/modules/lang-mustache/src/test/resources/rest-api-spec/test/lang_mustache/60_typed_keys.yaml b/modules/lang-mustache/src/test/resources/rest-api-spec/test/lang_mustache/60_typed_keys.yaml index df9a42f87d..c4adfbb106 100644 --- a/modules/lang-mustache/src/test/resources/rest-api-spec/test/lang_mustache/60_typed_keys.yaml +++ b/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 diff --git a/rest-api-spec/src/main/resources/rest-api-spec/api/msearch.json b/rest-api-spec/src/main/resources/rest-api-spec/api/msearch.json index 87d5604cff..69d9145207 100644 --- a/rest-api-spec/src/main/resources/rest-api-spec/api/msearch.json +++ b/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" } } }, diff --git a/rest-api-spec/src/main/resources/rest-api-spec/api/msearch_template.json b/rest-api-spec/src/main/resources/rest-api-spec/api/msearch_template.json index b6254f4a5d..2e7de08e76 100644 --- a/rest-api-spec/src/main/resources/rest-api-spec/api/msearch_template.json +++ b/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" } } }, diff --git a/rest-api-spec/src/main/resources/rest-api-spec/api/search.json b/rest-api-spec/src/main/resources/rest-api-spec/api/search.json index 1807d25236..328383e163 100644 --- a/rest-api-spec/src/main/resources/rest-api-spec/api/search.json +++ b/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", diff --git a/rest-api-spec/src/main/resources/rest-api-spec/api/search_template.json b/rest-api-spec/src/main/resources/rest-api-spec/api/search_template.json index 904027623b..c58cf21991 100644 --- a/rest-api-spec/src/main/resources/rest-api-spec/api/search_template.json +++ b/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" } } }, diff --git a/rest-api-spec/src/main/resources/rest-api-spec/test/msearch/20_typed_keys.yaml b/rest-api-spec/src/main/resources/rest-api-spec/test/msearch/20_typed_keys.yaml index 7f0fad201f..9c9b7de064 100644 --- a/rest-api-spec/src/main/resources/rest-api-spec/test/msearch/20_typed_keys.yaml +++ b/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": diff --git a/rest-api-spec/src/main/resources/rest-api-spec/test/suggest/40_typed_keys.yaml b/rest-api-spec/src/main/resources/rest-api-spec/test/suggest/40_typed_keys.yaml new file mode 100644 index 0000000000..139c972eea --- /dev/null +++ b/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 |