Skip to content

Commit 63eebde

Browse files
authored
Add query Explain Support.
Original Pull Request #1674 Closes #725
1 parent ddc7246 commit 63eebde

File tree

12 files changed

+301
-29
lines changed

12 files changed

+301
-29
lines changed

Diff for: src/main/java/org/springframework/data/elasticsearch/core/RequestFactory.java

+4
Original file line numberDiff line numberDiff line change
@@ -1144,6 +1144,8 @@ private SearchRequest prepareSearchRequest(Query query, @Nullable Class<?> clazz
11441144
sourceBuilder.timeout(timeout);
11451145
}
11461146

1147+
sourceBuilder.explain(query.getExplain());
1148+
11471149
request.source(sourceBuilder);
11481150
return request;
11491151
}
@@ -1224,6 +1226,8 @@ private SearchRequestBuilder prepareSearchRequestBuilder(Query query, Client cli
12241226
searchRequestBuilder.setTimeout(timeout);
12251227
}
12261228

1229+
searchRequestBuilder.setExplain(query.getExplain());
1230+
12271231
return searchRequestBuilder;
12281232
}
12291233

Diff for: src/main/java/org/springframework/data/elasticsearch/core/SearchHit.java

+16-4
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.util.Map;
2424
import java.util.stream.Collectors;
2525

26+
import org.springframework.data.elasticsearch.core.document.Explanation;
2627
import org.springframework.data.elasticsearch.core.document.NestedMetaData;
2728
import org.springframework.lang.Nullable;
2829
import org.springframework.util.Assert;
@@ -44,16 +45,18 @@ public class SearchHit<T> {
4445
private final Map<String, List<String>> highlightFields = new LinkedHashMap<>();
4546
private final Map<String, SearchHits<?>> innerHits = new LinkedHashMap<>();
4647
@Nullable private final NestedMetaData nestedMetaData;
47-
@Nullable private String routing;
48+
@Nullable private final String routing;
49+
@Nullable private final Explanation explanation;
4850

4951
public SearchHit(@Nullable String index, @Nullable String id, @Nullable String routing, float score,
5052
@Nullable Object[] sortValues, @Nullable Map<String, List<String>> highlightFields, T content) {
51-
this(index, id, routing, score, sortValues, highlightFields, null, null, content);
53+
this(index, id, routing, score, sortValues, highlightFields, null, null, null, content);
5254
}
5355

5456
public SearchHit(@Nullable String index, @Nullable String id, @Nullable String routing, float score,
5557
@Nullable Object[] sortValues, @Nullable Map<String, List<String>> highlightFields,
56-
@Nullable Map<String, SearchHits<?>> innerHits, @Nullable NestedMetaData nestedMetaData, T content) {
58+
@Nullable Map<String, SearchHits<?>> innerHits, @Nullable NestedMetaData nestedMetaData,
59+
@Nullable Explanation explanation, T content) {
5760
this.index = index;
5861
this.id = id;
5962
this.routing = routing;
@@ -69,7 +72,7 @@ public SearchHit(@Nullable String index, @Nullable String id, @Nullable String r
6972
}
7073

7174
this.nestedMetaData = nestedMetaData;
72-
75+
this.explanation = explanation;
7376
this.content = content;
7477
}
7578

@@ -176,4 +179,13 @@ public String toString() {
176179
public String getRouting() {
177180
return routing;
178181
}
182+
183+
/**
184+
* @return the explanation for this SearchHit.
185+
* @since 4.2
186+
*/
187+
@Nullable
188+
public Explanation getExplanation() {
189+
return explanation;
190+
}
179191
}

Diff for: src/main/java/org/springframework/data/elasticsearch/core/SearchHitMapping.java

+2
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ SearchHit<T> mapHit(SearchDocument searchDocument, T content) {
113113
getHighlightsAndRemapFieldNames(searchDocument), //
114114
mapInnerHits(searchDocument), //
115115
searchDocument.getNestedMetaData(), //
116+
searchDocument.getExplanation(), //
116117
content); //
117118
}
118119

@@ -196,6 +197,7 @@ private SearchHits<?> mapInnerDocuments(SearchHits<SearchDocument> searchHits, C
196197
searchDocument.getHighlightFields(), //
197198
searchHit.getInnerHits(), //
198199
persistentEntityWithNestedMetaData.nestedMetaData, //
200+
searchHit.getExplanation(), //
199201
targetObject));
200202
});
201203

