Remove blocking code in SearchDocument processing.

Original Pull Request #2094
Closes #2025

(cherry picked from commit c1a1ea9724a9cd6590758895281261bc01a272ab)
This commit is contained in:
Peter-Josef Meisch 2022-02-20 12:26:19 +01:00
parent 823cfa919a
commit 834b10f578
No known key found for this signature in database
GPG Key ID: DE108246970C7708
6 changed files with 88 additions and 46 deletions

View File

@ -20,6 +20,7 @@ import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -109,7 +110,8 @@ public abstract class AbstractElasticsearchRestTransportTemplate extends Abstrac
List<SearchHits<T>> res = new ArrayList<>(queries.size()); List<SearchHits<T>> res = new ArrayList<>(queries.size());
int c = 0; int c = 0;
for (Query query : queries) { for (Query query : queries) {
res.add(callback.doWith(SearchDocumentResponse.from(items[c++].getResponse(), documentCallback::doWith))); res.add(
callback.doWith(SearchDocumentResponse.from(items[c++].getResponse(), getEntityCreator(documentCallback))));
} }
return res; return res;
} }
@ -142,7 +144,7 @@ public abstract class AbstractElasticsearchRestTransportTemplate extends Abstrac
index); index);
SearchResponse response = items[c++].getResponse(); SearchResponse response = items[c++].getResponse();
res.add(callback.doWith(SearchDocumentResponse.from(response, documentCallback::doWith))); res.add(callback.doWith(SearchDocumentResponse.from(response, getEntityCreator(documentCallback))));
} }
return res; return res;
} }
@ -175,7 +177,7 @@ public abstract class AbstractElasticsearchRestTransportTemplate extends Abstrac
index); index);
SearchResponse response = items[c++].getResponse(); SearchResponse response = items[c++].getResponse();
res.add(callback.doWith(SearchDocumentResponse.from(response, documentCallback::doWith))); res.add(callback.doWith(SearchDocumentResponse.from(response, getEntityCreator(documentCallback))));
} }
return res; return res;
} }
@ -215,5 +217,9 @@ public abstract class AbstractElasticsearchRestTransportTemplate extends Abstrac
return suggest(suggestion, getIndexCoordinatesFor(clazz)); return suggest(suggestion, getIndexCoordinatesFor(clazz));
} }
protected <T> SearchDocumentResponse.EntityCreator<T> getEntityCreator(ReadDocumentCallback<T> documentCallback) {
return searchDocument -> CompletableFuture.completedFuture(documentCallback.doWith(searchDocument));
}
// endregion // endregion
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2021 the original author or authors. * Copyright 2013-2022 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.
@ -319,7 +319,7 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchRestTranspor
ReadDocumentCallback<T> documentCallback = new ReadDocumentCallback<T>(elasticsearchConverter, clazz, index); ReadDocumentCallback<T> documentCallback = new ReadDocumentCallback<T>(elasticsearchConverter, clazz, index);
SearchDocumentResponseCallback<SearchHits<T>> callback = new ReadSearchDocumentResponseCallback<>(clazz, index); SearchDocumentResponseCallback<SearchHits<T>> callback = new ReadSearchDocumentResponseCallback<>(clazz, index);
return callback.doWith(SearchDocumentResponse.from(response, documentCallback::doWith)); return callback.doWith(SearchDocumentResponse.from(response, getEntityCreator(documentCallback)));
} }
@Override @Override
@ -336,7 +336,7 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchRestTranspor
ReadDocumentCallback<T> documentCallback = new ReadDocumentCallback<T>(elasticsearchConverter, clazz, index); ReadDocumentCallback<T> documentCallback = new ReadDocumentCallback<T>(elasticsearchConverter, clazz, index);
SearchDocumentResponseCallback<SearchScrollHits<T>> callback = new ReadSearchScrollDocumentResponseCallback<>(clazz, SearchDocumentResponseCallback<SearchScrollHits<T>> callback = new ReadSearchScrollDocumentResponseCallback<>(clazz,
index); index);
return callback.doWith(SearchDocumentResponse.from(response, documentCallback::doWith)); return callback.doWith(SearchDocumentResponse.from(response, getEntityCreator(documentCallback)));
} }
@Override @Override
@ -351,7 +351,7 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchRestTranspor
ReadDocumentCallback<T> documentCallback = new ReadDocumentCallback<T>(elasticsearchConverter, clazz, index); ReadDocumentCallback<T> documentCallback = new ReadDocumentCallback<T>(elasticsearchConverter, clazz, index);
SearchDocumentResponseCallback<SearchScrollHits<T>> callback = new ReadSearchScrollDocumentResponseCallback<>(clazz, SearchDocumentResponseCallback<SearchScrollHits<T>> callback = new ReadSearchScrollDocumentResponseCallback<>(clazz,
index); index);
return callback.doWith(SearchDocumentResponse.from(response, documentCallback::doWith)); return callback.doWith(SearchDocumentResponse.from(response, getEntityCreator(documentCallback)));
} }
@Override @Override
@ -378,8 +378,8 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchRestTranspor
Assert.isTrue(items.length == request.requests().size(), "Response should has same length with queries"); Assert.isTrue(items.length == request.requests().size(), "Response should has same length with queries");
return items; return items;
} }
// endregion
// endregion
// region ClientCallback // region ClientCallback
/** /**
* Callback interface to be used with {@link #execute(ClientCallback)} for operating directly on * Callback interface to be used with {@link #execute(ClientCallback)} for operating directly on

View File

@ -354,7 +354,7 @@ public class ElasticsearchTemplate extends AbstractElasticsearchRestTransportTem
ReadDocumentCallback<T> documentCallback = new ReadDocumentCallback<T>(elasticsearchConverter, clazz, index); ReadDocumentCallback<T> documentCallback = new ReadDocumentCallback<T>(elasticsearchConverter, clazz, index);
SearchDocumentResponseCallback<SearchHits<T>> callback = new ReadSearchDocumentResponseCallback<>(clazz, index); SearchDocumentResponseCallback<SearchHits<T>> callback = new ReadSearchDocumentResponseCallback<>(clazz, index);
return callback.doWith(SearchDocumentResponse.from(response, documentCallback::doWith)); return callback.doWith(SearchDocumentResponse.from(response, getEntityCreator(documentCallback)));
} }
@Override @Override
@ -372,7 +372,7 @@ public class ElasticsearchTemplate extends AbstractElasticsearchRestTransportTem
ReadDocumentCallback<T> documentCallback = new ReadDocumentCallback<T>(elasticsearchConverter, clazz, index); ReadDocumentCallback<T> documentCallback = new ReadDocumentCallback<T>(elasticsearchConverter, clazz, index);
SearchDocumentResponseCallback<SearchScrollHits<T>> callback = new ReadSearchScrollDocumentResponseCallback<>(clazz, SearchDocumentResponseCallback<SearchScrollHits<T>> callback = new ReadSearchScrollDocumentResponseCallback<>(clazz,
index); index);
return callback.doWith(SearchDocumentResponse.from(response, documentCallback::doWith)); return callback.doWith(SearchDocumentResponse.from(response, getEntityCreator(documentCallback)));
} }
@Override @Override
@ -389,7 +389,7 @@ public class ElasticsearchTemplate extends AbstractElasticsearchRestTransportTem
ReadDocumentCallback<T> documentCallback = new ReadDocumentCallback<T>(elasticsearchConverter, clazz, index); ReadDocumentCallback<T> documentCallback = new ReadDocumentCallback<T>(elasticsearchConverter, clazz, index);
SearchDocumentResponseCallback<SearchScrollHits<T>> callback = new ReadSearchScrollDocumentResponseCallback<>(clazz, SearchDocumentResponseCallback<SearchScrollHits<T>> callback = new ReadSearchScrollDocumentResponseCallback<>(clazz,
index); index);
return callback.doWith(SearchDocumentResponse.from(response, documentCallback::doWith)); return callback.doWith(SearchDocumentResponse.from(response, getEntityCreator(documentCallback)));
} }
@Override @Override

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2018-2021 the original author or authors. * Copyright 2018-2022 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.
@ -23,7 +23,6 @@ import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.elasticsearch.Version; import org.elasticsearch.Version;
@ -771,15 +770,18 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
}); });
} }
private Mono<SearchDocumentResponse> doFindForResponse(Query query, Class<?> clazz, IndexCoordinates index) { private <T> Mono<SearchDocumentResponse> doFindForResponse(Query query, Class<?> clazz, IndexCoordinates index) {
return Mono.defer(() -> { return Mono.defer(() -> {
SearchRequest request = requestFactory.searchRequest(query, clazz, index); SearchRequest request = requestFactory.searchRequest(query, clazz, index);
request = prepareSearchRequest(request, false); request = prepareSearchRequest(request, false);
SearchDocumentCallback<?> documentCallback = new ReadSearchDocumentCallback<>(clazz, index); SearchDocumentCallback<?> documentCallback = new ReadSearchDocumentCallback<>(clazz, index);
// noinspection unchecked
SearchDocumentResponse.EntityCreator<T> entityCreator = searchDocument -> ((Mono<T>) documentCallback
.toEntity(searchDocument)).toFuture();
return doFindForResponse(request, searchDocument -> documentCallback.toEntity(searchDocument).block()); return doFindForResponse(request, entityCreator);
}); });
} }
@ -896,19 +898,18 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
* Customization hook on the actual execution result {@link Mono}. <br /> * Customization hook on the actual execution result {@link Mono}. <br />
* *
* @param request the already prepared {@link SearchRequest} ready to be executed. * @param request the already prepared {@link SearchRequest} ready to be executed.
* @param suggestEntityCreator * @param entityCreator
* @return a {@link Mono} emitting the result of the operation converted to s {@link SearchDocumentResponse}. * @return a {@link Mono} emitting the result of the operation converted to s {@link SearchDocumentResponse}.
*/ */
protected Mono<SearchDocumentResponse> doFindForResponse(SearchRequest request, protected <T> Mono<SearchDocumentResponse> doFindForResponse(SearchRequest request,
Function<SearchDocument, ? extends Object> suggestEntityCreator) { SearchDocumentResponse.EntityCreator<T> entityCreator) {
if (QUERY_LOGGER.isDebugEnabled()) { if (QUERY_LOGGER.isDebugEnabled()) {
QUERY_LOGGER.debug("Executing doFindForResponse: {}", request); QUERY_LOGGER.debug("Executing doFindForResponse: {}", request);
} }
return Mono.from(execute(client1 -> client1.searchForResponse(request))).map(searchResponse -> { return Mono.from(execute(client -> client.searchForResponse(request)))
return SearchDocumentResponse.from(searchResponse, suggestEntityCreator); .map(searchResponse -> SearchDocumentResponse.from(searchResponse, entityCreator));
});
} }
/** /**

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2019-2021 the original author or authors. * Copyright 2019-2022 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.
@ -17,8 +17,11 @@ package org.springframework.data.elasticsearch.core.document;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function; import java.util.function.Function;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.lucene.search.TotalHits; import org.apache.lucene.search.TotalHits;
import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.text.Text; import org.elasticsearch.common.text.Text;
@ -38,13 +41,15 @@ import org.springframework.util.Assert;
/** /**
* This represents the complete search response from Elasticsearch, including the returned documents. Instances must be * This represents the complete search response from Elasticsearch, including the returned documents. Instances must be
* created with the {@link #from(SearchResponse,Function)} method. * created with the {@link #from(SearchResponse, EntityCreator)} method.
* *
* @author Peter-Josef Meisch * @author Peter-Josef Meisch
* @since 4.0 * @since 4.0
*/ */
public class SearchDocumentResponse { public class SearchDocumentResponse {
private static final Log LOGGER = LogFactory.getLog(SearchDocumentResponse.class);
private final long totalHits; private final long totalHits;
private final String totalHitsRelation; private final String totalHitsRelation;
private final float maxScore; private final float maxScore;
@ -98,12 +103,11 @@ public class SearchDocumentResponse {
* creates a SearchDocumentResponse from the {@link SearchResponse} * creates a SearchDocumentResponse from the {@link SearchResponse}
* *
* @param searchResponse must not be {@literal null} * @param searchResponse must not be {@literal null}
* @param suggestEntityCreator function to create an entity from a {@link SearchDocument} * @param entityCreator function to create an entity from a {@link SearchDocument}
* @param <T> entity type * @param <T> entity type
* @return the SearchDocumentResponse * @return the SearchDocumentResponse
*/ */
public static <T> SearchDocumentResponse from(SearchResponse searchResponse, public static <T> SearchDocumentResponse from(SearchResponse searchResponse, EntityCreator<T> entityCreator) {
Function<SearchDocument, T> suggestEntityCreator) {
Assert.notNull(searchResponse, "searchResponse must not be null"); Assert.notNull(searchResponse, "searchResponse must not be null");
@ -112,7 +116,7 @@ public class SearchDocumentResponse {
Aggregations aggregations = searchResponse.getAggregations(); Aggregations aggregations = searchResponse.getAggregations();
org.elasticsearch.search.suggest.Suggest suggest = searchResponse.getSuggest(); org.elasticsearch.search.suggest.Suggest suggest = searchResponse.getSuggest();
return from(searchHits, scrollId, aggregations, suggest, suggestEntityCreator); return from(searchHits, scrollId, aggregations, suggest, entityCreator);
} }
/** /**
@ -122,14 +126,14 @@ public class SearchDocumentResponse {
* @param scrollId scrollId * @param scrollId scrollId
* @param aggregations aggregations * @param aggregations aggregations
* @param suggestES the suggestion response from Elasticsearch * @param suggestES the suggestion response from Elasticsearch
* @param suggestEntityCreator function to create an entity from a {@link SearchDocument} * @param entityCreator function to create an entity from a {@link SearchDocument}
* @param <T> entity type * @param <T> entity type
* @return the {@link SearchDocumentResponse} * @return the {@link SearchDocumentResponse}
* @since 4.3 * @since 4.3
*/ */
public static <T> SearchDocumentResponse from(SearchHits searchHits, @Nullable String scrollId, public static <T> SearchDocumentResponse from(SearchHits searchHits, @Nullable String scrollId,
@Nullable Aggregations aggregations, @Nullable org.elasticsearch.search.suggest.Suggest suggestES, @Nullable Aggregations aggregations, @Nullable org.elasticsearch.search.suggest.Suggest suggestES,
Function<SearchDocument, T> suggestEntityCreator) { EntityCreator<T> entityCreator) {
TotalHits responseTotalHits = searchHits.getTotalHits(); TotalHits responseTotalHits = searchHits.getTotalHits();
@ -153,14 +157,14 @@ public class SearchDocumentResponse {
} }
} }
Suggest suggest = suggestFrom(suggestES, suggestEntityCreator); Suggest suggest = suggestFrom(suggestES, entityCreator);
return new SearchDocumentResponse(totalHits, totalHitsRelation, maxScore, scrollId, searchDocuments, aggregations, return new SearchDocumentResponse(totalHits, totalHitsRelation, maxScore, scrollId, searchDocuments, aggregations,
suggest); suggest);
} }
@Nullable @Nullable
private static <T> Suggest suggestFrom(@Nullable org.elasticsearch.search.suggest.Suggest suggestES, private static <T> Suggest suggestFrom(@Nullable org.elasticsearch.search.suggest.Suggest suggestES,
Function<SearchDocument, T> entityCreator) { EntityCreator<T> entityCreator) {
if (suggestES == null) { if (suggestES == null) {
return null; return null;
@ -219,7 +223,19 @@ public class SearchDocumentResponse {
List<CompletionSuggestion.Entry.Option<T>> options = new ArrayList<>(); List<CompletionSuggestion.Entry.Option<T>> options = new ArrayList<>();
for (org.elasticsearch.search.suggest.completion.CompletionSuggestion.Entry.Option optionES : entryES) { for (org.elasticsearch.search.suggest.completion.CompletionSuggestion.Entry.Option optionES : entryES) {
SearchDocument searchDocument = optionES.getHit() != null ? DocumentAdapters.from(optionES.getHit()) : null; SearchDocument searchDocument = optionES.getHit() != null ? DocumentAdapters.from(optionES.getHit()) : null;
T hitEntity = searchDocument != null ? entityCreator.apply(searchDocument) : null;
T hitEntity = null;
if (searchDocument != null) {
try {
hitEntity = entityCreator.apply(searchDocument).get();
} catch (Exception e) {
if (LOGGER.isWarnEnabled()) {
LOGGER.warn("Error creating entity from SearchDocument");
}
}
}
options.add(new CompletionSuggestion.Entry.Option<T>(textToString(optionES.getText()), options.add(new CompletionSuggestion.Entry.Option<T>(textToString(optionES.getText()),
textToString(optionES.getHighlighted()), optionES.getScore(), optionES.collateMatch(), textToString(optionES.getHighlighted()), optionES.getScore(), optionES.collateMatch(),
optionES.getContexts(), scoreDocFrom(optionES.getDoc()), searchDocument, hitEntity)); optionES.getContexts(), scoreDocFrom(optionES.getDoc()), searchDocument, hitEntity));
@ -254,4 +270,14 @@ public class SearchDocumentResponse {
private static String textToString(@Nullable Text text) { private static String textToString(@Nullable Text text) {
return text != null ? text.string() : ""; return text != null ? text.string() : "";
} }
/**
* 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.
*
* @param <T> the entity type
*/
@FunctionalInterface
public interface EntityCreator<T> extends Function<SearchDocument, CompletableFuture<T>> {}
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2021 the original author or authors. * Copyright 2021-2022 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.
@ -17,6 +17,7 @@ package org.springframework.data.elasticsearch.core.suggest;
import static org.assertj.core.api.Assertions.*; import static org.assertj.core.api.Assertions.*;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier; import reactor.test.StepVerifier;
import java.util.ArrayList; import java.util.ArrayList;
@ -39,8 +40,8 @@ import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations; import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.IndexQuery; import org.springframework.data.elasticsearch.core.query.IndexQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.core.suggest.response.CompletionSuggestion; import org.springframework.data.elasticsearch.core.suggest.response.CompletionSuggestion;
import org.springframework.data.elasticsearch.core.suggest.response.Suggest; import org.springframework.data.elasticsearch.core.suggest.response.Suggest;
import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchRestTemplateConfiguration; import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchRestTemplateConfiguration;
@ -86,13 +87,11 @@ public class ReactiveElasticsearchTemplateSuggestIntegrationTests {
@DisplayName("should find suggestions for given prefix completion") @DisplayName("should find suggestions for given prefix completion")
void shouldFindSuggestionsForGivenPrefixCompletion() { void shouldFindSuggestionsForGivenPrefixCompletion() {
loadCompletionObjectEntities(); loadCompletionObjectEntities() //
.flatMap(unused -> {
NativeSearchQuery query = new NativeSearchQueryBuilder().withSuggestBuilder(new SuggestBuilder() Query query = getSuggestQuery("test-suggest", "suggest", "m");
.addSuggestion("test-suggest", SuggestBuilders.completionSuggestion("suggest").prefix("m", Fuzziness.AUTO))) return operations.suggest(query, CompletionEntity.class);
.build(); }) //
operations.suggest(query, CompletionEntity.class) //
.as(StepVerifier::create) // .as(StepVerifier::create) //
.assertNext(suggest -> { .assertNext(suggest -> {
Suggest.Suggestion<? extends Suggest.Suggestion.Entry<? extends Suggest.Suggestion.Entry.Option>> suggestion = suggest Suggest.Suggestion<? extends Suggest.Suggestion.Entry<? extends Suggest.Suggestion.Entry.Option>> suggestion = suggest
@ -105,13 +104,21 @@ public class ReactiveElasticsearchTemplateSuggestIntegrationTests {
assertThat(options).hasSize(2); assertThat(options).hasSize(2);
assertThat(options.get(0).getText()).isIn("Marchand", "Mohsin"); assertThat(options.get(0).getText()).isIn("Marchand", "Mohsin");
assertThat(options.get(1).getText()).isIn("Marchand", "Mohsin"); assertThat(options.get(1).getText()).isIn("Marchand", "Mohsin");
}) // }) //
.verifyComplete(); .verifyComplete();
} }
protected Query getSuggestQuery(String suggestionName, String fieldName, String prefix) {
return new NativeSearchQueryBuilder() //
.withSuggestBuilder(new SuggestBuilder() //
.addSuggestion(suggestionName, //
SuggestBuilders.completionSuggestion(fieldName) //
.prefix(prefix, Fuzziness.AUTO))) //
.build(); //
}
// region helper functions // region helper functions
private void loadCompletionObjectEntities() { private Mono<CompletionEntity> loadCompletionObjectEntities() {
CompletionEntity rizwan_idrees = new CompletionEntityBuilder("1").name("Rizwan Idrees") CompletionEntity rizwan_idrees = new CompletionEntityBuilder("1").name("Rizwan Idrees")
.suggest(new String[] { "Rizwan Idrees" }).build(); .suggest(new String[] { "Rizwan Idrees" }).build();
@ -124,7 +131,7 @@ public class ReactiveElasticsearchTemplateSuggestIntegrationTests {
List<CompletionEntity> entities = new ArrayList<>( List<CompletionEntity> entities = new ArrayList<>(
Arrays.asList(rizwan_idrees, franck_marchand, mohsin_husen, artur_konczak)); Arrays.asList(rizwan_idrees, franck_marchand, mohsin_husen, artur_konczak));
IndexCoordinates index = IndexCoordinates.of(indexNameProvider.indexName()); IndexCoordinates index = IndexCoordinates.of(indexNameProvider.indexName());
operations.saveAll(entities, index).blockLast(); return operations.saveAll(entities, index).last();
} }
// endregion // endregion
@ -132,11 +139,13 @@ public class ReactiveElasticsearchTemplateSuggestIntegrationTests {
@Document(indexName = "#{@indexNameProvider.indexName()}") @Document(indexName = "#{@indexNameProvider.indexName()}")
static class CompletionEntity { static class CompletionEntity {
@Nullable @Id private String id; @Nullable
@Id private String id;
@Nullable private String name; @Nullable private String name;
@Nullable @CompletionField(maxInputLength = 100) private Completion suggest; @Nullable
@CompletionField(maxInputLength = 100) private Completion suggest;
private CompletionEntity() {} private CompletionEntity() {}