Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
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
  • Loading branch information
javanna committed May 17, 2017
1 parent 9fc9db2 commit da669f0
Show file tree
Hide file tree
Showing 10 changed files with 318 additions and 24 deletions.
107 changes: 101 additions & 6 deletions core/src/main/java/org/elasticsearch/action/search/SearchResponse.java
Expand Up @@ -21,30 +21,45 @@

import org.elasticsearch.action.ActionResponse;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.StatusToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.rest.action.RestActions;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.internal.InternalSearchResponse;
import org.elasticsearch.search.profile.ProfileShardResult;
import org.elasticsearch.search.profile.SearchProfileShardResults;
import org.elasticsearch.search.suggest.Suggest;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

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.
*/
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 String scrollId;
Expand Down Expand Up @@ -175,7 +190,8 @@ public void scrollId(String scrollId) {
*
* @return The profile results or an empty map
*/
@Nullable public Map<String, ProfileShardResult> getProfileResults() {
@Nullable
public Map<String, ProfileShardResult> getProfileResults() {
return internalResponse.profile();
}

Expand All @@ -189,22 +205,101 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws

public XContentBuilder innerToXContent(XContentBuilder builder, Params params) throws IOException {
if (scrollId != null) {
builder.field("_scroll_id", scrollId);
builder.field(SCROLL_ID.getPreferredName(), scrollId);
}
builder.field("took", tookInMillis);
builder.field("timed_out", isTimedOut());
builder.field(TOOK.getPreferredName(), tookInMillis);
builder.field(TIMED_OUT.getPreferredName(), isTimedOut());
if (isTerminatedEarly() != null) {
builder.field("terminated_early", isTerminatedEarly());
builder.field(TERMINATED_EARLY.getPreferredName(), isTerminatedEarly());
}
if (getNumReducePhases() != 1) {
builder.field("num_reduce_phases", getNumReducePhases());
builder.field(NUM_REDUCE_PHASES.getPreferredName(), getNumReducePhases());
}
RestActions.buildBroadcastShardsHeader(builder, params, getTotalShards(), getSuccessfulShards(), getFailedShards(),
getShardFailures());
internalResponse.toXContent(builder, params);
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
public void readFrom(StreamInput in) throws IOException {
super.readFrom(in);
Expand Down
17 changes: 12 additions & 5 deletions core/src/main/java/org/elasticsearch/rest/action/RestActions.java
Expand Up @@ -25,6 +25,7 @@
import org.elasticsearch.action.support.broadcast.BroadcastResponse;
import org.elasticsearch.action.support.nodes.BaseNodeResponse;
import org.elasticsearch.action.support.nodes.BaseNodesResponse;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.lucene.uid.Versions;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.ToXContent.Params;
Expand All @@ -46,6 +47,12 @@

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) {
if (request.hasParam("version")) {
return request.paramAsLong("version", Versions.MATCH_ANY);
Expand All @@ -71,12 +78,12 @@ public static void buildBroadcastShardsHeader(XContentBuilder builder, Params pa
public static void buildBroadcastShardsHeader(XContentBuilder builder, Params params,
int total, int successful, int failed,
ShardOperationFailedException[] shardFailures) throws IOException {
builder.startObject("_shards");
builder.field("total", total);
builder.field("successful", successful);
builder.field("failed", failed);
builder.startObject(_SHARDS_FIELD.getPreferredName());
builder.field(TOTAL_FIELD.getPreferredName(), total);
builder.field(SUCCESSFUL_FIELD.getPreferredName(), successful);
builder.field(FAILED_FIELD.getPreferredName(), failed);
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
for (ShardOperationFailedException shardFailure : group ? ExceptionsHelper.groupBy(shardFailures) : shardFailures) {
builder.startObject();
Expand Down
8 changes: 4 additions & 4 deletions core/src/main/java/org/elasticsearch/search/SearchHits.java
Expand Up @@ -105,10 +105,10 @@ public SearchHit[] internalHits() {
return this.hits;
}

static final class Fields {
static final String HITS = "hits";
static final String TOTAL = "total";
static final String MAX_SCORE = "max_score";
public static final class Fields {
public static final String HITS = "hits";
public static final String TOTAL = "total";
public static final String MAX_SCORE = "max_score";
}

@Override
Expand Down
Expand Up @@ -60,7 +60,7 @@
*/
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) -> {
int cmp = Float.compare(second.getScore(), first.getScore());
Expand Down

0 comments on commit da669f0

Please sign in to comment.