Diff for: src/main/java/org/springframework/data/elasticsearch/core/document/DocumentAdapters.java

+35-16
Original file line numberDiff line numberDiff line change
@@ -161,24 +161,20 @@ public static SearchDocument from(SearchHit source) {
161161
Map<String, SearchHits> sourceInnerHits = source.getInnerHits();
162162

163163
if (sourceInnerHits != null) {
164-
sourceInnerHits.forEach((name, searchHits) -> {
165-
innerHits.put(name, SearchDocumentResponse.from(searchHits, null, null));
166-
});
164+
sourceInnerHits
165+
.forEach((name, searchHits) -> innerHits.put(name, SearchDocumentResponse.from(searchHits, null, null)));
167166
}
168167

169-
NestedMetaData nestedMetaData = null;
170-
171-
if (source.getNestedIdentity() != null) {
172-
nestedMetaData = from(source.getNestedIdentity());
173-
}
168+
NestedMetaData nestedMetaData = from(source.getNestedIdentity());
169+
Explanation explanation = from(source.getExplanation());
174170

175171
BytesReference sourceRef = source.getSourceRef();
176172

177173
if (sourceRef == null || sourceRef.length() == 0) {
178174
return new SearchDocumentAdapter(
179175
source.getScore(), source.getSortValues(), source.getFields(), highlightFields, fromDocumentFields(source,
180176
source.getIndex(), source.getId(), source.getVersion(), source.getSeqNo(), source.getPrimaryTerm()),
181-
innerHits, nestedMetaData);
177+
innerHits, nestedMetaData, explanation);
182178
}
183179

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

194190
return new SearchDocumentAdapter(source.getScore(), source.getSortValues(), source.getFields(), highlightFields,
195-
document, innerHits, nestedMetaData);
191+
document, innerHits, nestedMetaData, explanation);
196192
}
197193

