mirror of
https://github.com/spring-projects/spring-data-elasticsearch.git
synced 2025-06-29 23:32:12 +00:00
Expose search shard statistics in search hits.
Original Pull Request #2806 Closes #2605
This commit is contained in:
parent
6350514e7e
commit
433d52981e
@ -49,6 +49,7 @@ import org.springframework.util.Assert;
|
|||||||
* {@link org.springframework.data.elasticsearch.core.document.Document}
|
* {@link org.springframework.data.elasticsearch.core.document.Document}
|
||||||
*
|
*
|
||||||
* @author Peter-Josef Meisch
|
* @author Peter-Josef Meisch
|
||||||
|
* @author Haibo Liu
|
||||||
* @since 4.4
|
* @since 4.4
|
||||||
*/
|
*/
|
||||||
final class DocumentAdapters {
|
final class DocumentAdapters {
|
||||||
@ -73,7 +74,7 @@ final class DocumentAdapters {
|
|||||||
Map<String, SearchDocumentResponse> innerHits = new LinkedHashMap<>();
|
Map<String, SearchDocumentResponse> innerHits = new LinkedHashMap<>();
|
||||||
hit.innerHits().forEach((name, innerHitsResult) -> {
|
hit.innerHits().forEach((name, innerHitsResult) -> {
|
||||||
// noinspection ReturnOfNull
|
// noinspection ReturnOfNull
|
||||||
innerHits.put(name, SearchDocumentResponseBuilder.from(innerHitsResult.hits(), null, null, null, null,
|
innerHits.put(name, SearchDocumentResponseBuilder.from(innerHitsResult.hits(), null, null, null, null, null,
|
||||||
searchDocument -> null, jsonpMapper));
|
searchDocument -> null, jsonpMapper));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -541,7 +541,7 @@ class ResponseConverter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private static ElasticsearchErrorCause toErrorCause(@Nullable ErrorCause errorCause) {
|
static ElasticsearchErrorCause toErrorCause(@Nullable ErrorCause errorCause) {
|
||||||
|
|
||||||
if (errorCause != null) {
|
if (errorCause != null) {
|
||||||
return new ElasticsearchErrorCause( //
|
return new ElasticsearchErrorCause( //
|
||||||
|
@ -15,6 +15,8 @@
|
|||||||
*/
|
*/
|
||||||
package org.springframework.data.elasticsearch.client.elc;
|
package org.springframework.data.elasticsearch.client.elc;
|
||||||
|
|
||||||
|
import co.elastic.clients.elasticsearch._types.ShardFailure;
|
||||||
|
import co.elastic.clients.elasticsearch._types.ShardStatistics;
|
||||||
import co.elastic.clients.elasticsearch._types.aggregations.Aggregate;
|
import co.elastic.clients.elasticsearch._types.aggregations.Aggregate;
|
||||||
import co.elastic.clients.elasticsearch.core.SearchResponse;
|
import co.elastic.clients.elasticsearch.core.SearchResponse;
|
||||||
import co.elastic.clients.elasticsearch.core.SearchTemplateResponse;
|
import co.elastic.clients.elasticsearch.core.SearchTemplateResponse;
|
||||||
@ -36,6 +38,7 @@ import java.util.stream.Collectors;
|
|||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.springframework.data.elasticsearch.core.SearchShardStatistics;
|
||||||
import org.springframework.data.elasticsearch.core.TotalHitsRelation;
|
import org.springframework.data.elasticsearch.core.TotalHitsRelation;
|
||||||
import org.springframework.data.elasticsearch.core.document.SearchDocument;
|
import org.springframework.data.elasticsearch.core.document.SearchDocument;
|
||||||
import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse;
|
import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse;
|
||||||
@ -52,6 +55,7 @@ import org.springframework.util.CollectionUtils;
|
|||||||
* Factory class to create {@link SearchDocumentResponse} instances.
|
* Factory class to create {@link SearchDocumentResponse} instances.
|
||||||
*
|
*
|
||||||
* @author Peter-Josef Meisch
|
* @author Peter-Josef Meisch
|
||||||
|
* @author Haibo Liu
|
||||||
* @since 4.4
|
* @since 4.4
|
||||||
*/
|
*/
|
||||||
class SearchDocumentResponseBuilder {
|
class SearchDocumentResponseBuilder {
|
||||||
@ -78,8 +82,9 @@ class SearchDocumentResponseBuilder {
|
|||||||
Map<String, Aggregate> aggregations = responseBody.aggregations();
|
Map<String, Aggregate> aggregations = responseBody.aggregations();
|
||||||
Map<String, List<Suggestion<EntityAsMap>>> suggest = responseBody.suggest();
|
Map<String, List<Suggestion<EntityAsMap>>> suggest = responseBody.suggest();
|
||||||
var pointInTimeId = responseBody.pitId();
|
var pointInTimeId = responseBody.pitId();
|
||||||
|
var shards = responseBody.shards();
|
||||||
|
|
||||||
return from(hitsMetadata, scrollId, pointInTimeId, aggregations, suggest, entityCreator, jsonpMapper);
|
return from(hitsMetadata, shards, scrollId, pointInTimeId, aggregations, suggest, entityCreator, jsonpMapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -98,13 +103,14 @@ class SearchDocumentResponseBuilder {
|
|||||||
Assert.notNull(entityCreator, "entityCreator must not be null");
|
Assert.notNull(entityCreator, "entityCreator must not be null");
|
||||||
Assert.notNull(jsonpMapper, "jsonpMapper must not be null");
|
Assert.notNull(jsonpMapper, "jsonpMapper must not be null");
|
||||||
|
|
||||||
|
var shards = response.shards();
|
||||||
var hitsMetadata = response.hits();
|
var hitsMetadata = response.hits();
|
||||||
var scrollId = response.scrollId();
|
var scrollId = response.scrollId();
|
||||||
var aggregations = response.aggregations();
|
var aggregations = response.aggregations();
|
||||||
var suggest = response.suggest();
|
var suggest = response.suggest();
|
||||||
var pointInTimeId = response.pitId();
|
var pointInTimeId = response.pitId();
|
||||||
|
|
||||||
return from(hitsMetadata, scrollId, pointInTimeId, aggregations, suggest, entityCreator, jsonpMapper);
|
return from(hitsMetadata, shards, scrollId, pointInTimeId, aggregations, suggest, entityCreator, jsonpMapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -120,8 +126,8 @@ class SearchDocumentResponseBuilder {
|
|||||||
* @param jsonpMapper to map JsonData objects
|
* @param jsonpMapper to map JsonData objects
|
||||||
* @return the {@link SearchDocumentResponse}
|
* @return the {@link SearchDocumentResponse}
|
||||||
*/
|
*/
|
||||||
public static <T> SearchDocumentResponse from(HitsMetadata<?> hitsMetadata, @Nullable String scrollId,
|
public static <T> SearchDocumentResponse from(HitsMetadata<?> hitsMetadata, @Nullable ShardStatistics shards,
|
||||||
@Nullable String pointInTimeId, @Nullable Map<String, Aggregate> aggregations,
|
@Nullable String scrollId, @Nullable String pointInTimeId, @Nullable Map<String, Aggregate> aggregations,
|
||||||
Map<String, List<Suggestion<EntityAsMap>>> suggestES, SearchDocumentResponse.EntityCreator<T> entityCreator,
|
Map<String, List<Suggestion<EntityAsMap>>> suggestES, SearchDocumentResponse.EntityCreator<T> entityCreator,
|
||||||
JsonpMapper jsonpMapper) {
|
JsonpMapper jsonpMapper) {
|
||||||
|
|
||||||
@ -155,8 +161,19 @@ class SearchDocumentResponseBuilder {
|
|||||||
|
|
||||||
Suggest suggest = suggestFrom(suggestES, entityCreator);
|
Suggest suggest = suggestFrom(suggestES, entityCreator);
|
||||||
|
|
||||||
|
SearchShardStatistics shardStatistics = shards != null ? shardsFrom(shards) : null;
|
||||||
|
|
||||||
return new SearchDocumentResponse(totalHits, totalHitsRelation, maxScore, scrollId, pointInTimeId, searchDocuments,
|
return new SearchDocumentResponse(totalHits, totalHitsRelation, maxScore, scrollId, pointInTimeId, searchDocuments,
|
||||||
aggregationsContainer, suggest);
|
aggregationsContainer, suggest, shardStatistics);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SearchShardStatistics shardsFrom(ShardStatistics shards) {
|
||||||
|
List<ShardFailure> failures = shards.failures();
|
||||||
|
List<SearchShardStatistics.Failure> searchFailures = failures.stream()
|
||||||
|
.map(f -> SearchShardStatistics.Failure.of(f.index(), f.node(), f.status(), f.shard(), null,
|
||||||
|
ResponseConverter.toErrorCause(f.reason())))
|
||||||
|
.toList();
|
||||||
|
return SearchShardStatistics.of(shards.failed(), shards.successful(), shards.total(), shards.skipped(), searchFailures);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
|
@ -46,6 +46,7 @@ import org.springframework.util.Assert;
|
|||||||
* @author Matt Gilene
|
* @author Matt Gilene
|
||||||
* @author Sascha Woo
|
* @author Sascha Woo
|
||||||
* @author Jakob Hoeper
|
* @author Jakob Hoeper
|
||||||
|
* @author Haibo Liu
|
||||||
* @since 4.0
|
* @since 4.0
|
||||||
*/
|
*/
|
||||||
public class SearchHitMapping<T> {
|
public class SearchHitMapping<T> {
|
||||||
@ -84,6 +85,7 @@ public class SearchHitMapping<T> {
|
|||||||
"Count of documents must match the count of entities");
|
"Count of documents must match the count of entities");
|
||||||
|
|
||||||
long totalHits = searchDocumentResponse.getTotalHits();
|
long totalHits = searchDocumentResponse.getTotalHits();
|
||||||
|
SearchShardStatistics shardStatistics = searchDocumentResponse.getSearchShardStatistics();
|
||||||
float maxScore = searchDocumentResponse.getMaxScore();
|
float maxScore = searchDocumentResponse.getMaxScore();
|
||||||
String scrollId = searchDocumentResponse.getScrollId();
|
String scrollId = searchDocumentResponse.getScrollId();
|
||||||
String pointInTimeId = searchDocumentResponse.getPointInTimeId();
|
String pointInTimeId = searchDocumentResponse.getPointInTimeId();
|
||||||
@ -103,7 +105,7 @@ public class SearchHitMapping<T> {
|
|||||||
mapHitsInCompletionSuggestion(suggest);
|
mapHitsInCompletionSuggestion(suggest);
|
||||||
|
|
||||||
return new SearchHitsImpl<>(totalHits, totalHitsRelation, maxScore, scrollId, pointInTimeId, searchHits,
|
return new SearchHitsImpl<>(totalHits, totalHitsRelation, maxScore, scrollId, pointInTimeId, searchHits,
|
||||||
aggregations, suggest);
|
aggregations, suggest, shardStatistics);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@ -240,7 +242,8 @@ public class SearchHitMapping<T> {
|
|||||||
searchHits.getPointInTimeId(), //
|
searchHits.getPointInTimeId(), //
|
||||||
convertedSearchHits, //
|
convertedSearchHits, //
|
||||||
searchHits.getAggregations(), //
|
searchHits.getAggregations(), //
|
||||||
searchHits.getSuggest());
|
searchHits.getSuggest(),
|
||||||
|
searchHits.getSearchShardStatistics());
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new UncategorizedElasticsearchException("Unable to convert inner hits.", e);
|
throw new UncategorizedElasticsearchException("Unable to convert inner hits.", e);
|
||||||
|
@ -27,6 +27,7 @@ import org.springframework.lang.Nullable;
|
|||||||
*
|
*
|
||||||
* @param <T> the result data class.
|
* @param <T> the result data class.
|
||||||
* @author Sascha Woo
|
* @author Sascha Woo
|
||||||
|
* @author Haibo Liu
|
||||||
* @since 4.0
|
* @since 4.0
|
||||||
*/
|
*/
|
||||||
public interface SearchHits<T> extends Streamable<SearchHit<T>> {
|
public interface SearchHits<T> extends Streamable<SearchHit<T>> {
|
||||||
@ -108,4 +109,10 @@ public interface SearchHits<T> extends Streamable<SearchHit<T>> {
|
|||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
String getPointInTimeId();
|
String getPointInTimeId();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return shard statistics for the search hit.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
SearchShardStatistics getSearchShardStatistics();
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,7 @@ import org.springframework.util.Assert;
|
|||||||
* @param <T> the result data class.
|
* @param <T> the result data class.
|
||||||
* @author Peter-Josef Meisch
|
* @author Peter-Josef Meisch
|
||||||
* @author Sascha Woo
|
* @author Sascha Woo
|
||||||
|
* @author Haibo Liu
|
||||||
* @since 4.0
|
* @since 4.0
|
||||||
*/
|
*/
|
||||||
public class SearchHitsImpl<T> implements SearchScrollHits<T> {
|
public class SearchHitsImpl<T> implements SearchScrollHits<T> {
|
||||||
@ -42,6 +43,7 @@ public class SearchHitsImpl<T> implements SearchScrollHits<T> {
|
|||||||
@Nullable private final AggregationsContainer<?> aggregations;
|
@Nullable private final AggregationsContainer<?> aggregations;
|
||||||
@Nullable private final Suggest suggest;
|
@Nullable private final Suggest suggest;
|
||||||
@Nullable private String pointInTimeId;
|
@Nullable private String pointInTimeId;
|
||||||
|
@Nullable private final SearchShardStatistics searchShardStatistics;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param totalHits the number of total hits for the search
|
* @param totalHits the number of total hits for the search
|
||||||
@ -53,7 +55,8 @@ public class SearchHitsImpl<T> implements SearchScrollHits<T> {
|
|||||||
*/
|
*/
|
||||||
public SearchHitsImpl(long totalHits, TotalHitsRelation totalHitsRelation, float maxScore, @Nullable String scrollId,
|
public SearchHitsImpl(long totalHits, TotalHitsRelation totalHitsRelation, float maxScore, @Nullable String scrollId,
|
||||||
@Nullable String pointInTimeId, List<? extends SearchHit<T>> searchHits,
|
@Nullable String pointInTimeId, List<? extends SearchHit<T>> searchHits,
|
||||||
@Nullable AggregationsContainer<?> aggregations, @Nullable Suggest suggest) {
|
@Nullable AggregationsContainer<?> aggregations, @Nullable Suggest suggest,
|
||||||
|
@Nullable SearchShardStatistics searchShardStatistics) {
|
||||||
|
|
||||||
Assert.notNull(searchHits, "searchHits must not be null");
|
Assert.notNull(searchHits, "searchHits must not be null");
|
||||||
|
|
||||||
@ -66,6 +69,7 @@ public class SearchHitsImpl<T> implements SearchScrollHits<T> {
|
|||||||
this.aggregations = aggregations;
|
this.aggregations = aggregations;
|
||||||
this.suggest = suggest;
|
this.suggest = suggest;
|
||||||
this.unmodifiableSearchHits = Lazy.of(() -> Collections.unmodifiableList(searchHits));
|
this.unmodifiableSearchHits = Lazy.of(() -> Collections.unmodifiableList(searchHits));
|
||||||
|
this.searchShardStatistics = searchShardStatistics;
|
||||||
}
|
}
|
||||||
|
|
||||||
// region getter
|
// region getter
|
||||||
@ -118,6 +122,11 @@ public class SearchHitsImpl<T> implements SearchScrollHits<T> {
|
|||||||
return pointInTimeId;
|
return pointInTimeId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SearchShardStatistics getSearchShardStatistics() {
|
||||||
|
return searchShardStatistics;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "SearchHits{" + //
|
return "SearchHits{" + //
|
||||||
@ -128,6 +137,7 @@ public class SearchHitsImpl<T> implements SearchScrollHits<T> {
|
|||||||
", pointInTimeId='" + pointInTimeId + '\'' + //
|
", pointInTimeId='" + pointInTimeId + '\'' + //
|
||||||
", searchHits={" + searchHits.size() + " elements}" + //
|
", searchHits={" + searchHits.size() + " elements}" + //
|
||||||
", aggregations=" + aggregations + //
|
", aggregations=" + aggregations + //
|
||||||
|
", shardStatistics=" + searchShardStatistics + //
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,130 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2023 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;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.data.elasticsearch.ElasticsearchErrorCause;
|
||||||
|
import org.springframework.lang.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Haibo Liu
|
||||||
|
* @since 5.3
|
||||||
|
*/
|
||||||
|
public class SearchShardStatistics {
|
||||||
|
private final Number failed;
|
||||||
|
|
||||||
|
private final Number successful;
|
||||||
|
|
||||||
|
private final Number total;
|
||||||
|
|
||||||
|
@Nullable private final Number skipped;
|
||||||
|
|
||||||
|
private final List<Failure> failures;
|
||||||
|
|
||||||
|
private SearchShardStatistics(Number failed, Number successful, Number total, @Nullable Number skipped,
|
||||||
|
List<Failure> failures) {
|
||||||
|
this.failed = failed;
|
||||||
|
this.successful = successful;
|
||||||
|
this.total = total;
|
||||||
|
this.skipped = skipped;
|
||||||
|
this.failures = failures;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SearchShardStatistics of(Number failed, Number successful, Number total, @Nullable Number skipped,
|
||||||
|
List<Failure> failures) {
|
||||||
|
return new SearchShardStatistics(failed, successful, total, skipped, failures);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Number getFailed() {
|
||||||
|
return failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Number getSuccessful() {
|
||||||
|
return successful;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Number getTotal() {
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public Number getSkipped() {
|
||||||
|
return skipped;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFailed() {
|
||||||
|
return failed.intValue() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Failure> getFailures() {
|
||||||
|
return failures;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Failure {
|
||||||
|
@Nullable private final String index;
|
||||||
|
@Nullable private final String node;
|
||||||
|
@Nullable private final String status;
|
||||||
|
private final int shard;
|
||||||
|
@Nullable private final Exception exception;
|
||||||
|
@Nullable private final ElasticsearchErrorCause elasticsearchErrorCause;
|
||||||
|
|
||||||
|
private Failure(@Nullable String index, @Nullable String node, @Nullable String status, int shard,
|
||||||
|
@Nullable Exception exception, @Nullable ElasticsearchErrorCause elasticsearchErrorCause) {
|
||||||
|
this.index = index;
|
||||||
|
this.node = node;
|
||||||
|
this.status = status;
|
||||||
|
this.shard = shard;
|
||||||
|
this.exception = exception;
|
||||||
|
this.elasticsearchErrorCause = elasticsearchErrorCause;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SearchShardStatistics.Failure of(@Nullable String index, @Nullable String node,
|
||||||
|
@Nullable String status, int shard, @Nullable Exception exception,
|
||||||
|
@Nullable ElasticsearchErrorCause elasticsearchErrorCause) {
|
||||||
|
return new SearchShardStatistics.Failure(index, node, status, shard, exception, elasticsearchErrorCause);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public String getIndex() {
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public String getNode() {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public String getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public Exception getException() {
|
||||||
|
return exception;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getShard() {
|
||||||
|
return shard;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public ElasticsearchErrorCause getElasticsearchErrorCause() {
|
||||||
|
return elasticsearchErrorCause;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -129,7 +129,7 @@ public class ElasticsearchCustomConversions extends CustomConversions {
|
|||||||
@WritingConverter
|
@WritingConverter
|
||||||
enum ByteArrayToBase64Converter implements Converter<byte[], String> {
|
enum ByteArrayToBase64Converter implements Converter<byte[], String> {
|
||||||
|
|
||||||
INSTANCE,;
|
INSTANCE;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String convert(byte[] source) {
|
public String convert(byte[] source) {
|
||||||
|
@ -20,6 +20,7 @@ import java.util.concurrent.CompletableFuture;
|
|||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
import org.springframework.data.elasticsearch.core.AggregationsContainer;
|
import org.springframework.data.elasticsearch.core.AggregationsContainer;
|
||||||
|
import org.springframework.data.elasticsearch.core.SearchShardStatistics;
|
||||||
import org.springframework.data.elasticsearch.core.suggest.response.Suggest;
|
import org.springframework.data.elasticsearch.core.suggest.response.Suggest;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
|
|
||||||
@ -27,6 +28,7 @@ import org.springframework.lang.Nullable;
|
|||||||
* This represents the complete search response from Elasticsearch, including the returned documents.
|
* This represents the complete search response from Elasticsearch, including the returned documents.
|
||||||
*
|
*
|
||||||
* @author Peter-Josef Meisch
|
* @author Peter-Josef Meisch
|
||||||
|
* @author Haibo Liu
|
||||||
* @since 4.0
|
* @since 4.0
|
||||||
*/
|
*/
|
||||||
public class SearchDocumentResponse {
|
public class SearchDocumentResponse {
|
||||||
@ -40,10 +42,12 @@ public class SearchDocumentResponse {
|
|||||||
@Nullable private final Suggest suggest;
|
@Nullable private final Suggest suggest;
|
||||||
|
|
||||||
@Nullable String pointInTimeId;
|
@Nullable String pointInTimeId;
|
||||||
|
@Nullable private final SearchShardStatistics searchShardStatistics;
|
||||||
|
|
||||||
public SearchDocumentResponse(long totalHits, String totalHitsRelation, float maxScore, @Nullable String scrollId,
|
public SearchDocumentResponse(long totalHits, String totalHitsRelation, float maxScore, @Nullable String scrollId,
|
||||||
@Nullable String pointInTimeId, List<SearchDocument> searchDocuments,
|
@Nullable String pointInTimeId, List<SearchDocument> searchDocuments,
|
||||||
@Nullable AggregationsContainer<?> aggregationsContainer, @Nullable Suggest suggest) {
|
@Nullable AggregationsContainer<?> aggregationsContainer, @Nullable Suggest suggest,
|
||||||
|
@Nullable SearchShardStatistics searchShardStatistics) {
|
||||||
this.totalHits = totalHits;
|
this.totalHits = totalHits;
|
||||||
this.totalHitsRelation = totalHitsRelation;
|
this.totalHitsRelation = totalHitsRelation;
|
||||||
this.maxScore = maxScore;
|
this.maxScore = maxScore;
|
||||||
@ -52,6 +56,7 @@ public class SearchDocumentResponse {
|
|||||||
this.searchDocuments = searchDocuments;
|
this.searchDocuments = searchDocuments;
|
||||||
this.aggregations = aggregationsContainer;
|
this.aggregations = aggregationsContainer;
|
||||||
this.suggest = suggest;
|
this.suggest = suggest;
|
||||||
|
this.searchShardStatistics = searchShardStatistics;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getTotalHits() {
|
public long getTotalHits() {
|
||||||
@ -93,6 +98,11 @@ public class SearchDocumentResponse {
|
|||||||
return pointInTimeId;
|
return pointInTimeId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public SearchShardStatistics getSearchShardStatistics() {
|
||||||
|
return searchShardStatistics;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A function to convert a {@link SearchDocument} async into an entity. Asynchronous so that it can be used from the
|
* A function to convert a {@link SearchDocument} async into an entity. Asynchronous so that it can be used from the
|
||||||
* imperative and the reactive code.
|
* imperative and the reactive code.
|
||||||
|
@ -15,14 +15,10 @@
|
|||||||
*/
|
*/
|
||||||
package org.springframework.data.elasticsearch.repository.query;
|
package org.springframework.data.elasticsearch.repository.query;
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
|
|
||||||
import org.springframework.data.domain.PageRequest;
|
import org.springframework.data.domain.PageRequest;
|
||||||
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
|
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
|
||||||
import org.springframework.data.elasticsearch.core.SearchHitSupport;
|
import org.springframework.data.elasticsearch.core.SearchHitSupport;
|
||||||
import org.springframework.data.elasticsearch.core.SearchHits;
|
import org.springframework.data.elasticsearch.core.SearchHits;
|
||||||
import org.springframework.data.elasticsearch.core.SearchHitsImpl;
|
|
||||||
import org.springframework.data.elasticsearch.core.TotalHitsRelation;
|
|
||||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||||
import org.springframework.data.elasticsearch.core.query.BaseQuery;
|
import org.springframework.data.elasticsearch.core.query.BaseQuery;
|
||||||
@ -42,6 +38,7 @@ import org.springframework.util.ClassUtils;
|
|||||||
* @author Rizwan Idrees
|
* @author Rizwan Idrees
|
||||||
* @author Mohsin Husen
|
* @author Mohsin Husen
|
||||||
* @author Peter-Josef Meisch
|
* @author Peter-Josef Meisch
|
||||||
|
* @author Haibo Liu
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public abstract class AbstractElasticsearchRepositoryQuery implements RepositoryQuery {
|
public abstract class AbstractElasticsearchRepositoryQuery implements RepositoryQuery {
|
||||||
@ -107,24 +104,13 @@ public abstract class AbstractElasticsearchRepositoryQuery implements Repository
|
|||||||
: PageRequest.of(0, DEFAULT_STREAM_BATCH_SIZE));
|
: PageRequest.of(0, DEFAULT_STREAM_BATCH_SIZE));
|
||||||
result = StreamUtils.createStreamFromIterator(elasticsearchOperations.searchForStream(query, clazz, index));
|
result = StreamUtils.createStreamFromIterator(elasticsearchOperations.searchForStream(query, clazz, index));
|
||||||
} else if (queryMethod.isCollectionQuery()) {
|
} else if (queryMethod.isCollectionQuery()) {
|
||||||
|
|
||||||
if (parameterAccessor.getPageable().isUnpaged()) {
|
if (parameterAccessor.getPageable().isUnpaged()) {
|
||||||
int itemCount = (int) elasticsearchOperations.count(query, clazz, index);
|
int itemCount = (int) elasticsearchOperations.count(query, clazz, index);
|
||||||
|
query.setPageable(PageRequest.of(0, Math.max(1, itemCount)));
|
||||||
if (itemCount == 0) {
|
|
||||||
result = new SearchHitsImpl<>(0, TotalHitsRelation.EQUAL_TO, Float.NaN, null,
|
|
||||||
query.getPointInTime() != null ? query.getPointInTime().id() : null, Collections.emptyList(), null, null);
|
|
||||||
} else {
|
|
||||||
query.setPageable(PageRequest.of(0, Math.max(1, itemCount)));
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
query.setPageable(parameterAccessor.getPageable());
|
query.setPageable(parameterAccessor.getPageable());
|
||||||
}
|
}
|
||||||
|
result = elasticsearchOperations.search(query, clazz, index);
|
||||||
if (result == null) {
|
|
||||||
result = elasticsearchOperations.search(query, clazz, index);
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
result = elasticsearchOperations.searchOne(query, clazz, index);
|
result = elasticsearchOperations.searchOne(query, clazz, index);
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2013-2023 the original author or authors.
|
* Copyright 2023 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -15,26 +15,35 @@
|
|||||||
*/
|
*/
|
||||||
package org.springframework.data.elasticsearch.client.elc;
|
package org.springframework.data.elasticsearch.client.elc;
|
||||||
|
|
||||||
|
import co.elastic.clients.elasticsearch._types.ShardFailure;
|
||||||
|
import co.elastic.clients.elasticsearch._types.ShardStatistics;
|
||||||
import co.elastic.clients.elasticsearch.core.search.HitsMetadata;
|
import co.elastic.clients.elasticsearch.core.search.HitsMetadata;
|
||||||
import co.elastic.clients.elasticsearch.core.search.Suggestion;
|
import co.elastic.clients.elasticsearch.core.search.Suggestion;
|
||||||
import co.elastic.clients.elasticsearch.core.search.TotalHitsRelation;
|
import co.elastic.clients.elasticsearch.core.search.TotalHitsRelation;
|
||||||
|
import co.elastic.clients.json.JsonData;
|
||||||
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
|
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import org.assertj.core.api.SoftAssertions;
|
import org.assertj.core.api.SoftAssertions;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.data.elasticsearch.ElasticsearchErrorCause;
|
||||||
|
import org.springframework.data.elasticsearch.core.SearchShardStatistics;
|
||||||
import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse;
|
import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for the factory class to create {@link SearchDocumentResponse} instances.
|
* Tests for the factory class to create {@link SearchDocumentResponse} instances.
|
||||||
*
|
*
|
||||||
* @author Sébastien Comeau
|
* @author Sébastien Comeau
|
||||||
|
* @author Haibo Liu
|
||||||
* @since 5.2
|
* @since 5.2
|
||||||
*/
|
*/
|
||||||
class SearchDocumentResponseBuilderUnitTests {
|
class SearchDocumentResponseBuilderUnitTests {
|
||||||
@ -73,7 +82,7 @@ class SearchDocumentResponseBuilderUnitTests {
|
|||||||
.build();
|
.build();
|
||||||
|
|
||||||
// act
|
// act
|
||||||
final var actual = SearchDocumentResponseBuilder.from(hitsMetadata, null, null, null, sortProperties, null,
|
final var actual = SearchDocumentResponseBuilder.from(hitsMetadata, null, null, null, null, sortProperties, null,
|
||||||
jsonpMapper);
|
jsonpMapper);
|
||||||
|
|
||||||
// assert
|
// assert
|
||||||
@ -108,4 +117,62 @@ class SearchDocumentResponseBuilderUnitTests {
|
|||||||
|
|
||||||
softly.assertAll();
|
softly.assertAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test // #2605
|
||||||
|
void shouldGetShardStatisticsInfo() {
|
||||||
|
// arrange
|
||||||
|
HitsMetadata<EntityAsMap> hitsMetadata = new HitsMetadata.Builder<EntityAsMap>()
|
||||||
|
.total(t -> t
|
||||||
|
.value(0)
|
||||||
|
.relation(TotalHitsRelation.Eq))
|
||||||
|
.hits(new ArrayList<>())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
ShardStatistics shards = new ShardStatistics.Builder()
|
||||||
|
.total(15)
|
||||||
|
.successful(14)
|
||||||
|
.skipped(0)
|
||||||
|
.failed(1)
|
||||||
|
.failures(List.of(
|
||||||
|
ShardFailure.of(sfb -> sfb
|
||||||
|
.index("test-index")
|
||||||
|
.node("test-node")
|
||||||
|
.shard(1)
|
||||||
|
.reason(rb -> rb
|
||||||
|
.reason("this is a mock failure in shards")
|
||||||
|
.causedBy(cbb ->
|
||||||
|
cbb.reason("inner reason")
|
||||||
|
.metadata(Map.of("hello", JsonData.of("world")))
|
||||||
|
)
|
||||||
|
.type("reason-type")
|
||||||
|
|
||||||
|
)
|
||||||
|
.status("fail")
|
||||||
|
)
|
||||||
|
))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// act
|
||||||
|
SearchDocumentResponse response = SearchDocumentResponseBuilder.from(hitsMetadata, shards, null, null,
|
||||||
|
null, null, null, jsonpMapper);
|
||||||
|
|
||||||
|
// assert
|
||||||
|
SearchShardStatistics shardStatistics = response.getSearchShardStatistics();
|
||||||
|
assertThat(shardStatistics).isNotNull();
|
||||||
|
assertThat(shardStatistics.getTotal()).isEqualTo(15);
|
||||||
|
assertThat(shardStatistics.getSuccessful()).isEqualTo(14);
|
||||||
|
assertThat(shardStatistics.getSkipped()).isEqualTo(0);
|
||||||
|
assertThat(shardStatistics.getFailed()).isEqualTo(1);
|
||||||
|
// assert failure
|
||||||
|
List<SearchShardStatistics.Failure> failures = shardStatistics.getFailures();
|
||||||
|
assertThat(failures.size()).isEqualTo(1);
|
||||||
|
assertThat(failures).extracting(SearchShardStatistics.Failure::getIndex).containsExactly("test-index");
|
||||||
|
assertThat(failures).extracting(SearchShardStatistics.Failure::getElasticsearchErrorCause)
|
||||||
|
.extracting(ElasticsearchErrorCause::getReason)
|
||||||
|
.containsExactly("this is a mock failure in shards");
|
||||||
|
assertThat(failures).extracting(SearchShardStatistics.Failure::getElasticsearchErrorCause)
|
||||||
|
.extracting(ElasticsearchErrorCause::getCausedBy)
|
||||||
|
.extracting(ElasticsearchErrorCause::getReason)
|
||||||
|
.containsExactly("inner reason");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,6 +32,7 @@ import org.springframework.data.util.CloseableIterator;
|
|||||||
/**
|
/**
|
||||||
* @author Roman Puchkovskiy
|
* @author Roman Puchkovskiy
|
||||||
* @author Peter-Josef Meisch
|
* @author Peter-Josef Meisch
|
||||||
|
* @author Haibo Liu
|
||||||
*/
|
*/
|
||||||
class SearchHitSupportTest {
|
class SearchHitSupportTest {
|
||||||
|
|
||||||
@ -65,7 +66,7 @@ class SearchHitSupportTest {
|
|||||||
hits.add(new SearchHit<>(null, null, null, 0, null, null, null, null, null, null, "five"));
|
hits.add(new SearchHit<>(null, null, null, 0, null, null, null, null, null, null, "five"));
|
||||||
|
|
||||||
SearchHits<String> originalSearchHits = new SearchHitsImpl<>(hits.size(), TotalHitsRelation.EQUAL_TO, 0, "scroll",
|
SearchHits<String> originalSearchHits = new SearchHitsImpl<>(hits.size(), TotalHitsRelation.EQUAL_TO, 0, "scroll",
|
||||||
null, hits, null, null);
|
null, hits, null, null, null);
|
||||||
|
|
||||||
SearchPage<String> searchPage = SearchHitSupport.searchPageFor(originalSearchHits, PageRequest.of(0, 3));
|
SearchPage<String> searchPage = SearchHitSupport.searchPageFor(originalSearchHits, PageRequest.of(0, 3));
|
||||||
SearchHits<String> searchHits = searchPage.getSearchHits();
|
SearchHits<String> searchHits = searchPage.getSearchHits();
|
||||||
|
@ -31,6 +31,7 @@ import org.springframework.data.util.StreamUtils;
|
|||||||
/**
|
/**
|
||||||
* @author Sascha Woo
|
* @author Sascha Woo
|
||||||
* @author Peter-Josef Meisch
|
* @author Peter-Josef Meisch
|
||||||
|
* @author Haibo Liu
|
||||||
*/
|
*/
|
||||||
public class StreamQueriesTest {
|
public class StreamQueriesTest {
|
||||||
|
|
||||||
@ -180,6 +181,6 @@ public class StreamQueriesTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private SearchScrollHits<String> newSearchScrollHits(List<SearchHit<String>> hits, String scrollId) {
|
private SearchScrollHits<String> newSearchScrollHits(List<SearchHit<String>> hits, String scrollId) {
|
||||||
return new SearchHitsImpl<>(hits.size(), TotalHitsRelation.EQUAL_TO, 0, scrollId, null, hits, null, null);
|
return new SearchHitsImpl<>(hits.size(), TotalHitsRelation.EQUAL_TO, 0, scrollId, null, hits, null, null, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user