Add fromXContent method to SearchResponse (#24720)
SearchResponse#fromXContent allows to parse a search response, including search hits, aggregations, suggestions and profile results. Only the aggs that we can parse today are supported (which means all of them but a couple that are left to support). SearchResponseTests reuses the existing test infra to randomize aggregations, suggestions and profile response. Relates to #23331
This commit is contained in:
parent
9fc9db26fd
commit
da669f0554
|
@ -21,30 +21,45 @@ package org.elasticsearch.action.search;
|
||||||
|
|
||||||
import org.elasticsearch.action.ActionResponse;
|
import org.elasticsearch.action.ActionResponse;
|
||||||
import org.elasticsearch.common.Nullable;
|
import org.elasticsearch.common.Nullable;
|
||||||
|
import org.elasticsearch.common.ParseField;
|
||||||
import org.elasticsearch.common.Strings;
|
import org.elasticsearch.common.Strings;
|
||||||
import org.elasticsearch.common.io.stream.StreamInput;
|
import org.elasticsearch.common.io.stream.StreamInput;
|
||||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||||
import org.elasticsearch.common.unit.TimeValue;
|
import org.elasticsearch.common.unit.TimeValue;
|
||||||
import org.elasticsearch.common.xcontent.StatusToXContentObject;
|
import org.elasticsearch.common.xcontent.StatusToXContentObject;
|
||||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||||
|
import org.elasticsearch.common.xcontent.XContentParser;
|
||||||
import org.elasticsearch.rest.RestStatus;
|
import org.elasticsearch.rest.RestStatus;
|
||||||
import org.elasticsearch.rest.action.RestActions;
|
import org.elasticsearch.rest.action.RestActions;
|
||||||
import org.elasticsearch.search.SearchHits;
|
import org.elasticsearch.search.SearchHits;
|
||||||
import org.elasticsearch.search.aggregations.Aggregations;
|
import org.elasticsearch.search.aggregations.Aggregations;
|
||||||
import org.elasticsearch.search.internal.InternalSearchResponse;
|
import org.elasticsearch.search.internal.InternalSearchResponse;
|
||||||
import org.elasticsearch.search.profile.ProfileShardResult;
|
import org.elasticsearch.search.profile.ProfileShardResult;
|
||||||
|
import org.elasticsearch.search.profile.SearchProfileShardResults;
|
||||||
import org.elasticsearch.search.suggest.Suggest;
|
import org.elasticsearch.search.suggest.Suggest;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import static org.elasticsearch.action.search.ShardSearchFailure.readShardSearchFailure;
|
import static org.elasticsearch.action.search.ShardSearchFailure.readShardSearchFailure;
|
||||||
|
import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken;
|
||||||
|
import static org.elasticsearch.common.xcontent.XContentParserUtils.throwUnknownField;
|
||||||
|
import static org.elasticsearch.common.xcontent.XContentParserUtils.throwUnknownToken;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A response of a search request.
|
* A response of a search request.
|
||||||
*/
|
*/
|
||||||
public class SearchResponse extends ActionResponse implements StatusToXContentObject {
|
public class SearchResponse extends ActionResponse implements StatusToXContentObject {
|
||||||
|
|
||||||
|
private static final ParseField SCROLL_ID = new ParseField("_scroll_id");
|
||||||
|
private static final ParseField TOOK = new ParseField("took");
|
||||||
|
private static final ParseField TIMED_OUT = new ParseField("timed_out");
|
||||||
|
private static final ParseField TERMINATED_EARLY = new ParseField("terminated_early");
|
||||||
|
private static final ParseField NUM_REDUCE_PHASES = new ParseField("num_reduce_phases");
|
||||||
|
|
||||||
private SearchResponseSections internalResponse;
|
private SearchResponseSections internalResponse;
|
||||||
|
|
||||||
private String scrollId;
|
private String scrollId;
|
||||||
|
@ -175,7 +190,8 @@ public class SearchResponse extends ActionResponse implements StatusToXContentOb
|
||||||
*
|
*
|
||||||
* @return The profile results or an empty map
|
* @return The profile results or an empty map
|
||||||
*/
|
*/
|
||||||
@Nullable public Map<String, ProfileShardResult> getProfileResults() {
|
@Nullable
|
||||||
|
public Map<String, ProfileShardResult> getProfileResults() {
|
||||||
return internalResponse.profile();
|
return internalResponse.profile();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -189,15 +205,15 @@ public class SearchResponse extends ActionResponse implements StatusToXContentOb
|
||||||
|
|
||||||
public XContentBuilder innerToXContent(XContentBuilder builder, Params params) throws IOException {
|
public XContentBuilder innerToXContent(XContentBuilder builder, Params params) throws IOException {
|
||||||
if (scrollId != null) {
|
if (scrollId != null) {
|
||||||
builder.field("_scroll_id", scrollId);
|
builder.field(SCROLL_ID.getPreferredName(), scrollId);
|
||||||
}
|
}
|
||||||
builder.field("took", tookInMillis);
|
builder.field(TOOK.getPreferredName(), tookInMillis);
|
||||||
builder.field("timed_out", isTimedOut());
|
builder.field(TIMED_OUT.getPreferredName(), isTimedOut());
|
||||||
if (isTerminatedEarly() != null) {
|
if (isTerminatedEarly() != null) {
|
||||||
builder.field("terminated_early", isTerminatedEarly());
|
builder.field(TERMINATED_EARLY.getPreferredName(), isTerminatedEarly());
|
||||||
}
|
}
|
||||||
if (getNumReducePhases() != 1) {
|
if (getNumReducePhases() != 1) {
|
||||||
builder.field("num_reduce_phases", getNumReducePhases());
|
builder.field(NUM_REDUCE_PHASES.getPreferredName(), getNumReducePhases());
|
||||||
}
|
}
|
||||||
RestActions.buildBroadcastShardsHeader(builder, params, getTotalShards(), getSuccessfulShards(), getFailedShards(),
|
RestActions.buildBroadcastShardsHeader(builder, params, getTotalShards(), getSuccessfulShards(), getFailedShards(),
|
||||||
getShardFailures());
|
getShardFailures());
|
||||||
|
@ -205,6 +221,85 @@ public class SearchResponse extends ActionResponse implements StatusToXContentOb
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static SearchResponse fromXContent(XContentParser parser) throws IOException {
|
||||||
|
ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.currentToken(), parser::getTokenLocation);
|
||||||
|
XContentParser.Token token;
|
||||||
|
String currentFieldName = null;
|
||||||
|
SearchHits hits = null;
|
||||||
|
Aggregations aggs = null;
|
||||||
|
Suggest suggest = null;
|
||||||
|
SearchProfileShardResults profile = null;
|
||||||
|
boolean timedOut = false;
|
||||||
|
Boolean terminatedEarly = null;
|
||||||
|
int numReducePhases = 1;
|
||||||
|
long tookInMillis = -1;
|
||||||
|
int successfulShards = -1;
|
||||||
|
int totalShards = -1;
|
||||||
|
String scrollId = null;
|
||||||
|
List<ShardSearchFailure> failures = new ArrayList<>();
|
||||||
|
while((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
|
||||||
|
if (token == XContentParser.Token.FIELD_NAME) {
|
||||||
|
currentFieldName = parser.currentName();
|
||||||
|
} else if (token.isValue()) {
|
||||||
|
if (SCROLL_ID.match(currentFieldName)) {
|
||||||
|
scrollId = parser.text();
|
||||||
|
} else if (TOOK.match(currentFieldName)) {
|
||||||
|
tookInMillis = parser.longValue();
|
||||||
|
} else if (TIMED_OUT.match(currentFieldName)) {
|
||||||
|
timedOut = parser.booleanValue();
|
||||||
|
} else if (TERMINATED_EARLY.match(currentFieldName)) {
|
||||||
|
terminatedEarly = parser.booleanValue();
|
||||||
|
} else if (NUM_REDUCE_PHASES.match(currentFieldName)) {
|
||||||
|
numReducePhases = parser.intValue();
|
||||||
|
} else {
|
||||||
|
throwUnknownField(currentFieldName, parser.getTokenLocation());
|
||||||
|
}
|
||||||
|
} else if (token == XContentParser.Token.START_OBJECT) {
|
||||||
|
if (SearchHits.Fields.HITS.equals(currentFieldName)) {
|
||||||
|
hits = SearchHits.fromXContent(parser);
|
||||||
|
} else if (Aggregations.AGGREGATIONS_FIELD.equals(currentFieldName)) {
|
||||||
|
aggs = Aggregations.fromXContent(parser);
|
||||||
|
} else if (Suggest.NAME.equals(currentFieldName)) {
|
||||||
|
suggest = Suggest.fromXContent(parser);
|
||||||
|
} else if (SearchProfileShardResults.PROFILE_FIELD.equals(currentFieldName)) {
|
||||||
|
profile = SearchProfileShardResults.fromXContent(parser);
|
||||||
|
} else if (RestActions._SHARDS_FIELD.match(currentFieldName)) {
|
||||||
|
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
|
||||||
|
if (token == XContentParser.Token.FIELD_NAME) {
|
||||||
|
currentFieldName = parser.currentName();
|
||||||
|
} else if (token.isValue()) {
|
||||||
|
if (RestActions.FAILED_FIELD.match(currentFieldName)) {
|
||||||
|
parser.intValue(); // we don't need it but need to consume it
|
||||||
|
} else if (RestActions.SUCCESSFUL_FIELD.match(currentFieldName)) {
|
||||||
|
successfulShards = parser.intValue();
|
||||||
|
} else if (RestActions.TOTAL_FIELD.match(currentFieldName)) {
|
||||||
|
totalShards = parser.intValue();
|
||||||
|
} else {
|
||||||
|
throwUnknownField(currentFieldName, parser.getTokenLocation());
|
||||||
|
}
|
||||||
|
} else if (token == XContentParser.Token.START_ARRAY) {
|
||||||
|
if (RestActions.FAILURES_FIELD.match(currentFieldName)) {
|
||||||
|
while((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
|
||||||
|
failures.add(ShardSearchFailure.fromXContent(parser));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throwUnknownField(currentFieldName, parser.getTokenLocation());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throwUnknownToken(token, parser.getTokenLocation());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throwUnknownField(currentFieldName, parser.getTokenLocation());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SearchResponseSections searchResponseSections = new SearchResponseSections(hits, aggs, suggest, timedOut, terminatedEarly,
|
||||||
|
profile, numReducePhases);
|
||||||
|
return new SearchResponse(searchResponseSections, scrollId, totalShards, successfulShards, tookInMillis,
|
||||||
|
failures.toArray(new ShardSearchFailure[failures.size()]));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void readFrom(StreamInput in) throws IOException {
|
public void readFrom(StreamInput in) throws IOException {
|
||||||
super.readFrom(in);
|
super.readFrom(in);
|
||||||
|
|
|
@ -25,6 +25,7 @@ import org.elasticsearch.action.ShardOperationFailedException;
|
||||||
import org.elasticsearch.action.support.broadcast.BroadcastResponse;
|
import org.elasticsearch.action.support.broadcast.BroadcastResponse;
|
||||||
import org.elasticsearch.action.support.nodes.BaseNodeResponse;
|
import org.elasticsearch.action.support.nodes.BaseNodeResponse;
|
||||||
import org.elasticsearch.action.support.nodes.BaseNodesResponse;
|
import org.elasticsearch.action.support.nodes.BaseNodesResponse;
|
||||||
|
import org.elasticsearch.common.ParseField;
|
||||||
import org.elasticsearch.common.lucene.uid.Versions;
|
import org.elasticsearch.common.lucene.uid.Versions;
|
||||||
import org.elasticsearch.common.xcontent.ToXContent;
|
import org.elasticsearch.common.xcontent.ToXContent;
|
||||||
import org.elasticsearch.common.xcontent.ToXContent.Params;
|
import org.elasticsearch.common.xcontent.ToXContent.Params;
|
||||||
|
@ -46,6 +47,12 @@ import java.util.List;
|
||||||
|
|
||||||
public class RestActions {
|
public class RestActions {
|
||||||
|
|
||||||
|
public static final ParseField _SHARDS_FIELD = new ParseField("_shards");
|
||||||
|
public static final ParseField TOTAL_FIELD = new ParseField("total");
|
||||||
|
public static final ParseField SUCCESSFUL_FIELD = new ParseField("successful");
|
||||||
|
public static final ParseField FAILED_FIELD = new ParseField("failed");
|
||||||
|
public static final ParseField FAILURES_FIELD = new ParseField("failures");
|
||||||
|
|
||||||
public static long parseVersion(RestRequest request) {
|
public static long parseVersion(RestRequest request) {
|
||||||
if (request.hasParam("version")) {
|
if (request.hasParam("version")) {
|
||||||
return request.paramAsLong("version", Versions.MATCH_ANY);
|
return request.paramAsLong("version", Versions.MATCH_ANY);
|
||||||
|
@ -71,12 +78,12 @@ public class RestActions {
|
||||||
public static void buildBroadcastShardsHeader(XContentBuilder builder, Params params,
|
public static void buildBroadcastShardsHeader(XContentBuilder builder, Params params,
|
||||||
int total, int successful, int failed,
|
int total, int successful, int failed,
|
||||||
ShardOperationFailedException[] shardFailures) throws IOException {
|
ShardOperationFailedException[] shardFailures) throws IOException {
|
||||||
builder.startObject("_shards");
|
builder.startObject(_SHARDS_FIELD.getPreferredName());
|
||||||
builder.field("total", total);
|
builder.field(TOTAL_FIELD.getPreferredName(), total);
|
||||||
builder.field("successful", successful);
|
builder.field(SUCCESSFUL_FIELD.getPreferredName(), successful);
|
||||||
builder.field("failed", failed);
|
builder.field(FAILED_FIELD.getPreferredName(), failed);
|
||||||
if (shardFailures != null && shardFailures.length > 0) {
|
if (shardFailures != null && shardFailures.length > 0) {
|
||||||
builder.startArray("failures");
|
builder.startArray(FAILURES_FIELD.getPreferredName());
|
||||||
final boolean group = params.paramAsBoolean("group_shard_failures", true); // we group by default
|
final boolean group = params.paramAsBoolean("group_shard_failures", true); // we group by default
|
||||||
for (ShardOperationFailedException shardFailure : group ? ExceptionsHelper.groupBy(shardFailures) : shardFailures) {
|
for (ShardOperationFailedException shardFailure : group ? ExceptionsHelper.groupBy(shardFailures) : shardFailures) {
|
||||||
builder.startObject();
|
builder.startObject();
|
||||||
|
|
|
@ -105,10 +105,10 @@ public final class SearchHits implements Streamable, ToXContent, Iterable<Search
|
||||||
return this.hits;
|
return this.hits;
|
||||||
}
|
}
|
||||||
|
|
||||||
static final class Fields {
|
public static final class Fields {
|
||||||
static final String HITS = "hits";
|
public static final String HITS = "hits";
|
||||||
static final String TOTAL = "total";
|
public static final String TOTAL = "total";
|
||||||
static final String MAX_SCORE = "max_score";
|
public static final String MAX_SCORE = "max_score";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -60,7 +60,7 @@ import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpect
|
||||||
*/
|
*/
|
||||||
public class Suggest implements Iterable<Suggest.Suggestion<? extends Entry<? extends Option>>>, Streamable, ToXContent {
|
public class Suggest implements Iterable<Suggest.Suggestion<? extends Entry<? extends Option>>>, Streamable, ToXContent {
|
||||||
|
|
||||||
static final String NAME = "suggest";
|
public static final String NAME = "suggest";
|
||||||
|
|
||||||
public static final Comparator<Option> COMPARATOR = (first, second) -> {
|
public static final Comparator<Option> COMPARATOR = (first, second) -> {
|
||||||
int cmp = Float.compare(second.getScore(), first.getScore());
|
int cmp = Float.compare(second.getScore(), first.getScore());
|
||||||
|
|
|
@ -0,0 +1,180 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Elasticsearch under one or more contributor
|
||||||
|
* license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright
|
||||||
|
* ownership. Elasticsearch licenses this file to you under
|
||||||
|
* the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
* not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.elasticsearch.action.search;
|
||||||
|
|
||||||
|
import org.elasticsearch.common.Strings;
|
||||||
|
import org.elasticsearch.common.bytes.BytesReference;
|
||||||
|
import org.elasticsearch.common.text.Text;
|
||||||
|
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
|
||||||
|
import org.elasticsearch.common.xcontent.ToXContent;
|
||||||
|
import org.elasticsearch.common.xcontent.XContentHelper;
|
||||||
|
import org.elasticsearch.common.xcontent.XContentParser;
|
||||||
|
import org.elasticsearch.common.xcontent.XContentType;
|
||||||
|
import org.elasticsearch.rest.action.search.RestSearchAction;
|
||||||
|
import org.elasticsearch.search.SearchHit;
|
||||||
|
import org.elasticsearch.search.SearchHits;
|
||||||
|
import org.elasticsearch.search.SearchHitsTests;
|
||||||
|
import org.elasticsearch.search.aggregations.AggregationsTests;
|
||||||
|
import org.elasticsearch.search.aggregations.InternalAggregations;
|
||||||
|
import org.elasticsearch.search.internal.InternalSearchResponse;
|
||||||
|
import org.elasticsearch.search.profile.SearchProfileShardResults;
|
||||||
|
import org.elasticsearch.search.profile.SearchProfileShardResultsTests;
|
||||||
|
import org.elasticsearch.search.suggest.Suggest;
|
||||||
|
import org.elasticsearch.search.suggest.SuggestTests;
|
||||||
|
import org.elasticsearch.test.ESTestCase;
|
||||||
|
import org.elasticsearch.test.InternalAggregationTestCase;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static java.util.Collections.singletonMap;
|
||||||
|
import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken;
|
||||||
|
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertToXContentEquivalent;
|
||||||
|
|
||||||
|
public class SearchResponseTests extends ESTestCase {
|
||||||
|
|
||||||
|
private static final NamedXContentRegistry xContentRegistry;
|
||||||
|
static {
|
||||||
|
List<NamedXContentRegistry.Entry> namedXContents = new ArrayList<>(InternalAggregationTestCase.getDefaultNamedXContents());
|
||||||
|
namedXContents.addAll(SuggestTests.getDefaultNamedXContents());
|
||||||
|
xContentRegistry = new NamedXContentRegistry(namedXContents);
|
||||||
|
}
|
||||||
|
|
||||||
|
private AggregationsTests aggregationsTests = new AggregationsTests();
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void init() throws Exception {
|
||||||
|
aggregationsTests.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void cleanUp() throws Exception {
|
||||||
|
aggregationsTests.cleanUp();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected NamedXContentRegistry xContentRegistry() {
|
||||||
|
return xContentRegistry;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SearchResponse createTestItem(ShardSearchFailure... shardSearchFailures) {
|
||||||
|
SearchHits hits = SearchHitsTests.createTestItem();
|
||||||
|
boolean timedOut = randomBoolean();
|
||||||
|
Boolean terminatedEarly = randomBoolean() ? null : randomBoolean();
|
||||||
|
int numReducePhases = randomIntBetween(1, 10);
|
||||||
|
long tookInMillis = randomNonNegativeLong();
|
||||||
|
int successfulShards = randomInt();
|
||||||
|
int totalShards = randomInt();
|
||||||
|
|
||||||
|
InternalAggregations aggregations = aggregationsTests.createTestInstance();
|
||||||
|
Suggest suggest = SuggestTests.createTestItem();
|
||||||
|
SearchProfileShardResults profileShardResults = SearchProfileShardResultsTests.createTestItem();
|
||||||
|
|
||||||
|
InternalSearchResponse internalSearchResponse = new InternalSearchResponse(hits, aggregations, suggest, profileShardResults,
|
||||||
|
timedOut, terminatedEarly, numReducePhases);
|
||||||
|
return new SearchResponse(internalSearchResponse, null, totalShards, successfulShards, tookInMillis, shardSearchFailures);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testFromXContent() throws IOException {
|
||||||
|
// the "_shard/total/failures" section makes if impossible to directly compare xContent, so we omit it here
|
||||||
|
SearchResponse response = createTestItem();
|
||||||
|
XContentType xcontentType = randomFrom(XContentType.values());
|
||||||
|
boolean humanReadable = randomBoolean();
|
||||||
|
final ToXContent.Params params = new ToXContent.MapParams(singletonMap(RestSearchAction.TYPED_KEYS_PARAM, "true"));
|
||||||
|
BytesReference originalBytes = toShuffledXContent(response, xcontentType, params, humanReadable);
|
||||||
|
try (XContentParser parser = createParser(xcontentType.xContent(), originalBytes)) {
|
||||||
|
ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.nextToken(), parser::getTokenLocation);
|
||||||
|
SearchResponse parsed = SearchResponse.fromXContent(parser);
|
||||||
|
assertToXContentEquivalent(originalBytes, XContentHelper.toXContent(parsed, xcontentType, params, humanReadable), xcontentType);
|
||||||
|
assertEquals(XContentParser.Token.END_OBJECT, parser.currentToken());
|
||||||
|
assertNull(parser.nextToken());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The "_shard/total/failures" section makes if impossible to directly compare xContent, because
|
||||||
|
* the failures in the parsed SearchResponse are wrapped in an extra ElasticSearchException on the client side.
|
||||||
|
* Because of this, in this special test case we compare the "top level" fields for equality
|
||||||
|
* and the subsections xContent equivalence independently
|
||||||
|
*/
|
||||||
|
public void testFromXContentWithFailures() throws IOException {
|
||||||
|
int numFailures = randomIntBetween(1, 5);
|
||||||
|
ShardSearchFailure[] failures = new ShardSearchFailure[numFailures];
|
||||||
|
for (int i = 0; i < failures.length; i++) {
|
||||||
|
failures[i] = ShardSearchFailureTests.createTestItem();
|
||||||
|
}
|
||||||
|
SearchResponse response = createTestItem(failures);
|
||||||
|
XContentType xcontentType = randomFrom(XContentType.values());
|
||||||
|
final ToXContent.Params params = new ToXContent.MapParams(singletonMap(RestSearchAction.TYPED_KEYS_PARAM, "true"));
|
||||||
|
BytesReference originalBytes = toShuffledXContent(response, xcontentType, params, randomBoolean());
|
||||||
|
try (XContentParser parser = createParser(xcontentType.xContent(), originalBytes)) {
|
||||||
|
ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.nextToken(), parser::getTokenLocation);
|
||||||
|
SearchResponse parsed = SearchResponse.fromXContent(parser);
|
||||||
|
for (int i = 0; i < parsed.getShardFailures().length; i++) {
|
||||||
|
ShardSearchFailure parsedFailure = parsed.getShardFailures()[i];
|
||||||
|
ShardSearchFailure originalFailure = failures[i];
|
||||||
|
assertEquals(originalFailure.index(), parsedFailure.index());
|
||||||
|
assertEquals(originalFailure.shard().getNodeId(), parsedFailure.shard().getNodeId());
|
||||||
|
assertEquals(originalFailure.shardId(), parsedFailure.shardId());
|
||||||
|
String originalMsg = originalFailure.getCause().getMessage();
|
||||||
|
assertEquals(parsedFailure.getCause().getMessage(), "Elasticsearch exception [type=parsing_exception, reason=" +
|
||||||
|
originalMsg + "]");
|
||||||
|
String nestedMsg = originalFailure.getCause().getCause().getMessage();
|
||||||
|
assertEquals(parsedFailure.getCause().getCause().getMessage(),
|
||||||
|
"Elasticsearch exception [type=illegal_argument_exception, reason=" + nestedMsg + "]");
|
||||||
|
}
|
||||||
|
assertEquals(XContentParser.Token.END_OBJECT, parser.currentToken());
|
||||||
|
assertNull(parser.nextToken());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testToXContent() {
|
||||||
|
SearchHit hit = new SearchHit(1, "id1", new Text("type"), Collections.emptyMap());
|
||||||
|
hit.score(2.0f);
|
||||||
|
SearchHit[] hits = new SearchHit[] { hit };
|
||||||
|
SearchResponse response = new SearchResponse(
|
||||||
|
new InternalSearchResponse(new SearchHits(hits, 100, 1.5f), null, null, null, false, null, 1), null, 0, 0, 0,
|
||||||
|
new ShardSearchFailure[0]);
|
||||||
|
StringBuilder expectedString = new StringBuilder();
|
||||||
|
expectedString.append("{");
|
||||||
|
{
|
||||||
|
expectedString.append("\"took\":0,");
|
||||||
|
expectedString.append("\"timed_out\":false,");
|
||||||
|
expectedString.append("\"_shards\":");
|
||||||
|
{
|
||||||
|
expectedString.append("{\"total\":0,");
|
||||||
|
expectedString.append("\"successful\":0,");
|
||||||
|
expectedString.append("\"failed\":0},");
|
||||||
|
}
|
||||||
|
expectedString.append("\"hits\":");
|
||||||
|
{
|
||||||
|
expectedString.append("{\"total\":100,");
|
||||||
|
expectedString.append("\"max_score\":1.5,");
|
||||||
|
expectedString.append("\"hits\":[{\"_type\":\"type\",\"_id\":\"id1\",\"_score\":2.0}]}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expectedString.append("}");
|
||||||
|
assertEquals(expectedString.toString(), Strings.toString(response));
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,6 +22,7 @@ package org.elasticsearch.action.search;
|
||||||
import org.elasticsearch.action.OriginalIndices;
|
import org.elasticsearch.action.OriginalIndices;
|
||||||
import org.elasticsearch.common.ParsingException;
|
import org.elasticsearch.common.ParsingException;
|
||||||
import org.elasticsearch.common.bytes.BytesReference;
|
import org.elasticsearch.common.bytes.BytesReference;
|
||||||
|
import org.elasticsearch.common.xcontent.ToXContent;
|
||||||
import org.elasticsearch.common.xcontent.XContentParser;
|
import org.elasticsearch.common.xcontent.XContentParser;
|
||||||
import org.elasticsearch.common.xcontent.XContentType;
|
import org.elasticsearch.common.xcontent.XContentType;
|
||||||
import org.elasticsearch.index.Index;
|
import org.elasticsearch.index.Index;
|
||||||
|
@ -50,7 +51,7 @@ public class ShardSearchFailureTests extends ESTestCase {
|
||||||
ShardSearchFailure response = createTestItem();
|
ShardSearchFailure response = createTestItem();
|
||||||
XContentType xContentType = randomFrom(XContentType.values());
|
XContentType xContentType = randomFrom(XContentType.values());
|
||||||
boolean humanReadable = randomBoolean();
|
boolean humanReadable = randomBoolean();
|
||||||
BytesReference originalBytes = toXContent(response, xContentType, humanReadable);
|
BytesReference originalBytes = toShuffledXContent(response, xContentType, ToXContent.EMPTY_PARAMS, humanReadable);
|
||||||
|
|
||||||
ShardSearchFailure parsed;
|
ShardSearchFailure parsed;
|
||||||
try (XContentParser parser = createParser(xContentType.xContent(), originalBytes)) {
|
try (XContentParser parser = createParser(xContentType.xContent(), originalBytes)) {
|
||||||
|
|
|
@ -178,7 +178,7 @@ public class AggregationsTests extends ESTestCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static InternalAggregations createTestInstance() {
|
public final InternalAggregations createTestInstance() {
|
||||||
return createTestInstance(1, 0, 5);
|
return createTestInstance(1, 0, 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -62,7 +62,7 @@ public class SearchProfileShardResultsTests extends ESTestCase {
|
||||||
XContentType xContentType = randomFrom(XContentType.values());
|
XContentType xContentType = randomFrom(XContentType.values());
|
||||||
boolean humanReadable = randomBoolean();
|
boolean humanReadable = randomBoolean();
|
||||||
BytesReference originalBytes = toShuffledXContent(shardResult, xContentType, ToXContent.EMPTY_PARAMS, humanReadable);
|
BytesReference originalBytes = toShuffledXContent(shardResult, xContentType, ToXContent.EMPTY_PARAMS, humanReadable);
|
||||||
SearchProfileShardResults parsed = null;
|
SearchProfileShardResults parsed;
|
||||||
try (XContentParser parser = createParser(xContentType.xContent(), originalBytes)) {
|
try (XContentParser parser = createParser(xContentType.xContent(), originalBytes)) {
|
||||||
ensureExpectedToken(parser.nextToken(), XContentParser.Token.START_OBJECT, parser::getTokenLocation);
|
ensureExpectedToken(parser.nextToken(), XContentParser.Token.START_OBJECT, parser::getTokenLocation);
|
||||||
ensureFieldName(parser, parser.nextToken(), SearchProfileShardResults.PROFILE_FIELD);
|
ensureFieldName(parser, parser.nextToken(), SearchProfileShardResults.PROFILE_FIELD);
|
||||||
|
|
|
@ -49,15 +49,26 @@ import static org.hamcrest.Matchers.equalTo;
|
||||||
|
|
||||||
public class SuggestTests extends ESTestCase {
|
public class SuggestTests extends ESTestCase {
|
||||||
|
|
||||||
static NamedXContentRegistry getSuggestersRegistry() {
|
private static final NamedXContentRegistry xContentRegistry;
|
||||||
List<NamedXContentRegistry.Entry> namedXContents = new ArrayList<>();
|
private static final List<NamedXContentRegistry.Entry> namedXContents;
|
||||||
|
|
||||||
|
static {
|
||||||
|
namedXContents = new ArrayList<>();
|
||||||
namedXContents.add(new NamedXContentRegistry.Entry(Suggest.Suggestion.class, new ParseField("term"),
|
namedXContents.add(new NamedXContentRegistry.Entry(Suggest.Suggestion.class, new ParseField("term"),
|
||||||
(parser, context) -> TermSuggestion.fromXContent(parser, (String)context)));
|
(parser, context) -> TermSuggestion.fromXContent(parser, (String)context)));
|
||||||
namedXContents.add(new NamedXContentRegistry.Entry(Suggest.Suggestion.class, new ParseField("phrase"),
|
namedXContents.add(new NamedXContentRegistry.Entry(Suggest.Suggestion.class, new ParseField("phrase"),
|
||||||
(parser, context) -> PhraseSuggestion.fromXContent(parser, (String)context)));
|
(parser, context) -> PhraseSuggestion.fromXContent(parser, (String)context)));
|
||||||
namedXContents.add(new NamedXContentRegistry.Entry(Suggest.Suggestion.class, new ParseField("completion"),
|
namedXContents.add(new NamedXContentRegistry.Entry(Suggest.Suggestion.class, new ParseField("completion"),
|
||||||
(parser, context) -> CompletionSuggestion.fromXContent(parser, (String)context)));
|
(parser, context) -> CompletionSuggestion.fromXContent(parser, (String)context)));
|
||||||
return new NamedXContentRegistry(namedXContents);
|
xContentRegistry = new NamedXContentRegistry(namedXContents);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<NamedXContentRegistry.Entry> getDefaultNamedXContents() {
|
||||||
|
return namedXContents;
|
||||||
|
}
|
||||||
|
|
||||||
|
static NamedXContentRegistry getSuggestersRegistry() {
|
||||||
|
return xContentRegistry;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -68,8 +68,8 @@ public class SuggestionTests extends ESTestCase {
|
||||||
String name = randomAlphaOfLengthBetween(5, 10);
|
String name = randomAlphaOfLengthBetween(5, 10);
|
||||||
// note: size will not be rendered via "toXContent", only passed on internally on transport layer
|
// note: size will not be rendered via "toXContent", only passed on internally on transport layer
|
||||||
int size = randomInt();
|
int size = randomInt();
|
||||||
Supplier<Entry> entrySupplier = null;
|
Supplier<Entry> entrySupplier;
|
||||||
Suggestion suggestion = null;
|
Suggestion suggestion;
|
||||||
if (type == TermSuggestion.class) {
|
if (type == TermSuggestion.class) {
|
||||||
suggestion = new TermSuggestion(name, size, randomFrom(SortBy.values()));
|
suggestion = new TermSuggestion(name, size, randomFrom(SortBy.values()));
|
||||||
entrySupplier = () -> SuggestionEntryTests.createTestItem(TermSuggestion.Entry.class);
|
entrySupplier = () -> SuggestionEntryTests.createTestItem(TermSuggestion.Entry.class);
|
||||||
|
|
Loading…
Reference in New Issue