198-
private static NestedMetaData from(SearchHit.NestedIdentity nestedIdentity) {
194+
@Nullable
195+
private static Explanation from(@Nullable org.apache.lucene.search.Explanation explanation) {
199196

200-
NestedMetaData child = null;
197+
if (explanation == null) {
198+
return null;
199+
}
201200

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

206+
return new Explanation(explanation.isMatch(), explanation.getValue().doubleValue(), explanation.getDescription(),
207+
details);
208+
}
209+
210+
@Nullable
211+
private static NestedMetaData from(@Nullable SearchHit.NestedIdentity nestedIdentity) {
212+
213+
if (nestedIdentity == null) {
214+
return null;
215+
}
216+
NestedMetaData child = from(nestedIdentity.getChild());
206217
return NestedMetaData.of(nestedIdentity.getField().string(), nestedIdentity.getOffset(), child);
207218
}
208219

209220
/**
210221
* Create an unmodifiable {@link Document} from {@link Iterable} of {@link DocumentField}s.
211222
*
212223
* @param documentFields the {@link DocumentField}s backing the {@link Document}.
213-
* @param index
224+
* @param index the index where the Document was found
214225
* @return the adapted {@link Document}.
215226
*/
216227
public static Document fromDocumentFields(Iterable<DocumentField> documentFields, String index, String id,
@@ -458,10 +469,11 @@ static class SearchDocumentAdapter implements SearchDocument {
458469
private final Map<String, List<String>> highlightFields = new HashMap<>();
459470
private final Map<String, SearchDocumentResponse> innerHits = new HashMap<>();
460471
@Nullable private final NestedMetaData nestedMetaData;
472+
@Nullable private final Explanation explanation;
461473

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

466478
this.score = score;
467479
this.sortValues = sortValues;
@@ -470,6 +482,7 @@ static class SearchDocumentAdapter implements SearchDocument {
470482
this.highlightFields.putAll(highlightFields);
471483
this.innerHits.putAll(innerHits);
472484
this.nestedMetaData = nestedMetaData;
485+
this.explanation = explanation;
473486
}
474487

475488
@Override
@@ -646,6 +659,12 @@ public Set<Entry<String, Object>> entrySet() {
646659
return delegate.entrySet();
647660
}
648661

662+
@Override
663+
@Nullable
664+
public Explanation getExplanation() {
665+
return explanation;
666+
}
667+
649668
@Override
650669
public boolean equals(Object o) {
651670
if (this == o) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/*
2+
* Copyright 2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.elasticsearch.core.document;
17+
18+
import java.util.List;
19+
import java.util.Objects;
20+
21+
import org.springframework.lang.Nullable;
22+
import org.springframework.util.Assert;
23+
24+
/**
25+
* class that holds explanations returned from an Elasticsearch search.
26+
*
27+
* @author Peter-Josef Meisch
28+
*/
29+
public class Explanation {
30+
private final boolean match;
31+
private final Double value;
32+
@Nullable private final String description;
33+
private final List<Explanation> details;
34+
35+
public Explanation(boolean match, Double value, @Nullable String description, List<Explanation> details) {
36+
37+
Assert.notNull(value, "value must not be null");
38+
Assert.notNull(details, "details must not be null");
39+
40+
this.match = match;
41+
this.value = value;
42+
this.description = description;
43+
this.details = details;
44+
}
45+
46+
public boolean isMatch() {
47+
return match;
48+
}
49+
50+
public Double getValue() {
51+
return value;
52+
}
53+
54+
@Nullable
55+
public String getDescription() {
56+
return description;
57+
}
58+
59+
public List<Explanation> getDetails() {
60+
return details;
61+
}
62+
63+
@Override
64+
public boolean equals(Object o) {
65+
if (this == o)
66+
return true;
67+
if (o == null || getClass() != o.getClass())
68+
return false;
69+
70+
Explanation that = (Explanation) o;
71+
72+
if (match != that.match)
73+
return false;
74+
if (!value.equals(that.value))
75+
return false;
76+
if (!Objects.equals(description, that.description))
77+
return false;
78+
return details.equals(that.details);
79+
}
80+
81+
@Override
82+
public int hashCode() {
83+
int result = (match ? 1 : 0);
84+
result = 31 * result + value.hashCode();
85+
result = 31 * result + (description != null ? description.hashCode() : 0);
86+
result = 31 * result + details.hashCode();
87+
return result;
88+
}
89+
90+
@Override
91+
public String toString() {
92+
return "Explanation{" + //
93+
"match=" + match + //
94+
", value=" + value + //
95+
", description='" + description + '\'' + //
96+
", details=" + details + //
97+
'}'; //
98+
}
99+
}

Diff for: src/main/java/org/springframework/data/elasticsearch/core/document/SearchDocument.java

+8-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
import org.springframework.lang.Nullable;
2222

2323
/**
24-
* Extension to {@link Document} exposing a search response related data.
24+
* Extension to {@link Document} exposing search response related data.
2525
*
2626
* @author Mark Paluch
2727
* @author Peter-Josef Meisch
@@ -98,4 +98,11 @@ default NestedMetaData getNestedMetaData() {
9898
default String getRouting() {
9999
return getFieldValue("_routing");
100100
}
101+
102+
/**
103+
* @return the explanation for the SearchHit.
104+
* @since 4.2
105+
*/
106+
@Nullable
107+
Explanation getExplanation();
101108
}
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
/**
2+
* Classes related to the Document structure of Elasticsearch documents and search responses.
3+
*/
14
@org.springframework.lang.NonNullApi
25
@org.springframework.lang.NonNullFields
36
package org.springframework.data.elasticsearch.core.document;

Diff for: src/main/java/org/springframework/data/elasticsearch/core/query/AbstractQuery.java

+13
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ abstract class AbstractQuery implements Query {
6161
@Nullable private Integer trackTotalHitsUpTo;
6262
@Nullable private Duration scrollTime;
6363
@Nullable private TimeValue timeout;
64+
private boolean explain = false;
6465

6566
@Override
6667
@Nullable
@@ -270,4 +271,16 @@ public TimeValue getTimeout() {
270271
public void setTimeout(@Nullable TimeValue timeout) {
271272
this.timeout = timeout;
272273
}
274+
275+
@Override
276+
public boolean getExplain() {
277+
return explain;
278+
}
279+
280+
/**
281+
* @param explain the explain flag on the query.
282+
*/
283+
public void setExplain(boolean explain) {
284+
this.explain = explain;
285+
}
273286
}

Diff for: src/main/java/org/springframework/data/elasticsearch/core/query/Query.java

+8
Original file line numberDiff line numberDiff line change
@@ -285,4 +285,12 @@ default boolean hasScrollTime() {
285285
*/
286286
@Nullable
287287
TimeValue getTimeout();
288+
289+
/**
290+
* @return {@literal true} when the query has the eplain parameter set, defaults to {@literal false}
291+
* @since 4.2
292+
*/
293+
default boolean getExplain() {
294+
return false;
295+
}
288296
}

0 commit comments

Comments
 (0)