parent
b7246199db
commit
616703b880
|
@ -65,14 +65,15 @@ import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateReque
|
|||
import org.elasticsearch.action.admin.indices.validate.query.ValidateQueryRequest;
|
||||
import org.elasticsearch.action.bulk.BulkRequest;
|
||||
import org.elasticsearch.action.delete.DeleteRequest;
|
||||
import org.elasticsearch.action.explain.ExplainRequest;
|
||||
import org.elasticsearch.action.fieldcaps.FieldCapabilitiesRequest;
|
||||
import org.elasticsearch.action.get.GetRequest;
|
||||
import org.elasticsearch.action.get.MultiGetRequest;
|
||||
import org.elasticsearch.action.index.IndexRequest;
|
||||
import org.elasticsearch.action.ingest.DeletePipelineRequest;
|
||||
import org.elasticsearch.action.ingest.PutPipelineRequest;
|
||||
import org.elasticsearch.action.ingest.GetPipelineRequest;
|
||||
import org.elasticsearch.action.ingest.SimulatePipelineRequest;
|
||||
import org.elasticsearch.action.ingest.PutPipelineRequest;
|
||||
import org.elasticsearch.action.search.ClearScrollRequest;
|
||||
import org.elasticsearch.action.search.MultiSearchRequest;
|
||||
import org.elasticsearch.action.search.SearchRequest;
|
||||
|
@ -618,6 +619,19 @@ final class RequestConverters {
|
|||
return request;
|
||||
}
|
||||
|
||||
static Request explain(ExplainRequest explainRequest) throws IOException {
|
||||
Request request = new Request(HttpGet.METHOD_NAME,
|
||||
endpoint(explainRequest.index(), explainRequest.type(), explainRequest.id(), "_explain"));
|
||||
|
||||
Params params = new Params(request);
|
||||
params.withStoredFields(explainRequest.storedFields());
|
||||
params.withFetchSourceContext(explainRequest.fetchSourceContext());
|
||||
params.withRouting(explainRequest.routing());
|
||||
params.withPreference(explainRequest.preference());
|
||||
request.setEntity(createEntity(explainRequest, REQUEST_BODY_CONTENT_TYPE));
|
||||
return request;
|
||||
}
|
||||
|
||||
static Request fieldCaps(FieldCapabilitiesRequest fieldCapabilitiesRequest) {
|
||||
Request request = new Request(HttpGet.METHOD_NAME, endpoint(fieldCapabilitiesRequest.indices(), "_field_caps"));
|
||||
|
||||
|
|
|
@ -34,6 +34,8 @@ import org.elasticsearch.action.bulk.BulkRequest;
|
|||
import org.elasticsearch.action.bulk.BulkResponse;
|
||||
import org.elasticsearch.action.delete.DeleteRequest;
|
||||
import org.elasticsearch.action.delete.DeleteResponse;
|
||||
import org.elasticsearch.action.explain.ExplainRequest;
|
||||
import org.elasticsearch.action.explain.ExplainResponse;
|
||||
import org.elasticsearch.action.fieldcaps.FieldCapabilitiesRequest;
|
||||
import org.elasticsearch.action.fieldcaps.FieldCapabilitiesResponse;
|
||||
import org.elasticsearch.action.get.GetRequest;
|
||||
|
@ -614,6 +616,42 @@ public class RestHighLevelClient implements Closeable {
|
|||
SearchTemplateResponse::fromXContent, listener, emptySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a request using the Explain API.
|
||||
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-explain.html">Explain API on elastic.co</a>
|
||||
* @param explainRequest the request
|
||||
* @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
|
||||
* @return the response
|
||||
* @throws IOException in case there is a problem sending the request or parsing back the response
|
||||
*/
|
||||
public final ExplainResponse explain(ExplainRequest explainRequest, RequestOptions options) throws IOException {
|
||||
return performRequest(explainRequest, RequestConverters::explain, options,
|
||||
response -> {
|
||||
CheckedFunction<XContentParser, ExplainResponse, IOException> entityParser =
|
||||
parser -> ExplainResponse.fromXContent(parser, convertExistsResponse(response));
|
||||
return parseEntity(response.getEntity(), entityParser);
|
||||
},
|
||||
singleton(404));
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously executes a request using the Explain API.
|
||||
*
|
||||
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-explain.html">Explain API on elastic.co</a>
|
||||
* @param explainRequest the request
|
||||
* @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
|
||||
* @param listener the listener to be notified upon request completion
|
||||
*/
|
||||
public final void explainAsync(ExplainRequest explainRequest, RequestOptions options, ActionListener<ExplainResponse> listener) {
|
||||
performRequestAsync(explainRequest, RequestConverters::explain, options,
|
||||
response -> {
|
||||
CheckedFunction<XContentParser, ExplainResponse, IOException> entityParser =
|
||||
parser -> ExplainResponse.fromXContent(parser, convertExistsResponse(response));
|
||||
return parseEntity(response.getEntity(), entityParser);
|
||||
},
|
||||
listener, singleton(404));
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a request using the Ranking Evaluation API.
|
||||
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-rank-eval.html">Ranking Evaluation API
|
||||
|
|
|
@ -68,6 +68,7 @@ import org.elasticsearch.action.admin.indices.validate.query.ValidateQueryReques
|
|||
import org.elasticsearch.action.bulk.BulkRequest;
|
||||
import org.elasticsearch.action.bulk.BulkShardRequest;
|
||||
import org.elasticsearch.action.delete.DeleteRequest;
|
||||
import org.elasticsearch.action.explain.ExplainRequest;
|
||||
import org.elasticsearch.action.fieldcaps.FieldCapabilitiesRequest;
|
||||
import org.elasticsearch.action.get.GetRequest;
|
||||
import org.elasticsearch.action.get.MultiGetRequest;
|
||||
|
@ -111,6 +112,7 @@ import org.elasticsearch.common.xcontent.XContentParser;
|
|||
import org.elasticsearch.common.xcontent.XContentType;
|
||||
import org.elasticsearch.index.RandomCreateIndexGenerator;
|
||||
import org.elasticsearch.index.VersionType;
|
||||
import org.elasticsearch.index.query.QueryBuilders;
|
||||
import org.elasticsearch.index.query.TermQueryBuilder;
|
||||
import org.elasticsearch.index.rankeval.PrecisionAtK;
|
||||
import org.elasticsearch.index.rankeval.RankEvalRequest;
|
||||
|
@ -1418,6 +1420,49 @@ public class RequestConvertersTests extends ESTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
public void testExplain() throws IOException {
|
||||
String index = randomAlphaOfLengthBetween(3, 10);
|
||||
String type = randomAlphaOfLengthBetween(3, 10);
|
||||
String id = randomAlphaOfLengthBetween(3, 10);
|
||||
|
||||
ExplainRequest explainRequest = new ExplainRequest(index, type, id);
|
||||
explainRequest.query(QueryBuilders.termQuery(randomAlphaOfLengthBetween(3, 10), randomAlphaOfLengthBetween(3, 10)));
|
||||
|
||||
Map<String, String> expectedParams = new HashMap<>();
|
||||
|
||||
if (randomBoolean()) {
|
||||
String routing = randomAlphaOfLengthBetween(3, 10);
|
||||
explainRequest.routing(routing);
|
||||
expectedParams.put("routing", routing);
|
||||
}
|
||||
if (randomBoolean()) {
|
||||
String preference = randomAlphaOfLengthBetween(3, 10);
|
||||
explainRequest.preference(preference);
|
||||
expectedParams.put("preference", preference);
|
||||
}
|
||||
if (randomBoolean()) {
|
||||
String[] storedFields = generateRandomStringArray(10, 5, false);
|
||||
String storedFieldsParams = randomFields(storedFields);
|
||||
explainRequest.storedFields(storedFields);
|
||||
expectedParams.put("stored_fields", storedFieldsParams);
|
||||
}
|
||||
if (randomBoolean()) {
|
||||
randomizeFetchSourceContextParams(explainRequest::fetchSourceContext, expectedParams);
|
||||
}
|
||||
|
||||
Request request = RequestConverters.explain(explainRequest);
|
||||
StringJoiner endpoint = new StringJoiner("/", "/", "");
|
||||
endpoint.add(index)
|
||||
.add(type)
|
||||
.add(id)
|
||||
.add("_explain");
|
||||
|
||||
assertEquals(HttpGet.METHOD_NAME, request.getMethod());
|
||||
assertEquals(endpoint.toString(), request.getEndpoint());
|
||||
assertEquals(expectedParams, request.getParameters());
|
||||
assertToXContentBody(explainRequest, request.getEntity());
|
||||
}
|
||||
|
||||
public void testFieldCaps() {
|
||||
// Create a random request.
|
||||
String[] indices = randomIndicesNames(0, 5);
|
||||
|
|
|
@ -27,6 +27,8 @@ import org.apache.http.entity.StringEntity;
|
|||
import org.apache.http.nio.entity.NStringEntity;
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.elasticsearch.ElasticsearchStatusException;
|
||||
import org.elasticsearch.action.explain.ExplainRequest;
|
||||
import org.elasticsearch.action.explain.ExplainResponse;
|
||||
import org.elasticsearch.action.fieldcaps.FieldCapabilities;
|
||||
import org.elasticsearch.action.fieldcaps.FieldCapabilitiesRequest;
|
||||
import org.elasticsearch.action.fieldcaps.FieldCapabilitiesResponse;
|
||||
|
@ -44,6 +46,7 @@ import org.elasticsearch.common.xcontent.XContentBuilder;
|
|||
import org.elasticsearch.common.xcontent.XContentFactory;
|
||||
import org.elasticsearch.common.xcontent.XContentType;
|
||||
import org.elasticsearch.index.query.MatchQueryBuilder;
|
||||
import org.elasticsearch.index.query.QueryBuilders;
|
||||
import org.elasticsearch.index.query.ScriptQueryBuilder;
|
||||
import org.elasticsearch.index.query.TermsQueryBuilder;
|
||||
import org.elasticsearch.join.aggregations.Children;
|
||||
|
@ -63,6 +66,7 @@ import org.elasticsearch.search.aggregations.matrix.stats.MatrixStats;
|
|||
import org.elasticsearch.search.aggregations.matrix.stats.MatrixStatsAggregationBuilder;
|
||||
import org.elasticsearch.search.aggregations.support.ValueType;
|
||||
import org.elasticsearch.search.builder.SearchSourceBuilder;
|
||||
import org.elasticsearch.search.fetch.subphase.FetchSourceContext;
|
||||
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
|
||||
import org.elasticsearch.search.sort.SortOrder;
|
||||
import org.elasticsearch.search.suggest.Suggest;
|
||||
|
@ -135,7 +139,44 @@ public class SearchIT extends ESRestHighLevelClientTestCase {
|
|||
client().performRequest(HttpPut.METHOD_NAME, "/index3/doc/5", Collections.emptyMap(), doc);
|
||||
doc = new StringEntity("{\"field\":\"value2\"}", ContentType.APPLICATION_JSON);
|
||||
client().performRequest(HttpPut.METHOD_NAME, "/index3/doc/6", Collections.emptyMap(), doc);
|
||||
client().performRequest(HttpPost.METHOD_NAME, "/index1,index2,index3/_refresh");
|
||||
|
||||
mappings = new StringEntity(
|
||||
"{" +
|
||||
" \"mappings\": {" +
|
||||
" \"doc\": {" +
|
||||
" \"properties\": {" +
|
||||
" \"field1\": {" +
|
||||
" \"type\": \"keyword\"," +
|
||||
" \"store\": true" +
|
||||
" }," +
|
||||
" \"field2\": {" +
|
||||
" \"type\": \"keyword\"," +
|
||||
" \"store\": true" +
|
||||
" }" +
|
||||
" }" +
|
||||
" }" +
|
||||
" }" +
|
||||
"}}",
|
||||
ContentType.APPLICATION_JSON);
|
||||
client().performRequest(HttpPut.METHOD_NAME, "/index4", Collections.emptyMap(), mappings);
|
||||
doc = new StringEntity("{\"field1\":\"value1\", \"field2\":\"value2\"}", ContentType.APPLICATION_JSON);
|
||||
client().performRequest(HttpPut.METHOD_NAME, "/index4/doc/1", Collections.emptyMap(), doc);
|
||||
StringEntity aliasFilter = new StringEntity(
|
||||
"{" +
|
||||
" \"actions\" : [" +
|
||||
" {" +
|
||||
" \"add\" : {" +
|
||||
" \"index\" : \"index4\"," +
|
||||
" \"alias\" : \"alias4\"," +
|
||||
" \"filter\" : { \"term\" : { \"field2\" : \"value1\" } }" +
|
||||
" }" +
|
||||
" }" +
|
||||
" ]" +
|
||||
"}",
|
||||
ContentType.APPLICATION_JSON);
|
||||
client().performRequest(HttpPost.METHOD_NAME, "/_aliases", Collections.emptyMap(), aliasFilter);
|
||||
|
||||
client().performRequest(HttpPost.METHOD_NAME, "/index1,index2,index3,index4/_refresh");
|
||||
}
|
||||
|
||||
public void testSearchNoQuery() throws IOException {
|
||||
|
@ -835,6 +876,174 @@ public class SearchIT extends ESRestHighLevelClientTestCase {
|
|||
assertToXContentEquivalent(expectedSource, actualSource, XContentType.JSON);
|
||||
}
|
||||
|
||||
public void testExplain() throws IOException {
|
||||
{
|
||||
ExplainRequest explainRequest = new ExplainRequest("index1", "doc", "1");
|
||||
explainRequest.query(QueryBuilders.matchAllQuery());
|
||||
|
||||
ExplainResponse explainResponse = execute(explainRequest, highLevelClient()::explain, highLevelClient()::explainAsync);
|
||||
|
||||
assertThat(explainResponse.getIndex(), equalTo("index1"));
|
||||
assertThat(explainResponse.getType(), equalTo("doc"));
|
||||
assertThat(Integer.valueOf(explainResponse.getId()), equalTo(1));
|
||||
assertTrue(explainResponse.isExists());
|
||||
assertTrue(explainResponse.isMatch());
|
||||
assertTrue(explainResponse.hasExplanation());
|
||||
assertThat(explainResponse.getExplanation().getValue(), equalTo(1.0f));
|
||||
assertNull(explainResponse.getGetResult());
|
||||
}
|
||||
{
|
||||
ExplainRequest explainRequest = new ExplainRequest("index1", "doc", "1");
|
||||
explainRequest.query(QueryBuilders.termQuery("field", "value1"));
|
||||
|
||||
ExplainResponse explainResponse = execute(explainRequest, highLevelClient()::explain, highLevelClient()::explainAsync);
|
||||
|
||||
assertThat(explainResponse.getIndex(), equalTo("index1"));
|
||||
assertThat(explainResponse.getType(), equalTo("doc"));
|
||||
assertThat(Integer.valueOf(explainResponse.getId()), equalTo(1));
|
||||
assertTrue(explainResponse.isExists());
|
||||
assertTrue(explainResponse.isMatch());
|
||||
assertTrue(explainResponse.hasExplanation());
|
||||
assertThat(explainResponse.getExplanation().getValue(), greaterThan(0.0f));
|
||||
assertNull(explainResponse.getGetResult());
|
||||
}
|
||||
{
|
||||
ExplainRequest explainRequest = new ExplainRequest("index1", "doc", "1");
|
||||
explainRequest.query(QueryBuilders.termQuery("field", "value2"));
|
||||
|
||||
ExplainResponse explainResponse = execute(explainRequest, highLevelClient()::explain, highLevelClient()::explainAsync);
|
||||
|
||||
assertThat(explainResponse.getIndex(), equalTo("index1"));
|
||||
assertThat(explainResponse.getType(), equalTo("doc"));
|
||||
assertThat(Integer.valueOf(explainResponse.getId()), equalTo(1));
|
||||
assertTrue(explainResponse.isExists());
|
||||
assertFalse(explainResponse.isMatch());
|
||||
assertTrue(explainResponse.hasExplanation());
|
||||
assertNull(explainResponse.getGetResult());
|
||||
}
|
||||
{
|
||||
ExplainRequest explainRequest = new ExplainRequest("index1", "doc", "1");
|
||||
explainRequest.query(QueryBuilders.boolQuery()
|
||||
.must(QueryBuilders.termQuery("field", "value1"))
|
||||
.must(QueryBuilders.termQuery("field", "value2")));
|
||||
|
||||
ExplainResponse explainResponse = execute(explainRequest, highLevelClient()::explain, highLevelClient()::explainAsync);
|
||||
|
||||
assertThat(explainResponse.getIndex(), equalTo("index1"));
|
||||
assertThat(explainResponse.getType(), equalTo("doc"));
|
||||
assertThat(Integer.valueOf(explainResponse.getId()), equalTo(1));
|
||||
assertTrue(explainResponse.isExists());
|
||||
assertFalse(explainResponse.isMatch());
|
||||
assertTrue(explainResponse.hasExplanation());
|
||||
assertThat(explainResponse.getExplanation().getDetails().length, equalTo(2));
|
||||
assertNull(explainResponse.getGetResult());
|
||||
}
|
||||
}
|
||||
|
||||
public void testExplainNonExistent() throws IOException {
|
||||
{
|
||||
ExplainRequest explainRequest = new ExplainRequest("non_existent_index", "doc", "1");
|
||||
explainRequest.query(QueryBuilders.matchQuery("field", "value"));
|
||||
ElasticsearchException exception = expectThrows(ElasticsearchException.class,
|
||||
() -> execute(explainRequest, highLevelClient()::explain, highLevelClient()::explainAsync));
|
||||
assertThat(exception.status(), equalTo(RestStatus.NOT_FOUND));
|
||||
assertThat(exception.getIndex().getName(), equalTo("non_existent_index"));
|
||||
assertThat(exception.getDetailedMessage(),
|
||||
containsString("Elasticsearch exception [type=index_not_found_exception, reason=no such index]"));
|
||||
}
|
||||
{
|
||||
ExplainRequest explainRequest = new ExplainRequest("index1", "doc", "999");
|
||||
explainRequest.query(QueryBuilders.matchQuery("field", "value1"));
|
||||
|
||||
ExplainResponse explainResponse = execute(explainRequest, highLevelClient()::explain, highLevelClient()::explainAsync);
|
||||
|
||||
assertThat(explainResponse.getIndex(), equalTo("index1"));
|
||||
assertThat(explainResponse.getType(), equalTo("doc"));
|
||||
assertThat(explainResponse.getId(), equalTo("999"));
|
||||
assertFalse(explainResponse.isExists());
|
||||
assertFalse(explainResponse.isMatch());
|
||||
assertFalse(explainResponse.hasExplanation());
|
||||
assertNull(explainResponse.getGetResult());
|
||||
}
|
||||
}
|
||||
|
||||
public void testExplainWithStoredFields() throws IOException {
|
||||
{
|
||||
ExplainRequest explainRequest = new ExplainRequest("index4", "doc", "1");
|
||||
explainRequest.query(QueryBuilders.matchAllQuery());
|
||||
explainRequest.storedFields(new String[]{"field1"});
|
||||
|
||||
ExplainResponse explainResponse = execute(explainRequest, highLevelClient()::explain, highLevelClient()::explainAsync);
|
||||
|
||||
assertTrue(explainResponse.isExists());
|
||||
assertTrue(explainResponse.isMatch());
|
||||
assertTrue(explainResponse.hasExplanation());
|
||||
assertThat(explainResponse.getExplanation().getValue(), equalTo(1.0f));
|
||||
assertTrue(explainResponse.getGetResult().isExists());
|
||||
assertThat(explainResponse.getGetResult().getFields().keySet(), equalTo(Collections.singleton("field1")));
|
||||
assertThat(explainResponse.getGetResult().getFields().get("field1").getValue().toString(), equalTo("value1"));
|
||||
assertTrue(explainResponse.getGetResult().isSourceEmpty());
|
||||
}
|
||||
{
|
||||
ExplainRequest explainRequest = new ExplainRequest("index4", "doc", "1");
|
||||
explainRequest.query(QueryBuilders.matchAllQuery());
|
||||
explainRequest.storedFields(new String[]{"field1", "field2"});
|
||||
|
||||
ExplainResponse explainResponse = execute(explainRequest, highLevelClient()::explain, highLevelClient()::explainAsync);
|
||||
|
||||
assertTrue(explainResponse.isExists());
|
||||
assertTrue(explainResponse.isMatch());
|
||||
assertTrue(explainResponse.hasExplanation());
|
||||
assertThat(explainResponse.getExplanation().getValue(), equalTo(1.0f));
|
||||
assertTrue(explainResponse.getGetResult().isExists());
|
||||
assertThat(explainResponse.getGetResult().getFields().keySet().size(), equalTo(2));
|
||||
assertThat(explainResponse.getGetResult().getFields().get("field1").getValue().toString(), equalTo("value1"));
|
||||
assertThat(explainResponse.getGetResult().getFields().get("field2").getValue().toString(), equalTo("value2"));
|
||||
assertTrue(explainResponse.getGetResult().isSourceEmpty());
|
||||
}
|
||||
}
|
||||
|
||||
public void testExplainWithFetchSource() throws IOException {
|
||||
{
|
||||
ExplainRequest explainRequest = new ExplainRequest("index4", "doc", "1");
|
||||
explainRequest.query(QueryBuilders.matchAllQuery());
|
||||
explainRequest.fetchSourceContext(new FetchSourceContext(true, new String[]{"field1"}, null));
|
||||
|
||||
ExplainResponse explainResponse = execute(explainRequest, highLevelClient()::explain, highLevelClient()::explainAsync);
|
||||
|
||||
assertTrue(explainResponse.isExists());
|
||||
assertTrue(explainResponse.isMatch());
|
||||
assertTrue(explainResponse.hasExplanation());
|
||||
assertThat(explainResponse.getExplanation().getValue(), equalTo(1.0f));
|
||||
assertTrue(explainResponse.getGetResult().isExists());
|
||||
assertThat(explainResponse.getGetResult().getSource(), equalTo(Collections.singletonMap("field1", "value1")));
|
||||
}
|
||||
{
|
||||
ExplainRequest explainRequest = new ExplainRequest("index4", "doc", "1");
|
||||
explainRequest.query(QueryBuilders.matchAllQuery());
|
||||
explainRequest.fetchSourceContext(new FetchSourceContext(true, null, new String[] {"field2"}));
|
||||
|
||||
ExplainResponse explainResponse = execute(explainRequest, highLevelClient()::explain, highLevelClient()::explainAsync);
|
||||
|
||||
assertTrue(explainResponse.isExists());
|
||||
assertTrue(explainResponse.isMatch());
|
||||
assertTrue(explainResponse.hasExplanation());
|
||||
assertThat(explainResponse.getExplanation().getValue(), equalTo(1.0f));
|
||||
assertTrue(explainResponse.getGetResult().isExists());
|
||||
assertThat(explainResponse.getGetResult().getSource(), equalTo(Collections.singletonMap("field1", "value1")));
|
||||
}
|
||||
}
|
||||
|
||||
public void testExplainWithAliasFilter() throws IOException {
|
||||
ExplainRequest explainRequest = new ExplainRequest("alias4", "doc", "1");
|
||||
explainRequest.query(QueryBuilders.matchAllQuery());
|
||||
|
||||
ExplainResponse explainResponse = execute(explainRequest, highLevelClient()::explain, highLevelClient()::explainAsync);
|
||||
|
||||
assertTrue(explainResponse.isExists());
|
||||
assertFalse(explainResponse.isMatch());
|
||||
}
|
||||
|
||||
public void testFieldCaps() throws IOException {
|
||||
FieldCapabilitiesRequest request = new FieldCapabilitiesRequest()
|
||||
.indices("index1", "index2")
|
||||
|
|
|
@ -19,12 +19,15 @@
|
|||
|
||||
package org.elasticsearch.client.documentation;
|
||||
|
||||
import org.apache.lucene.search.Explanation;
|
||||
import org.elasticsearch.action.ActionListener;
|
||||
import org.elasticsearch.action.LatchedActionListener;
|
||||
import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
|
||||
import org.elasticsearch.action.admin.indices.create.CreateIndexResponse;
|
||||
import org.elasticsearch.action.bulk.BulkRequest;
|
||||
import org.elasticsearch.action.bulk.BulkResponse;
|
||||
import org.elasticsearch.action.explain.ExplainRequest;
|
||||
import org.elasticsearch.action.explain.ExplainResponse;
|
||||
import org.elasticsearch.action.fieldcaps.FieldCapabilities;
|
||||
import org.elasticsearch.action.fieldcaps.FieldCapabilitiesRequest;
|
||||
import org.elasticsearch.action.fieldcaps.FieldCapabilitiesResponse;
|
||||
|
@ -47,10 +50,12 @@ import org.elasticsearch.client.Response;
|
|||
import org.elasticsearch.client.RestClient;
|
||||
import org.elasticsearch.client.RestHighLevelClient;
|
||||
import org.elasticsearch.common.bytes.BytesReference;
|
||||
import org.elasticsearch.common.document.DocumentField;
|
||||
import org.elasticsearch.common.text.Text;
|
||||
import org.elasticsearch.common.unit.Fuzziness;
|
||||
import org.elasticsearch.common.unit.TimeValue;
|
||||
import org.elasticsearch.common.xcontent.XContentType;
|
||||
import org.elasticsearch.index.get.GetResult;
|
||||
import org.elasticsearch.index.query.MatchQueryBuilder;
|
||||
import org.elasticsearch.index.query.QueryBuilder;
|
||||
import org.elasticsearch.index.query.QueryBuilders;
|
||||
|
@ -80,6 +85,7 @@ import org.elasticsearch.search.aggregations.bucket.terms.Terms.Bucket;
|
|||
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
|
||||
import org.elasticsearch.search.aggregations.metrics.avg.Avg;
|
||||
import org.elasticsearch.search.builder.SearchSourceBuilder;
|
||||
import org.elasticsearch.search.fetch.subphase.FetchSourceContext;
|
||||
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
|
||||
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
|
||||
import org.elasticsearch.search.profile.ProfileResult;
|
||||
|
@ -835,6 +841,85 @@ public class SearchDocumentationIT extends ESRestHighLevelClientTestCase {
|
|||
assertTrue(latch.await(30L, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
public void testExplain() throws Exception {
|
||||
indexSearchTestData();
|
||||
RestHighLevelClient client = highLevelClient();
|
||||
|
||||
// tag::explain-request
|
||||
ExplainRequest request = new ExplainRequest("contributors", "doc", "1");
|
||||
request.query(QueryBuilders.termQuery("user", "tanguy"));
|
||||
// end::explain-request
|
||||
|
||||
// tag::explain-request-routing
|
||||
request.routing("routing"); // <1>
|
||||
// end::explain-request-routing
|
||||
|
||||
// tag::explain-request-preference
|
||||
request.preference("_local"); // <1>
|
||||
// end::explain-request-preference
|
||||
|
||||
// tag::explain-request-source
|
||||
request.fetchSourceContext(new FetchSourceContext(true, new String[]{"user"}, null)); // <1>
|
||||
// end::explain-request-source
|
||||
|
||||
// tag::explain-request-stored-field
|
||||
request.storedFields(new String[]{"user"}); // <1>
|
||||
// end::explain-request-stored-field
|
||||
|
||||
// tag::explain-execute
|
||||
ExplainResponse response = client.explain(request, RequestOptions.DEFAULT);
|
||||
// end::explain-execute
|
||||
|
||||
// tag::explain-response
|
||||
String index = response.getIndex(); // <1>
|
||||
String type = response.getType(); // <2>
|
||||
String id = response.getId(); // <3>
|
||||
boolean exists = response.isExists(); // <4>
|
||||
boolean match = response.isMatch(); // <5>
|
||||
boolean hasExplanation = response.hasExplanation(); // <6>
|
||||
Explanation explanation = response.getExplanation(); // <7>
|
||||
GetResult getResult = response.getGetResult(); // <8>
|
||||
// end::explain-response
|
||||
assertThat(index, equalTo("contributors"));
|
||||
assertThat(type, equalTo("doc"));
|
||||
assertThat(id, equalTo("1"));
|
||||
assertTrue(exists);
|
||||
assertTrue(match);
|
||||
assertTrue(hasExplanation);
|
||||
assertNotNull(explanation);
|
||||
assertNotNull(getResult);
|
||||
|
||||
// tag::get-result
|
||||
Map<String, Object> source = getResult.getSource(); // <1>
|
||||
Map<String, DocumentField> fields = getResult.getFields(); // <2>
|
||||
// end::get-result
|
||||
assertThat(source, equalTo(Collections.singletonMap("user", "tanguy")));
|
||||
assertThat(fields.get("user").getValue(), equalTo("tanguy"));
|
||||
|
||||
// tag::explain-execute-listener
|
||||
ActionListener<ExplainResponse> listener = new ActionListener<ExplainResponse>() {
|
||||
@Override
|
||||
public void onResponse(ExplainResponse explainResponse) {
|
||||
// <1>
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Exception e) {
|
||||
// <2>
|
||||
}
|
||||
};
|
||||
// end::explain-execute-listener
|
||||
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
listener = new LatchedActionListener<>(listener, latch);
|
||||
|
||||
// tag::explain-execute-async
|
||||
client.explainAsync(request, RequestOptions.DEFAULT, listener); // <1>
|
||||
// end::explain-execute-async
|
||||
|
||||
assertTrue(latch.await(30L, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
public void testFieldCaps() throws Exception {
|
||||
indexSearchTestData();
|
||||
RestHighLevelClient client = highLevelClient();
|
||||
|
@ -1046,7 +1131,7 @@ public class SearchDocumentationIT extends ESRestHighLevelClientTestCase {
|
|||
assertTrue(authorsResponse.isAcknowledged());
|
||||
|
||||
CreateIndexRequest reviewersRequest = new CreateIndexRequest("contributors")
|
||||
.mapping("doc", "user", "type=keyword");
|
||||
.mapping("doc", "user", "type=keyword,store=true");
|
||||
CreateIndexResponse reviewersResponse = highLevelClient().indices().create(reviewersRequest, RequestOptions.DEFAULT);
|
||||
assertTrue(reviewersResponse.isAcknowledged());
|
||||
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
[[java-rest-high-explain]]
|
||||
=== Explain API
|
||||
|
||||
The explain api computes a score explanation for a query and a specific document.
|
||||
This can give useful feedback whether a document matches or didn’t match a specific query.
|
||||
|
||||
[[java-rest-high-explain-request]]
|
||||
==== Explain Request
|
||||
|
||||
An `ExplainRequest` expects an `index`, a `type` and an `id` to specify a certain document,
|
||||
and a query represented by `QueryBuilder` to run against it (the way of <<java-rest-high-query-builders, building queries>>).
|
||||
|
||||
["source","java",subs="attributes,callouts,macros"]
|
||||
--------------------------------------------------
|
||||
include-tagged::{doc-tests}/SearchDocumentationIT.java[explain-request]
|
||||
--------------------------------------------------
|
||||
|
||||
===== Optional arguments
|
||||
|
||||
["source","java",subs="attributes,callouts,macros"]
|
||||
--------------------------------------------------
|
||||
include-tagged::{doc-tests}/SearchDocumentationIT.java[explain-request-routing]
|
||||
--------------------------------------------------
|
||||
<1> Set a routing parameter
|
||||
|
||||
["source","java",subs="attributes,callouts,macros"]
|
||||
--------------------------------------------------
|
||||
include-tagged::{doc-tests}/SearchDocumentationIT.java[explain-request-preference]
|
||||
--------------------------------------------------
|
||||
<1> Use the preference parameter e.g. to execute the search to prefer local
|
||||
shards. The default is to randomize across shards.
|
||||
|
||||
["source","java",subs="attributes,callouts,macros"]
|
||||
--------------------------------------------------
|
||||
include-tagged::{doc-tests}/SearchDocumentationIT.java[explain-request-source]
|
||||
--------------------------------------------------
|
||||
<1> Set to true to retrieve the _source of the document explained. You can also
|
||||
retrieve part of the document by using _source_include & _source_exclude
|
||||
(see <<java-rest-high-document-get-request-optional-arguments, Get API>> for more details)
|
||||
|
||||
["source","java",subs="attributes,callouts,macros"]
|
||||
--------------------------------------------------
|
||||
include-tagged::{doc-tests}/SearchDocumentationIT.java[explain-request-stored-field]
|
||||
--------------------------------------------------
|
||||
<1> Allows to control which stored fields to return as part of the document explained
|
||||
(requires the field to be stored separately in the mappings).
|
||||
|
||||
[[java-rest-high-explain-sync]]
|
||||
==== Synchronous Execution
|
||||
|
||||
The `explain` method executes the request synchronously:
|
||||
|
||||
["source","java",subs="attributes,callouts,macros"]
|
||||
--------------------------------------------------
|
||||
include-tagged::{doc-tests}/SearchDocumentationIT.java[explain-execute]
|
||||
--------------------------------------------------
|
||||
|
||||
[[java-rest-high-explain-async]]
|
||||
==== Asynchronous Execution
|
||||
|
||||
The `explainAsync` method executes the request asynchronously,
|
||||
calling the provided `ActionListener` when the response is ready:
|
||||
|
||||
["source","java",subs="attributes,callouts,macros"]
|
||||
--------------------------------------------------
|
||||
include-tagged::{doc-tests}/SearchDocumentationIT.java[explain-execute-async]
|
||||
--------------------------------------------------
|
||||
<1> The `ExplainRequest` to execute and the `ActionListener` to use when
|
||||
the execution completes.
|
||||
|
||||
The asynchronous method does not block and returns immediately. Once the request
|
||||
completes, the `ActionListener` is called back using the `onResponse` method
|
||||
if the execution successfully completed or using the `onFailure` method if
|
||||
it failed.
|
||||
|
||||
A typical listener for `ExplainResponse` is constructed as follows:
|
||||
|
||||
["source","java",subs="attributes,callouts,macros"]
|
||||
--------------------------------------------------
|
||||
include-tagged::{doc-tests}/SearchDocumentationIT.java[explain-execute-listener]
|
||||
--------------------------------------------------
|
||||
<1> Called when the execution is successfully completed.
|
||||
<2> Called when the whole `FieldCapabilitiesRequest` fails.
|
||||
|
||||
[[java-rest-high-explain-response]]
|
||||
==== ExplainResponse
|
||||
|
||||
The `ExplainResponse` contains the following information:
|
||||
|
||||
["source","java",subs="attributes,callouts,macros"]
|
||||
--------------------------------------------------
|
||||
include-tagged::{doc-tests}/SearchDocumentationIT.java[explain-response]
|
||||
--------------------------------------------------
|
||||
<1> The index name of the explained document.
|
||||
<2> The type name of the explained document.
|
||||
<3> The id of the explained document.
|
||||
<4> Indicates whether or not the explained document exists.
|
||||
<5> Indicates whether or not there is a match between the explained document and
|
||||
the provided query (the `match` is retrieved from the lucene `Explanation` behind the scenes
|
||||
if the lucene `Explanation` models a match, it returns `true`, otherwise it returns `false`).
|
||||
<6> Indicates whether or not there exists a lucene `Explanation` for this request.
|
||||
<7> Get the lucene `Explanation` object if there exists.
|
||||
<8> Get the `GetResult` object if the `_source` or the stored fields are retrieved.
|
||||
|
||||
The `GetResult` contains two maps internally to store the fetched `_source` and stored fields.
|
||||
You can use the following methods to get them:
|
||||
|
||||
["source","java",subs="attributes,callouts,macros"]
|
||||
--------------------------------------------------
|
||||
include-tagged::{doc-tests}/SearchDocumentationIT.java[get-result]
|
||||
--------------------------------------------------
|
||||
<1> Retrieve the `_source` as a map.
|
||||
<2> Retrieve the specified stored fields as a map.
|
|
@ -35,6 +35,7 @@ The Java High Level REST Client supports the following Search APIs:
|
|||
* <<java-rest-high-multi-search>>
|
||||
* <<java-rest-high-field-caps>>
|
||||
* <<java-rest-high-rank-eval>>
|
||||
* <<java-rest-high-explain>>
|
||||
|
||||
include::search/search.asciidoc[]
|
||||
include::search/scroll.asciidoc[]
|
||||
|
@ -42,6 +43,7 @@ include::search/multi-search.asciidoc[]
|
|||
include::search/search-template.asciidoc[]
|
||||
include::search/field-caps.asciidoc[]
|
||||
include::search/rank-eval.asciidoc[]
|
||||
include::search/explain.asciidoc[]
|
||||
|
||||
== Miscellaneous APIs
|
||||
|
||||
|
|
|
@ -22,9 +22,12 @@ package org.elasticsearch.action.explain;
|
|||
import org.elasticsearch.action.ActionRequestValidationException;
|
||||
import org.elasticsearch.action.ValidateActions;
|
||||
import org.elasticsearch.action.support.single.shard.SingleShardRequest;
|
||||
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.xcontent.ToXContentObject;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.index.query.QueryBuilder;
|
||||
import org.elasticsearch.search.fetch.subphase.FetchSourceContext;
|
||||
import org.elasticsearch.search.internal.AliasFilter;
|
||||
|
@ -34,7 +37,9 @@ import java.io.IOException;
|
|||
/**
|
||||
* Explain request encapsulating the explain query and document identifier to get an explanation for.
|
||||
*/
|
||||
public class ExplainRequest extends SingleShardRequest<ExplainRequest> {
|
||||
public class ExplainRequest extends SingleShardRequest<ExplainRequest> implements ToXContentObject {
|
||||
|
||||
private static final ParseField QUERY_FIELD = new ParseField("query");
|
||||
|
||||
private String type = "_all";
|
||||
private String id;
|
||||
|
@ -186,4 +191,12 @@ public class ExplainRequest extends SingleShardRequest<ExplainRequest> {
|
|||
out.writeOptionalWriteable(fetchSourceContext);
|
||||
out.writeVLong(nowInMillis);
|
||||
}
|
||||
|
||||
@Override
|
||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||
builder.startObject();
|
||||
builder.field(QUERY_FIELD.getPreferredName(), query);
|
||||
builder.endObject();
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,11 +21,19 @@ package org.elasticsearch.action.explain;
|
|||
|
||||
import org.apache.lucene.search.Explanation;
|
||||
import org.elasticsearch.action.ActionResponse;
|
||||
import org.elasticsearch.common.ParseField;
|
||||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
|
||||
import org.elasticsearch.common.xcontent.StatusToXContentObject;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.index.get.GetResult;
|
||||
import org.elasticsearch.rest.RestStatus;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.Objects;
|
||||
|
||||
import static org.elasticsearch.common.lucene.Lucene.readExplanation;
|
||||
import static org.elasticsearch.common.lucene.Lucene.writeExplanation;
|
||||
|
@ -33,7 +41,17 @@ import static org.elasticsearch.common.lucene.Lucene.writeExplanation;
|
|||
/**
|
||||
* Response containing the score explanation.
|
||||
*/
|
||||
public class ExplainResponse extends ActionResponse {
|
||||
public class ExplainResponse extends ActionResponse implements StatusToXContentObject {
|
||||
|
||||
private static final ParseField _INDEX = new ParseField("_index");
|
||||
private static final ParseField _TYPE = new ParseField("_type");
|
||||
private static final ParseField _ID = new ParseField("_id");
|
||||
private static final ParseField MATCHED = new ParseField("matched");
|
||||
private static final ParseField EXPLANATION = new ParseField("explanation");
|
||||
private static final ParseField VALUE = new ParseField("value");
|
||||
private static final ParseField DESCRIPTION = new ParseField("description");
|
||||
private static final ParseField DETAILS = new ParseField("details");
|
||||
private static final ParseField GET = new ParseField("get");
|
||||
|
||||
private String index;
|
||||
private String type;
|
||||
|
@ -94,6 +112,11 @@ public class ExplainResponse extends ActionResponse {
|
|||
return getResult;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RestStatus status() {
|
||||
return exists ? RestStatus.OK : RestStatus.NOT_FOUND;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readFrom(StreamInput in) throws IOException {
|
||||
super.readFrom(in);
|
||||
|
@ -129,4 +152,90 @@ public class ExplainResponse extends ActionResponse {
|
|||
getResult.writeTo(out);
|
||||
}
|
||||
}
|
||||
|
||||
private static final ConstructingObjectParser<ExplainResponse, Boolean> PARSER = new ConstructingObjectParser<>("explain", true,
|
||||
(arg, exists) -> new ExplainResponse((String) arg[0], (String) arg[1], (String) arg[2], exists, (Explanation) arg[3],
|
||||
(GetResult) arg[4]));
|
||||
|
||||
static {
|
||||
PARSER.declareString(ConstructingObjectParser.constructorArg(), _INDEX);
|
||||
PARSER.declareString(ConstructingObjectParser.constructorArg(), _TYPE);
|
||||
PARSER.declareString(ConstructingObjectParser.constructorArg(), _ID);
|
||||
final ConstructingObjectParser<Explanation, Boolean> explanationParser = new ConstructingObjectParser<>("explanation", true,
|
||||
arg -> {
|
||||
if ((float) arg[0] > 0) {
|
||||
return Explanation.match((float) arg[0], (String) arg[1], (Collection<Explanation>) arg[2]);
|
||||
} else {
|
||||
return Explanation.noMatch((String) arg[1], (Collection<Explanation>) arg[2]);
|
||||
}
|
||||
});
|
||||
explanationParser.declareFloat(ConstructingObjectParser.constructorArg(), VALUE);
|
||||
explanationParser.declareString(ConstructingObjectParser.constructorArg(), DESCRIPTION);
|
||||
explanationParser.declareObjectArray(ConstructingObjectParser.constructorArg(), explanationParser, DETAILS);
|
||||
PARSER.declareObject(ConstructingObjectParser.optionalConstructorArg(), explanationParser, EXPLANATION);
|
||||
PARSER.declareObject(ConstructingObjectParser.optionalConstructorArg(), (p, c) -> GetResult.fromXContentEmbedded(p), GET);
|
||||
}
|
||||
|
||||
public static ExplainResponse fromXContent(XContentParser parser, boolean exists) {
|
||||
return PARSER.apply(parser, exists);
|
||||
}
|
||||
|
||||
@Override
|
||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||
builder.startObject();
|
||||
builder.field(_INDEX.getPreferredName(), index);
|
||||
builder.field(_TYPE.getPreferredName(), type);
|
||||
builder.field(_ID.getPreferredName(), id);
|
||||
builder.field(MATCHED.getPreferredName(), isMatch());
|
||||
if (hasExplanation()) {
|
||||
builder.startObject(EXPLANATION.getPreferredName());
|
||||
buildExplanation(builder, explanation);
|
||||
builder.endObject();
|
||||
}
|
||||
if (getResult != null) {
|
||||
builder.startObject(GET.getPreferredName());
|
||||
getResult.toXContentEmbedded(builder, params);
|
||||
builder.endObject();
|
||||
}
|
||||
builder.endObject();
|
||||
return builder;
|
||||
}
|
||||
|
||||
private void buildExplanation(XContentBuilder builder, Explanation explanation) throws IOException {
|
||||
builder.field(VALUE.getPreferredName(), explanation.getValue());
|
||||
builder.field(DESCRIPTION.getPreferredName(), explanation.getDescription());
|
||||
Explanation[] innerExps = explanation.getDetails();
|
||||
if (innerExps != null) {
|
||||
builder.startArray(DETAILS.getPreferredName());
|
||||
for (Explanation exp : innerExps) {
|
||||
builder.startObject();
|
||||
buildExplanation(builder, exp);
|
||||
builder.endObject();
|
||||
}
|
||||
builder.endArray();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null || getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
ExplainResponse other = (ExplainResponse) obj;
|
||||
return index.equals(other.index)
|
||||
&& type.equals(other.type)
|
||||
&& id.equals(other.id)
|
||||
&& Objects.equals(explanation, other.explanation)
|
||||
&& getResult.isExists() == other.getResult.isExists()
|
||||
&& Objects.equals(getResult.sourceAsMap(), other.getResult.sourceAsMap())
|
||||
&& Objects.equals(getResult.getFields(), other.getResult.getFields());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(index, type, id, explanation, getResult.isExists(), getResult.sourceAsMap(), getResult.getFields());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,30 +19,22 @@
|
|||
|
||||
package org.elasticsearch.rest.action.search;
|
||||
|
||||
import org.apache.lucene.search.Explanation;
|
||||
import org.elasticsearch.action.explain.ExplainRequest;
|
||||
import org.elasticsearch.action.explain.ExplainResponse;
|
||||
import org.elasticsearch.client.node.NodeClient;
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.index.get.GetResult;
|
||||
import org.elasticsearch.index.query.QueryBuilder;
|
||||
import org.elasticsearch.rest.BaseRestHandler;
|
||||
import org.elasticsearch.rest.BytesRestResponse;
|
||||
import org.elasticsearch.rest.RestController;
|
||||
import org.elasticsearch.rest.RestRequest;
|
||||
import org.elasticsearch.rest.RestResponse;
|
||||
import org.elasticsearch.rest.action.RestActions;
|
||||
import org.elasticsearch.rest.action.RestBuilderListener;
|
||||
import org.elasticsearch.rest.action.RestStatusToXContentListener;
|
||||
import org.elasticsearch.search.fetch.subphase.FetchSourceContext;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.elasticsearch.rest.RestRequest.Method.GET;
|
||||
import static org.elasticsearch.rest.RestRequest.Method.POST;
|
||||
import static org.elasticsearch.rest.RestStatus.NOT_FOUND;
|
||||
import static org.elasticsearch.rest.RestStatus.OK;
|
||||
|
||||
/**
|
||||
* Rest action for computing a score explanation for specific documents.
|
||||
|
@ -89,57 +81,6 @@ public class RestExplainAction extends BaseRestHandler {
|
|||
|
||||
explainRequest.fetchSourceContext(FetchSourceContext.parseFromRestRequest(request));
|
||||
|
||||
return channel -> client.explain(explainRequest, new RestBuilderListener<ExplainResponse>(channel) {
|
||||
@Override
|
||||
public RestResponse buildResponse(ExplainResponse response, XContentBuilder builder) throws Exception {
|
||||
builder.startObject();
|
||||
builder.field(Fields._INDEX, response.getIndex())
|
||||
.field(Fields._TYPE, response.getType())
|
||||
.field(Fields._ID, response.getId())
|
||||
.field(Fields.MATCHED, response.isMatch());
|
||||
|
||||
if (response.hasExplanation()) {
|
||||
builder.startObject(Fields.EXPLANATION);
|
||||
buildExplanation(builder, response.getExplanation());
|
||||
builder.endObject();
|
||||
}
|
||||
GetResult getResult = response.getGetResult();
|
||||
if (getResult != null) {
|
||||
builder.startObject(Fields.GET);
|
||||
response.getGetResult().toXContentEmbedded(builder, request);
|
||||
builder.endObject();
|
||||
}
|
||||
builder.endObject();
|
||||
return new BytesRestResponse(response.isExists() ? OK : NOT_FOUND, builder);
|
||||
}
|
||||
|
||||
private void buildExplanation(XContentBuilder builder, Explanation explanation) throws IOException {
|
||||
builder.field(Fields.VALUE, explanation.getValue());
|
||||
builder.field(Fields.DESCRIPTION, explanation.getDescription());
|
||||
Explanation[] innerExps = explanation.getDetails();
|
||||
if (innerExps != null) {
|
||||
builder.startArray(Fields.DETAILS);
|
||||
for (Explanation exp : innerExps) {
|
||||
builder.startObject();
|
||||
buildExplanation(builder, exp);
|
||||
builder.endObject();
|
||||
}
|
||||
builder.endArray();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static class Fields {
|
||||
static final String _INDEX = "_index";
|
||||
static final String _TYPE = "_type";
|
||||
static final String _ID = "_id";
|
||||
static final String MATCHED = "matched";
|
||||
static final String EXPLANATION = "explanation";
|
||||
static final String VALUE = "value";
|
||||
static final String DESCRIPTION = "description";
|
||||
static final String DETAILS = "details";
|
||||
static final String GET = "get";
|
||||
|
||||
return channel -> client.explain(explainRequest, new RestStatusToXContentListener<>(channel));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,9 +16,8 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.elasticsearch.action;
|
||||
package org.elasticsearch.action.explain;
|
||||
|
||||
import org.elasticsearch.action.explain.ExplainRequest;
|
||||
import org.elasticsearch.common.io.stream.BytesStreamOutput;
|
||||
import org.elasticsearch.common.io.stream.NamedWriteableAwareStreamInput;
|
||||
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
|
|
@ -0,0 +1,127 @@
|
|||
/*
|
||||
* 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.explain;
|
||||
|
||||
import org.apache.lucene.search.Explanation;
|
||||
import org.elasticsearch.common.bytes.BytesArray;
|
||||
import org.elasticsearch.common.bytes.BytesReference;
|
||||
import org.elasticsearch.common.document.DocumentField;
|
||||
import org.elasticsearch.common.xcontent.ToXContent;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentFactory;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.common.xcontent.XContentType;
|
||||
import org.elasticsearch.index.get.GetResult;
|
||||
import org.elasticsearch.test.AbstractStreamableXContentTestCase;
|
||||
import org.elasticsearch.test.RandomObjects;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import static java.util.Collections.singletonList;
|
||||
import static java.util.Collections.singletonMap;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
|
||||
public class ExplainResponseTests extends AbstractStreamableXContentTestCase<ExplainResponse> {
|
||||
@Override
|
||||
protected ExplainResponse doParseInstance(XContentParser parser) throws IOException {
|
||||
return ExplainResponse.fromXContent(parser, randomBoolean());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ExplainResponse createBlankInstance() {
|
||||
return new ExplainResponse();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ExplainResponse createTestInstance() {
|
||||
String index = randomAlphaOfLength(5);
|
||||
String type = randomAlphaOfLength(5);
|
||||
String id = String.valueOf(randomIntBetween(1,100));
|
||||
boolean exist = randomBoolean();
|
||||
Explanation explanation = randomExplanation(randomExplanation(randomExplanation()), randomExplanation());
|
||||
String fieldName = randomAlphaOfLength(10);
|
||||
List<Object> values = Arrays.asList(randomAlphaOfLengthBetween(3, 10), randomInt(), randomLong(), randomDouble(), randomBoolean());
|
||||
GetResult getResult = new GetResult(randomAlphaOfLengthBetween(3, 10),
|
||||
randomAlphaOfLengthBetween(3, 10),
|
||||
randomAlphaOfLengthBetween(3, 10),
|
||||
randomNonNegativeLong(),
|
||||
true,
|
||||
RandomObjects.randomSource(random()),
|
||||
singletonMap(fieldName, new DocumentField(fieldName, values)));
|
||||
return new ExplainResponse(index, type, id, exist, explanation, getResult);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Predicate<String> getRandomFieldsExcludeFilter() {
|
||||
return field -> field.equals("get") || field.startsWith("get.fields") || field.startsWith("get._source");
|
||||
}
|
||||
|
||||
public void testToXContent() throws IOException {
|
||||
String index = "index";
|
||||
String type = "type";
|
||||
String id = "1";
|
||||
boolean exist = true;
|
||||
Explanation explanation = Explanation.match(1.0f, "description", Collections.emptySet());
|
||||
GetResult getResult = new GetResult(null, null, null, -1, true, new BytesArray("{ \"field1\" : " +
|
||||
"\"value1\", \"field2\":\"value2\"}"), singletonMap("field1", new DocumentField("field1",
|
||||
singletonList("value1"))));
|
||||
ExplainResponse response = new ExplainResponse(index, type, id, exist, explanation, getResult);
|
||||
|
||||
XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON);
|
||||
response.toXContent(builder, ToXContent.EMPTY_PARAMS);
|
||||
|
||||
String generatedResponse = BytesReference.bytes(builder).utf8ToString().replaceAll("\\s+", "");
|
||||
|
||||
String expectedResponse =
|
||||
("{\n" +
|
||||
" \"_index\":\"index\",\n" +
|
||||
" \"_type\":\"type\",\n" +
|
||||
" \"_id\":\"1\",\n" +
|
||||
" \"matched\":true,\n" +
|
||||
" \"explanation\":{\n" +
|
||||
" \"value\":1.0,\n" +
|
||||
" \"description\":\"description\",\n" +
|
||||
" \"details\":[]\n" +
|
||||
" },\n" +
|
||||
" \"get\":{\n" +
|
||||
" \"found\":true,\n" +
|
||||
" \"_source\":{\n" +
|
||||
" \"field1\":\"value1\",\n" +
|
||||
" \"field2\":\"value2\"\n" +
|
||||
" },\n" +
|
||||
" \"fields\":{\n" +
|
||||
" \"field1\":[\n" +
|
||||
" \"value1\"\n" +
|
||||
" ]\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
"}").replaceAll("\\s+", "");
|
||||
assertThat(expectedResponse, equalTo(generatedResponse));
|
||||
}
|
||||
|
||||
private static Explanation randomExplanation(Explanation... explanations) {
|
||||
return Explanation.match(randomFloat(), randomAlphaOfLengthBetween(1, 10),
|
||||
explanations.length > 0 ? explanations : new Explanation[0]);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue