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.
This commit is contained in:
parent
63ea6f7168
commit
e2e5937455
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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]
|
||||
--------------------------------------------------
|
||||
|
|
|
@ -147,3 +147,5 @@ include::suggesters/phrase-suggest.asciidoc[]
|
|||
include::suggesters/completion-suggest.asciidoc[]
|
||||
|
||||
include::suggesters/context-suggest.asciidoc[]
|
||||
|
||||
include::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.
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -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":
|
||||
|
|
|
@ -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
|
Loading…
Reference in New Issue