Skip to content

Add query Explain Support. #1674

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 30, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1144,6 +1144,8 @@ private SearchRequest prepareSearchRequest(Query query, @Nullable Class<?> clazz
sourceBuilder.timeout(timeout);
}

sourceBuilder.explain(query.getExplain());

request.source(sourceBuilder);
return request;
}
Expand Down Expand Up @@ -1224,6 +1226,8 @@ private SearchRequestBuilder prepareSearchRequestBuilder(Query query, Client cli
searchRequestBuilder.setTimeout(timeout);
}

searchRequestBuilder.setExplain(query.getExplain());

return searchRequestBuilder;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import java.util.Map;
import java.util.stream.Collectors;

import org.springframework.data.elasticsearch.core.document.Explanation;
import org.springframework.data.elasticsearch.core.document.NestedMetaData;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
Expand All @@ -44,16 +45,18 @@ public class SearchHit<T> {
private final Map<String, List<String>> highlightFields = new LinkedHashMap<>();
private final Map<String, SearchHits<?>> innerHits = new LinkedHashMap<>();
@Nullable private final NestedMetaData nestedMetaData;
@Nullable private String routing;
@Nullable private final String routing;
@Nullable private final Explanation explanation;

public SearchHit(@Nullable String index, @Nullable String id, @Nullable String routing, float score,
@Nullable Object[] sortValues, @Nullable Map<String, List<String>> highlightFields, T content) {
this(index, id, routing, score, sortValues, highlightFields, null, null, content);
this(index, id, routing, score, sortValues, highlightFields, null, null, null, content);
}

public SearchHit(@Nullable String index, @Nullable String id, @Nullable String routing, float score,
@Nullable Object[] sortValues, @Nullable Map<String, List<String>> highlightFields,
@Nullable Map<String, SearchHits<?>> innerHits, @Nullable NestedMetaData nestedMetaData, T content) {
@Nullable Map<String, SearchHits<?>> innerHits, @Nullable NestedMetaData nestedMetaData,
@Nullable Explanation explanation, T content) {
this.index = index;
this.id = id;
this.routing = routing;
Expand All @@ -69,7 +72,7 @@ public SearchHit(@Nullable String index, @Nullable String id, @Nullable String r
}

this.nestedMetaData = nestedMetaData;

this.explanation = explanation;
this.content = content;
}

Expand Down Expand Up @@ -176,4 +179,13 @@ public String toString() {
public String getRouting() {
return routing;
}

/**
* @return the explanation for this SearchHit.
* @since 4.2
*/
@Nullable
public Explanation getExplanation() {
return explanation;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ SearchHit<T> mapHit(SearchDocument searchDocument, T content) {
getHighlightsAndRemapFieldNames(searchDocument), //
mapInnerHits(searchDocument), //
searchDocument.getNestedMetaData(), //
searchDocument.getExplanation(), //
content); //
}

Expand Down Expand Up @@ -196,6 +197,7 @@ private SearchHits<?> mapInnerDocuments(SearchHits<SearchDocument> searchHits, C
searchDocument.getHighlightFields(), //
searchHit.getInnerHits(), //
persistentEntityWithNestedMetaData.nestedMetaData, //
searchHit.getExplanation(), //
targetObject));
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,24 +161,20 @@ public static SearchDocument from(SearchHit source) {
Map<String, SearchHits> sourceInnerHits = source.getInnerHits();

if (sourceInnerHits != null) {
sourceInnerHits.forEach((name, searchHits) -> {
innerHits.put(name, SearchDocumentResponse.from(searchHits, null, null));
});
sourceInnerHits
.forEach((name, searchHits) -> innerHits.put(name, SearchDocumentResponse.from(searchHits, null, null)));
}

NestedMetaData nestedMetaData = null;

if (source.getNestedIdentity() != null) {
nestedMetaData = from(source.getNestedIdentity());
}
NestedMetaData nestedMetaData = from(source.getNestedIdentity());
Explanation explanation = from(source.getExplanation());

BytesReference sourceRef = source.getSourceRef();

if (sourceRef == null || sourceRef.length() == 0) {
return new SearchDocumentAdapter(
source.getScore(), source.getSortValues(), source.getFields(), highlightFields, fromDocumentFields(source,
source.getIndex(), source.getId(), source.getVersion(), source.getSeqNo(), source.getPrimaryTerm()),
innerHits, nestedMetaData);
innerHits, nestedMetaData, explanation);
}

Document document = Document.from(source.getSourceAsMap());
Expand All @@ -192,25 +188,40 @@ public static SearchDocument from(SearchHit source) {
document.setPrimaryTerm(source.getPrimaryTerm());

return new SearchDocumentAdapter(source.getScore(), source.getSortValues(), source.getFields(), highlightFields,
document, innerHits, nestedMetaData);
document, innerHits, nestedMetaData, explanation);
}

