Skip to content

Commit 433d529

Browse files
authored
Expose search shard statistics in search hits.
Original Pull Request #2806 Closes #2605
1 parent 6350514 commit 433d529

14 files changed

+266
-33
lines changed

src/main/java/org/springframework/data/elasticsearch/client/elc/DocumentAdapters.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
* {@link org.springframework.data.elasticsearch.core.document.Document}
5050
*
5151
* @author Peter-Josef Meisch
52+
* @author Haibo Liu
5253
* @since 4.4
5354
*/
5455
final class DocumentAdapters {
@@ -73,7 +74,7 @@ public static SearchDocument from(Hit<?> hit, JsonpMapper jsonpMapper) {
7374
Map<String, SearchDocumentResponse> innerHits = new LinkedHashMap<>();
7475
hit.innerHits().forEach((name, innerHitsResult) -> {
7576
// noinspection ReturnOfNull
76-
innerHits.put(name, SearchDocumentResponseBuilder.from(innerHitsResult.hits(), null, null, null, null,
77+
innerHits.put(name, SearchDocumentResponseBuilder.from(innerHitsResult.hits(), null, null, null, null, null,
7778
searchDocument -> null, jsonpMapper));
7879
});
7980

src/main/java/org/springframework/data/elasticsearch/client/elc/ResponseConverter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -541,7 +541,7 @@ private long timeToLong(Time time) {
541541
}
542542

543543
@Nullable
544-
private static ElasticsearchErrorCause toErrorCause(@Nullable ErrorCause errorCause) {
544+
static ElasticsearchErrorCause toErrorCause(@Nullable ErrorCause errorCause) {
545545

546546
if (errorCause != null) {
547547
return new ElasticsearchErrorCause( //

src/main/java/org/springframework/data/elasticsearch/client/elc/SearchDocumentResponseBuilder.java

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
*/
1616
package org.springframework.data.elasticsearch.client.elc;
1717

18+
import co.elastic.clients.elasticsearch._types.ShardFailure;
19+
import co.elastic.clients.elasticsearch._types.ShardStatistics;
1820
import co.elastic.clients.elasticsearch._types.aggregations.Aggregate;
1921
import co.elastic.clients.elasticsearch.core.SearchResponse;
2022
import co.elastic.clients.elasticsearch.core.SearchTemplateResponse;
@@ -36,6 +38,7 @@
3638

3739
import org.apache.commons.logging.Log;
3840
import org.apache.commons.logging.LogFactory;
41+
import org.springframework.data.elasticsearch.core.SearchShardStatistics;
3942
import org.springframework.data.elasticsearch.core.TotalHitsRelation;
4043
import org.springframework.data.elasticsearch.core.document.SearchDocument;
4144
import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse;
@@ -52,6 +55,7 @@
5255
* Factory class to create {@link SearchDocumentResponse} instances.
5356
*
5457
* @author Peter-Josef Meisch
58+
* @author Haibo Liu
5559
* @since 4.4
5660
*/
5761
class SearchDocumentResponseBuilder {
@@ -78,8 +82,9 @@ public static <T> SearchDocumentResponse from(ResponseBody<EntityAsMap> response
7882
Map<String, Aggregate> aggregations = responseBody.aggregations();
7983
Map<String, List<Suggestion<EntityAsMap>>> suggest = responseBody.suggest();
8084
var pointInTimeId = responseBody.pitId();
85+
var shards = responseBody.shards();
8186

82-
return from(hitsMetadata, scrollId, pointInTimeId, aggregations, suggest, entityCreator, jsonpMapper);
87+
return from(hitsMetadata, shards, scrollId, pointInTimeId, aggregations, suggest, entityCreator, jsonpMapper);
8388
}
8489

8590
/**
@@ -98,13 +103,14 @@ public static <T> SearchDocumentResponse from(SearchTemplateResponse<EntityAsMap
98103
Assert.notNull(entityCreator, "entityCreator must not be null");
99104
Assert.notNull(jsonpMapper, "jsonpMapper must not be null");
100105

106+
var shards = response.shards();
101107
var hitsMetadata = response.hits();
102108
var scrollId = response.scrollId();
103109
var aggregations = response.aggregations();
104110
var suggest = response.suggest();
105111
var pointInTimeId = response.pitId();
106112

107-
return from(hitsMetadata, scrollId, pointInTimeId, aggregations, suggest, entityCreator, jsonpMapper);
113+
return from(hitsMetadata, shards, scrollId, pointInTimeId, aggregations, suggest, entityCreator, jsonpMapper);
108114
}
109115

110116
/**
@@ -120,8 +126,8 @@ public static <T> SearchDocumentResponse from(SearchTemplateResponse<EntityAsMap
120126
* @param jsonpMapper to map JsonData objects
121127
* @return the {@link SearchDocumentResponse}
122128
*/
123-
public static <T> SearchDocumentResponse from(HitsMetadata<?> hitsMetadata, @Nullable String scrollId,
124-
@Nullable String pointInTimeId, @Nullable Map<String, Aggregate> aggregations,
129+
public static <T> SearchDocumentResponse from(HitsMetadata<?> hitsMetadata, @Nullable ShardStatistics shards,
130+
@Nullable String scrollId, @Nullable String pointInTimeId, @Nullable Map<String, Aggregate> aggregations,
125131
Map<String, List<Suggestion<EntityAsMap>>> suggestES, SearchDocumentResponse.EntityCreator<T> entityCreator,
126132
JsonpMapper jsonpMapper) {
127133

@@ -155,8 +161,19 @@ public static <T> SearchDocumentResponse from(HitsMetadata<?> hitsMetadata, @Nul
155161

156162
Suggest suggest = suggestFrom(suggestES, entityCreator);
157163

164+
SearchShardStatistics shardStatistics = shards != null ? shardsFrom(shards) : null;
165+
158166
return new SearchDocumentResponse(totalHits, totalHitsRelation, maxScore, scrollId, pointInTimeId, searchDocuments,
159-
aggregationsContainer, suggest);
167+
aggregationsContainer, suggest, shardStatistics);
168+
}
169+
170+
private static SearchShardStatistics shardsFrom(ShardStatistics shards) {
171+
List<ShardFailure> failures = shards.failures();
172+
List<SearchShardStatistics.Failure> searchFailures = failures.stream()
173+
.map(f -> SearchShardStatistics.Failure.of(f.index(), f.node(), f.status(), f.shard(), null,
174+
ResponseConverter.toErrorCause(f.reason())))
175+
.toList();
176+
return SearchShardStatistics.of(shards.failed(), shards.successful(), shards.total(), shards.skipped(), searchFailures);
160177
}
161178

162179
@Nullable

src/main/java/org/springframework/data/elasticsearch/core/SearchHitMapping.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
* @author Matt Gilene
4747
* @author Sascha Woo
4848
* @author Jakob Hoeper
49+
* @author Haibo Liu
4950
* @since 4.0
5051
*/
5152
public class SearchHitMapping<T> {
@@ -84,6 +85,7 @@ private SearchHitsImpl<T> mapHitsFromResponse(SearchDocumentResponse searchDocum
8485
"Count of documents must match the count of entities");
8586

8687
long totalHits = searchDocumentResponse.getTotalHits();
88+
SearchShardStatistics shardStatistics = searchDocumentResponse.getSearchShardStatistics();
8789
float maxScore = searchDocumentResponse.getMaxScore();
8890
String scrollId = searchDocumentResponse.getScrollId();
8991
String pointInTimeId = searchDocumentResponse.getPointInTimeId();
@@ -103,7 +105,7 @@ private SearchHitsImpl<T> mapHitsFromResponse(SearchDocumentResponse searchDocum
103105
mapHitsInCompletionSuggestion(suggest);
104106

105107
return new SearchHitsImpl<>(totalHits, totalHitsRelation, maxScore, scrollId, pointInTimeId, searchHits,
106-
aggregations, suggest);
108+
aggregations, suggest, shardStatistics);
107109
}
108110

109111
@SuppressWarnings("unchecked")
@@ -240,7 +242,8 @@ private SearchHits<?> mapInnerDocuments(SearchHits<SearchDocument> searchHits, C
240242
searchHits.getPointInTimeId(), //
241243
convertedSearchHits, //
242244
searchHits.getAggregations(), //
243-
searchHits.getSuggest());
245+
searchHits.getSuggest(),
246+
searchHits.getSearchShardStatistics());
244247
}
245248
} catch (Exception e) {
246249
throw new UncategorizedElasticsearchException("Unable to convert inner hits.", e);

src/main/java/org/springframework/data/elasticsearch/core/SearchHits.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
*
2828
* @param <T> the result data class.
2929
* @author Sascha Woo
30+
* @author Haibo Liu
3031
* @since 4.0
3132
*/
3233
public interface SearchHits<T> extends Streamable<SearchHit<T>> {
@@ -108,4 +109,10 @@ default Iterator<SearchHit<T>> iterator() {
108109
*/
109110
@Nullable
110111
String getPointInTimeId();
112+
113+
/**
114+
* @return shard statistics for the search hit.
115+
*/
116+
@Nullable
117+
SearchShardStatistics getSearchShardStatistics();
111118
}

src/main/java/org/springframework/data/elasticsearch/core/SearchHitsImpl.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
* @param <T> the result data class.
3030
* @author Peter-Josef Meisch
3131
* @author Sascha Woo
32+
* @author Haibo Liu
3233
* @since 4.0
3334
*/
3435
public class SearchHitsImpl<T> implements SearchScrollHits<T> {
@@ -42,6 +43,7 @@ public class SearchHitsImpl<T> implements SearchScrollHits<T> {
4243
@Nullable private final AggregationsContainer<?> aggregations;
4344
@Nullable private final Suggest suggest;
4445
@Nullable private String pointInTimeId;
46+
@Nullable private final SearchShardStatistics searchShardStatistics;
4547

4648
/**
4749
* @param totalHits the number of total hits for the search
@@ -53,7 +55,8 @@ public class SearchHitsImpl<T> implements SearchScrollHits<T> {
5355
*/
5456
public SearchHitsImpl(long totalHits, TotalHitsRelation totalHitsRelation, float maxScore, @Nullable String scrollId,
5557
@Nullable String pointInTimeId, List<? extends SearchHit<T>> searchHits,
56-
@Nullable AggregationsContainer<?> aggregations, @Nullable Suggest suggest) {
58+
@Nullable AggregationsContainer<?> aggregations, @Nullable Suggest suggest,
59+
@Nullable SearchShardStatistics searchShardStatistics) {
5760

5861
Assert.notNull(searchHits, "searchHits must not be null");
5962

@@ -66,6 +69,7 @@ public SearchHitsImpl(long totalHits, TotalHitsRelation totalHitsRelation, float
6669
this.aggregations = aggregations;
6770
this.suggest = suggest;
6871
this.unmodifiableSearchHits = Lazy.of(() -> Collections.unmodifiableList(searchHits));
72+
this.searchShardStatistics = searchShardStatistics;
6973
}
7074

7175
// region getter
@@ -118,6 +122,11 @@ public String getPointInTimeId() {
118122
return pointInTimeId;
119123
}
120124

125+
@Override
126+
public SearchShardStatistics getSearchShardStatistics() {
127+
return searchShardStatistics;
128+
}
129+
121130
@Override
122131
public String toString() {
123132
return "SearchHits{" + //
@@ -128,6 +137,7 @@ public String toString() {
128137
", pointInTimeId='" + pointInTimeId + '\'' + //
129138
", searchHits={" + searchHits.size() + " elements}" + //
130139
", aggregations=" + aggregations + //
140+
", shardStatistics=" + searchShardStatistics + //
131141
'}';
132142
}
133143
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
/*
2+
* Copyright 2023 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;
17+
18+
import java.util.List;
19+
20+
import org.springframework.data.elasticsearch.ElasticsearchErrorCause;
21+
import org.springframework.lang.Nullable;
22+
23+
/**
24+
* @author Haibo Liu
25+
* @since 5.3
26+
*/
27+
public class SearchShardStatistics {
28+
private final Number failed;
29+
30+
private final Number successful;
31+
32+
private final Number total;
33+
34+
@Nullable private final Number skipped;
35+
36+
private final List<Failure> failures;
37+
38+
private SearchShardStatistics(Number failed, Number successful, Number total, @Nullable Number skipped,
39+
List<Failure> failures) {
40+
this.failed = failed;
41+
this.successful = successful;
42+
this.total = total;
43+
this.skipped = skipped;
44+
this.failures = failures;
45+
}
46+
47+
public static SearchShardStatistics of(Number failed, Number successful, Number total, @Nullable Number skipped,
48+
List<Failure> failures) {
49+
return new SearchShardStatistics(failed, successful, total, skipped, failures);
50+
}
51+
52+
public Number getFailed() {
53+
return failed;
54+
}
55+
56+
public Number getSuccessful() {
57+
return successful;
58+
}
59+
60+
public Number getTotal() {
61+
return total;
62+
}
63+
64+
@Nullable
65+
public Number getSkipped() {
66+
return skipped;
67+
}
68+
69+
public boolean isFailed() {
70+
return failed.intValue() > 0;
71+
}
72+
73+
public List<Failure> getFailures() {
74+
return failures;
75+
}
76+
77+
public static class Failure {
78+
@Nullable private final String index;
79+
@Nullable private final String node;
80+
@Nullable private final String status;
81+
private final int shard;
82+
@Nullable private final Exception exception;
83+
@Nullable private final ElasticsearchErrorCause elasticsearchErrorCause;
84+
85+
private Failure(@Nullable String index, @Nullable String node, @Nullable String status, int shard,
86+
@Nullable Exception exception, @Nullable ElasticsearchErrorCause elasticsearchErrorCause) {
87+
this.index = index;
88+
this.node = node;
89+
this.status = status;
90+
this.shard = shard;
91+
this.exception = exception;
92+
this.elasticsearchErrorCause = elasticsearchErrorCause;
93+
}
94+
95+
public static SearchShardStatistics.Failure of(@Nullable String index, @Nullable String node,
96+
@Nullable String status, int shard, @Nullable Exception exception,
97+
@Nullable ElasticsearchErrorCause elasticsearchErrorCause) {
98+
return new SearchShardStatistics.Failure(index, node, status, shard, exception, elasticsearchErrorCause);
99+
}
100+
101+
@Nullable
102+
public String getIndex() {
103+
return index;
104+
}
105+
106+
@Nullable
107+
public String getNode() {
108+
return node;
109+
}
110+
111+
@Nullable
112+
public String getStatus() {
113+
return status;
114+
}
115+
116+
@Nullable
117+
public Exception getException() {
118+
return exception;
119+
}
120+
121+
public int getShard() {
122+
return shard;
123+
}
124+
125+
@Nullable
126+
public ElasticsearchErrorCause getElasticsearchErrorCause() {
127+
return elasticsearchErrorCause;
128+
}
129+
}
130+
}

src/main/java/org/springframework/data/elasticsearch/core/convert/ElasticsearchCustomConversions.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ public Double convert(BigDecimal source) {
129129
@WritingConverter
130130
enum ByteArrayToBase64Converter implements Converter<byte[], String> {
131131

132-
INSTANCE,;
132+
INSTANCE;
133133

134134
@Override
135135
public String convert(byte[] source) {

0 commit comments

Comments
 (0)