private static NestedMetaData from(SearchHit.NestedIdentity nestedIdentity) {
@Nullable
private static Explanation from(@Nullable org.apache.lucene.search.Explanation explanation) {

NestedMetaData child = null;
if (explanation == null) {
return null;
}

if (nestedIdentity.getChild() != null) {
child = from(nestedIdentity.getChild());
List<Explanation> details = new ArrayList<>();
for (org.apache.lucene.search.Explanation detail : explanation.getDetails()) {
details.add(from(detail));
}

return new Explanation(explanation.isMatch(), explanation.getValue().doubleValue(), explanation.getDescription(),
details);
}

@Nullable
private static NestedMetaData from(@Nullable SearchHit.NestedIdentity nestedIdentity) {

if (nestedIdentity == null) {
return null;
}
NestedMetaData child = from(nestedIdentity.getChild());
return NestedMetaData.of(nestedIdentity.getField().string(), nestedIdentity.getOffset(), child);
}

/**
* Create an unmodifiable {@link Document} from {@link Iterable} of {@link DocumentField}s.
*
* @param documentFields the {@link DocumentField}s backing the {@link Document}.
* @param index
* @param index the index where the Document was found
* @return the adapted {@link Document}.
*/
public static Document fromDocumentFields(Iterable<DocumentField> documentFields, String index, String id,
Expand Down Expand Up @@ -458,10 +469,11 @@ static class SearchDocumentAdapter implements SearchDocument {
private final Map<String, List<String>> highlightFields = new HashMap<>();
private final Map<String, SearchDocumentResponse> innerHits = new HashMap<>();
@Nullable private final NestedMetaData nestedMetaData;
@Nullable private final Explanation explanation;

SearchDocumentAdapter(float score, Object[] sortValues, Map<String, DocumentField> fields,
Map<String, List<String>> highlightFields, Document delegate, Map<String, SearchDocumentResponse> innerHits,
@Nullable NestedMetaData nestedMetaData) {
@Nullable NestedMetaData nestedMetaData, @Nullable Explanation explanation) {

this.score = score;
this.sortValues = sortValues;
Expand All @@ -470,6 +482,7 @@ static class SearchDocumentAdapter implements SearchDocument {
this.highlightFields.putAll(highlightFields);
this.innerHits.putAll(innerHits);
this.nestedMetaData = nestedMetaData;
this.explanation = explanation;
}

@Override
Expand Down Expand Up @@ -646,6 +659,12 @@ public Set<Entry<String, Object>> entrySet() {
return delegate.entrySet();
}

@Override
@Nullable
public Explanation getExplanation() {
return explanation;
}

@Override
public boolean equals(Object o) {
if (this == o) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* Copyright 2021 the original author or authors.
*
* Licensed 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
*
* https://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.springframework.data.elasticsearch.core.document;

import java.util.List;
import java.util.Objects;

import org.springframework.lang.Nullable;
import org.springframework.util.Assert;

/**
* class that holds explanations returned from an Elasticsearch search.
*
* @author Peter-Josef Meisch
*/
public class Explanation {
private final boolean match;
private final Double value;
@Nullable private final String description;
private final List<Explanation> details;

public Explanation(boolean match, Double value, @Nullable String description, List<Explanation> details) {

Assert.notNull(value, "value must not be null");
Assert.notNull(details, "details must not be null");

this.match = match;
this.value = value;
this.description = description;
this.details = details;
}

public boolean isMatch() {
return match;
}

public Double getValue() {
return value;
}

@Nullable
public String getDescription() {
return description;
}

public List<Explanation> getDetails() {
return details;
}

@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;

Explanation that = (Explanation) o;

if (match != that.match)
return false;
if (!value.equals(that.value))
return false;
if (!Objects.equals(description, that.description))
return false;
return details.equals(that.details);
}

@Override
public int hashCode() {
int result = (match ? 1 : 0);
result = 31 * result + value.hashCode();
result = 31 * result + (description != null ? description.hashCode() : 0);
result = 31 * result + details.hashCode();
return result;
}

@Override
public String toString() {
return "Explanation{" + //
"match=" + match + //
", value=" + value + //
", description='" + description + '\'' + //
", details=" + details + //
'}'; //
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import org.springframework.lang.Nullable;

/**
* Extension to {@link Document} exposing a search response related data.
* Extension to {@link Document} exposing search response related data.
*
* @author Mark Paluch
* @author Peter-Josef Meisch
Expand Down Expand Up @@ -98,4 +98,11 @@ default NestedMetaData getNestedMetaData() {
default String getRouting() {
return getFieldValue("_routing");
}

/**
* @return the explanation for the SearchHit.
* @since 4.2
*/
@Nullable
Explanation getExplanation();
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
/**
* Classes related to the Document structure of Elasticsearch documents and search responses.
*/
@org.springframework.lang.NonNullApi
@org.springframework.lang.NonNullFields
package org.springframework.data.elasticsearch.core.document;
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ abstract class AbstractQuery implements Query {
@Nullable private Integer trackTotalHitsUpTo;
@Nullable private Duration scrollTime;
@Nullable private TimeValue timeout;
private boolean explain = false;

@Override
@Nullable
Expand Down Expand Up @@ -270,4 +271,16 @@ public TimeValue getTimeout() {
public void setTimeout(@Nullable TimeValue timeout) {
this.timeout = timeout;
}

@Override
public boolean getExplain() {
return explain;
}

/**
* @param explain the explain flag on the query.
*/
public void setExplain(boolean explain) {
this.explain = explain;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -285,4 +285,12 @@ default boolean hasScrollTime() {
*/
@Nullable
TimeValue getTimeout();

/**
* @return {@literal true} when the query has the eplain parameter set, defaults to {@literal false}
* @since 4.2
*/
default boolean getExplain() {
return false;
}
}
Loading