mirror of
https://github.com/spring-projects/spring-data-elasticsearch.git
synced 2025-06-22 03:52:10 +00:00
Support multi search template API.
Original Pull Request #2807 Closes #2704
This commit is contained in:
parent
260dadd4d6
commit
1554c3c94f
@ -29,6 +29,7 @@ import co.elastic.clients.transport.Version;
|
||||
import java.io.IOException;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
@ -72,6 +73,7 @@ import org.springframework.util.Assert;
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Hamid Rahimi
|
||||
* @author Illia Ulianov
|
||||
* @author Haibo Liu
|
||||
* @since 4.4
|
||||
*/
|
||||
public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
|
||||
@ -437,13 +439,10 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
|
||||
Assert.notNull(queries, "queries must not be null");
|
||||
Assert.notNull(clazz, "clazz must not be null");
|
||||
|
||||
List<MultiSearchQueryParameter> multiSearchQueryParameters = new ArrayList<>(queries.size());
|
||||
for (Query query : queries) {
|
||||
multiSearchQueryParameters.add(new MultiSearchQueryParameter(query, clazz, getIndexCoordinatesFor(clazz)));
|
||||
}
|
||||
|
||||
int size = queries.size();
|
||||
// noinspection unchecked
|
||||
return doMultiSearch(multiSearchQueryParameters).stream().map(searchHits -> (SearchHits<T>) searchHits)
|
||||
return multiSearch(queries, Collections.nCopies(size, clazz), Collections.nCopies(size, index))
|
||||
.stream().map(searchHits -> (SearchHits<T>) searchHits)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@ -454,14 +453,7 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
|
||||
Assert.notNull(classes, "classes must not be null");
|
||||
Assert.isTrue(queries.size() == classes.size(), "queries and classes must have the same size");
|
||||
|
||||
List<MultiSearchQueryParameter> multiSearchQueryParameters = new ArrayList<>(queries.size());
|
||||
Iterator<Class<?>> it = classes.iterator();
|
||||
for (Query query : queries) {
|
||||
Class<?> clazz = it.next();
|
||||
multiSearchQueryParameters.add(new MultiSearchQueryParameter(query, clazz, getIndexCoordinatesFor(clazz)));
|
||||
}
|
||||
|
||||
return doMultiSearch(multiSearchQueryParameters);
|
||||
return multiSearch(queries, classes, classes.stream().map(this::getIndexCoordinatesFor).toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -473,14 +465,7 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
|
||||
Assert.notNull(index, "index must not be null");
|
||||
Assert.isTrue(queries.size() == classes.size(), "queries and classes must have the same size");
|
||||
|
||||
List<MultiSearchQueryParameter> multiSearchQueryParameters = new ArrayList<>(queries.size());
|
||||
Iterator<Class<?>> it = classes.iterator();
|
||||
for (Query query : queries) {
|
||||
Class<?> clazz = it.next();
|
||||
multiSearchQueryParameters.add(new MultiSearchQueryParameter(query, clazz, index));
|
||||
}
|
||||
|
||||
return doMultiSearch(multiSearchQueryParameters);
|
||||
return multiSearch(queries, classes, Collections.nCopies(queries.size(), index));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -497,16 +482,49 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
|
||||
Iterator<Class<?>> it = classes.iterator();
|
||||
Iterator<IndexCoordinates> indexesIt = indexes.iterator();
|
||||
|
||||
Assert.isTrue(!queries.isEmpty(), "queries should have at least 1 query");
|
||||
boolean isSearchTemplateQuery = queries.get(0) instanceof SearchTemplateQuery;
|
||||
|
||||
for (Query query : queries) {
|
||||
Assert.isTrue((query instanceof SearchTemplateQuery) == isSearchTemplateQuery,
|
||||
"SearchTemplateQuery can't be mixed with other types of query in multiple search");
|
||||
|
||||
Class<?> clazz = it.next();
|
||||
IndexCoordinates index = indexesIt.next();
|
||||
multiSearchQueryParameters.add(new MultiSearchQueryParameter(query, clazz, index));
|
||||
}
|
||||
|
||||
return doMultiSearch(multiSearchQueryParameters);
|
||||
return multiSearch(multiSearchQueryParameters, isSearchTemplateQuery);
|
||||
}
|
||||
|
||||
private List<SearchHits<?>> multiSearch(List<MultiSearchQueryParameter> multiSearchQueryParameters,
|
||||
boolean isSearchTemplateQuery) {
|
||||
return isSearchTemplateQuery ?
|
||||
doMultiTemplateSearch(multiSearchQueryParameters.stream()
|
||||
.map(p -> new MultiSearchTemplateQueryParameter((SearchTemplateQuery) p.query, p.clazz, p.index))
|
||||
.toList())
|
||||
: doMultiSearch(multiSearchQueryParameters);
|
||||
}
|
||||
|
||||
private List<SearchHits<?>> doMultiTemplateSearch(List<MultiSearchTemplateQueryParameter> mSearchTemplateQueryParameters) {
|
||||
MsearchTemplateRequest request = requestConverter.searchMsearchTemplateRequest(mSearchTemplateQueryParameters,
|
||||
routingResolver.getRouting());
|
||||
|
||||
MsearchTemplateResponse<EntityAsMap> response = execute(client -> client.msearchTemplate(request, EntityAsMap.class));
|
||||
List<MultiSearchResponseItem<EntityAsMap>> responseItems = response.responses();
|
||||
|
||||
Assert.isTrue(mSearchTemplateQueryParameters.size() == responseItems.size(),
|
||||
"number of response items does not match number of requests");
|
||||
|
||||
int size = mSearchTemplateQueryParameters.size();
|
||||
List<Class<?>> classes = mSearchTemplateQueryParameters
|
||||
.stream().map(MultiSearchTemplateQueryParameter::clazz).collect(Collectors.toList());
|
||||
List<IndexCoordinates> indices = mSearchTemplateQueryParameters
|
||||
.stream().map(MultiSearchTemplateQueryParameter::index).collect(Collectors.toList());
|
||||
|
||||
return getSearchHitsFromMsearchResponse(size, classes, indices, responseItems);
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
private List<SearchHits<?>> doMultiSearch(List<MultiSearchQueryParameter> multiSearchQueryParameters) {
|
||||
|
||||
MsearchRequest request = requestConverter.searchMsearchRequest(multiSearchQueryParameters,
|
||||
@ -518,22 +536,37 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
|
||||
Assert.isTrue(multiSearchQueryParameters.size() == responseItems.size(),
|
||||
"number of response items does not match number of requests");
|
||||
|
||||
List<SearchHits<?>> searchHitsList = new ArrayList<>(multiSearchQueryParameters.size());
|
||||
int size = multiSearchQueryParameters.size();
|
||||
List<Class<?>> classes = multiSearchQueryParameters
|
||||
.stream().map(MultiSearchQueryParameter::clazz).collect(Collectors.toList());
|
||||
List<IndexCoordinates> indices = multiSearchQueryParameters
|
||||
.stream().map(MultiSearchQueryParameter::index).collect(Collectors.toList());
|
||||
|
||||
Iterator<MultiSearchQueryParameter> queryIterator = multiSearchQueryParameters.iterator();
|
||||
return getSearchHitsFromMsearchResponse(size, classes, indices, responseItems);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link MsearchResponse} and {@link MsearchTemplateResponse} share the same {@link MultiSearchResponseItem}
|
||||
*/
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
private List<SearchHits<?>> getSearchHitsFromMsearchResponse(int size, List<Class<?>> classes,
|
||||
List<IndexCoordinates> indices, List<MultiSearchResponseItem<EntityAsMap>> responseItems) {
|
||||
List<SearchHits<?>> searchHitsList = new ArrayList<>(size);
|
||||
Iterator<Class<?>> clazzIter = classes.iterator();
|
||||
Iterator<IndexCoordinates> indexIter = indices.iterator();
|
||||
Iterator<MultiSearchResponseItem<EntityAsMap>> responseIterator = responseItems.iterator();
|
||||
|
||||
while (queryIterator.hasNext()) {
|
||||
MultiSearchQueryParameter queryParameter = queryIterator.next();
|
||||
while (clazzIter.hasNext() && indexIter.hasNext()) {
|
||||
MultiSearchResponseItem<EntityAsMap> responseItem = responseIterator.next();
|
||||
|
||||
if (responseItem.isResult()) {
|
||||
|
||||
Class clazz = queryParameter.clazz;
|
||||
Class clazz = clazzIter.next();
|
||||
IndexCoordinates index = indexIter.next();
|
||||
ReadDocumentCallback<?> documentCallback = new ReadDocumentCallback<>(elasticsearchConverter, clazz,
|
||||
queryParameter.index);
|
||||
index);
|
||||
SearchDocumentResponseCallback<SearchHits<?>> callback = new ReadSearchDocumentResponseCallback<>(clazz,
|
||||
queryParameter.index);
|
||||
index);
|
||||
|
||||
SearchHits<?> searchHits = callback.doWith(
|
||||
SearchDocumentResponseBuilder.from(responseItem.result(), getEntityCreator(documentCallback), jsonpMapper));
|
||||
@ -541,8 +574,8 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
|
||||
searchHitsList.add(searchHits);
|
||||
} else {
|
||||
if (LOGGER.isWarnEnabled()) {
|
||||
LOGGER
|
||||
.warn(String.format("multisearch responsecontains failure: {}", responseItem.failure().error().reason()));
|
||||
LOGGER.warn(String.format("multisearch response contains failure: %s",
|
||||
responseItem.failure().error().reason()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -556,6 +589,12 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
|
||||
record MultiSearchQueryParameter(Query query, Class<?> clazz, IndexCoordinates index) {
|
||||
}
|
||||
|
||||
/**
|
||||
* value class combining the information needed for a single query in a template multisearch request.
|
||||
*/
|
||||
record MultiSearchTemplateQueryParameter(SearchTemplateQuery query, Class<?> clazz, IndexCoordinates index) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String openPointInTime(IndexCoordinates index, Duration keepAlive, Boolean ignoreUnavailable) {
|
||||
|
||||
|
@ -44,6 +44,7 @@ import co.elastic.clients.elasticsearch.core.bulk.IndexOperation;
|
||||
import co.elastic.clients.elasticsearch.core.bulk.UpdateOperation;
|
||||
import co.elastic.clients.elasticsearch.core.mget.MultiGetOperation;
|
||||
import co.elastic.clients.elasticsearch.core.msearch.MultisearchBody;
|
||||
import co.elastic.clients.elasticsearch.core.msearch.MultisearchHeader;
|
||||
import co.elastic.clients.elasticsearch.core.search.Highlight;
|
||||
import co.elastic.clients.elasticsearch.core.search.Rescore;
|
||||
import co.elastic.clients.elasticsearch.core.search.SourceConfig;
|
||||
@ -54,6 +55,7 @@ import co.elastic.clients.elasticsearch.indices.update_aliases.Action;
|
||||
import co.elastic.clients.json.JsonData;
|
||||
import co.elastic.clients.json.JsonpDeserializer;
|
||||
import co.elastic.clients.json.JsonpMapper;
|
||||
import co.elastic.clients.util.ObjectBuilder;
|
||||
import jakarta.json.stream.JsonParser;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
@ -66,6 +68,7 @@ import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@ -397,10 +400,7 @@ class RequestConverter {
|
||||
.order(putTemplateRequest.getOrder());
|
||||
|
||||
if (putTemplateRequest.getSettings() != null) {
|
||||
Function<Map.Entry<String, Object>, String> keyMapper = Map.Entry::getKey;
|
||||
Function<Map.Entry<String, Object>, JsonData> valueMapper = entry -> JsonData.of(entry.getValue(), jsonpMapper);
|
||||
Map<String, JsonData> settings = putTemplateRequest.getSettings().entrySet().stream()
|
||||
.collect(Collectors.toMap(keyMapper, valueMapper));
|
||||
Map<String, JsonData> settings = getTemplateParams(putTemplateRequest.getSettings().entrySet());
|
||||
builder.settings(settings);
|
||||
}
|
||||
|
||||
@ -1146,6 +1146,36 @@ class RequestConverter {
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public MsearchTemplateRequest searchMsearchTemplateRequest(
|
||||
List<ElasticsearchTemplate.MultiSearchTemplateQueryParameter> multiSearchTemplateQueryParameters,
|
||||
@Nullable String routing) {
|
||||
|
||||
// basically the same stuff as in template search
|
||||
return MsearchTemplateRequest.of(mtrb -> {
|
||||
multiSearchTemplateQueryParameters.forEach(param -> {
|
||||
var query = param.query();
|
||||
mtrb.searchTemplates(stb -> stb
|
||||
.header(msearchHeaderBuilder(query, param.index(), routing))
|
||||
.body(bb -> {
|
||||
bb //
|
||||
.explain(query.getExplain()) //
|
||||
.id(query.getId()) //
|
||||
.source(query.getSource()) //
|
||||
;
|
||||
|
||||
if (!CollectionUtils.isEmpty(query.getParams())) {
|
||||
Map<String, JsonData> params = getTemplateParams(query.getParams().entrySet());
|
||||
bb.params(params);
|
||||
}
|
||||
|
||||
return bb;
|
||||
})
|
||||
);
|
||||
});
|
||||
return mtrb;
|
||||
});
|
||||
}
|
||||
|
||||
public MsearchRequest searchMsearchRequest(
|
||||
List<ElasticsearchTemplate.MultiSearchQueryParameter> multiSearchQueryParameters, @Nullable String routing) {
|
||||
|
||||
@ -1157,28 +1187,7 @@ class RequestConverter {
|
||||
|
||||
var query = param.query();
|
||||
mrb.searches(sb -> sb //
|
||||
.header(h -> {
|
||||
var searchType = (query instanceof NativeQuery nativeQuery && nativeQuery.getKnnQuery() != null) ? null
|
||||
: searchType(query.getSearchType());
|
||||
|
||||
h //
|
||||
.index(Arrays.asList(param.index().getIndexNames())) //
|
||||
.searchType(searchType) //
|
||||
.requestCache(query.getRequestCache()) //
|
||||
;
|
||||
|
||||
if (StringUtils.hasText(query.getRoute())) {
|
||||
h.routing(query.getRoute());
|
||||
} else if (StringUtils.hasText(routing)) {
|
||||
h.routing(routing);
|
||||
}
|
||||
|
||||
if (query.getPreference() != null) {
|
||||
h.preference(query.getPreference());
|
||||
}
|
||||
|
||||
return h;
|
||||
}) //
|
||||
.header(msearchHeaderBuilder(query, param.index(), routing)) //
|
||||
.body(bb -> {
|
||||
bb //
|
||||
.query(getQuery(query, param.clazz()))//
|
||||
@ -1284,6 +1293,35 @@ class RequestConverter {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link MsearchRequest} and {@link MsearchTemplateRequest} share the same {@link MultisearchHeader}
|
||||
*/
|
||||
private Function<MultisearchHeader.Builder, ObjectBuilder<MultisearchHeader>> msearchHeaderBuilder(Query query,
|
||||
IndexCoordinates index, @Nullable String routing) {
|
||||
return h -> {
|
||||
var searchType = (query instanceof NativeQuery nativeQuery && nativeQuery.getKnnQuery() != null) ? null
|
||||
: searchType(query.getSearchType());
|
||||
|
||||
h //
|
||||
.index(Arrays.asList(index.getIndexNames())) //
|
||||
.searchType(searchType) //
|
||||
.requestCache(query.getRequestCache()) //
|
||||
;
|
||||
|
||||
if (StringUtils.hasText(query.getRoute())) {
|
||||
h.routing(query.getRoute());
|
||||
} else if (StringUtils.hasText(routing)) {
|
||||
h.routing(routing);
|
||||
}
|
||||
|
||||
if (query.getPreference() != null) {
|
||||
h.preference(query.getPreference());
|
||||
}
|
||||
|
||||
return h;
|
||||
};
|
||||
}
|
||||
|
||||
private <T> void prepareSearchRequest(Query query, @Nullable String routing, @Nullable Class<T> clazz,
|
||||
IndexCoordinates indexCoordinates, SearchRequest.Builder builder, boolean forCount, boolean forBatchedSearch) {
|
||||
|
||||
@ -1770,7 +1808,8 @@ class RequestConverter {
|
||||
.id(query.getId()) //
|
||||
.index(Arrays.asList(index.getIndexNames())) //
|
||||
.preference(query.getPreference()) //
|
||||
.searchType(searchType(query.getSearchType())).source(query.getSource()) //
|
||||
.searchType(searchType(query.getSearchType())) //
|
||||
.source(query.getSource()) //
|
||||
;
|
||||
|
||||
if (query.getRoute() != null) {
|
||||
@ -1789,10 +1828,7 @@ class RequestConverter {
|
||||
}
|
||||
|
||||
if (!CollectionUtils.isEmpty(query.getParams())) {
|
||||
Function<Map.Entry<String, Object>, String> keyMapper = Map.Entry::getKey;
|
||||
Function<Map.Entry<String, Object>, JsonData> valueMapper = entry -> JsonData.of(entry.getValue(), jsonpMapper);
|
||||
Map<String, JsonData> params = query.getParams().entrySet().stream()
|
||||
.collect(Collectors.toMap(keyMapper, valueMapper));
|
||||
Map<String, JsonData> params = getTemplateParams(query.getParams().entrySet());
|
||||
builder.params(params);
|
||||
}
|
||||
|
||||
@ -1800,6 +1836,14 @@ class RequestConverter {
|
||||
});
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Map<String, JsonData> getTemplateParams(Set<Map.Entry<String, Object>> query) {
|
||||
Function<Map.Entry<String, Object>, String> keyMapper = Map.Entry::getKey;
|
||||
Function<Map.Entry<String, Object>, JsonData> valueMapper = entry -> JsonData.of(entry.getValue(), jsonpMapper);
|
||||
return query.stream()
|
||||
.collect(Collectors.toMap(keyMapper, valueMapper));
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
public PutScriptRequest scriptPut(Script script) {
|
||||
|
@ -18,6 +18,7 @@ package org.springframework.data.elasticsearch.core;
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.skyscreamer.jsonassert.JSONAssert.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.json.JSONException;
|
||||
@ -42,11 +43,12 @@ import org.springframework.lang.Nullable;
|
||||
* Integration tests search template API.
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Haibo Liu
|
||||
*/
|
||||
@SpringIntegrationTest
|
||||
public abstract class SearchTemplateIntegrationTests {
|
||||
|
||||
private static final String SCRIPT = """
|
||||
private static final String SEARCH_FIRSTNAME = """
|
||||
{
|
||||
"query": {
|
||||
"bool": {
|
||||
@ -63,21 +65,57 @@ public abstract class SearchTemplateIntegrationTests {
|
||||
"size": 100
|
||||
}
|
||||
""";
|
||||
private Script script = Script.builder() //
|
||||
.withId("testScript") //
|
||||
|
||||
private static final String SEARCH_LASTNAME = """
|
||||
{
|
||||
"query": {
|
||||
"bool": {
|
||||
"must": [
|
||||
{
|
||||
"match": {
|
||||
"lastName": "{{lastName}}"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"from": 0,
|
||||
"size": 100
|
||||
}
|
||||
""";
|
||||
|
||||
private static final Script SCRIPT_SEARCH_FIRSTNAME = Script.builder() //
|
||||
.withId("searchFirstName") //
|
||||
.withLanguage("mustache") //
|
||||
.withSource(SCRIPT) //
|
||||
.withSource(SEARCH_FIRSTNAME) //
|
||||
.build();
|
||||
|
||||
private static final Script SCRIPT_SEARCH_LASTNAME = Script.builder() //
|
||||
.withId("searchLastName") //
|
||||
.withLanguage("mustache") //
|
||||
.withSource(SEARCH_LASTNAME) //
|
||||
.build();
|
||||
|
||||
@Autowired ElasticsearchOperations operations;
|
||||
@Autowired IndexNameProvider indexNameProvider;
|
||||
@Nullable IndexOperations indexOperations;
|
||||
IndexOperations personIndexOperations, studentIndexOperations;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
indexNameProvider.increment();
|
||||
indexOperations = operations.indexOps(Person.class);
|
||||
indexOperations.createWithMapping();
|
||||
personIndexOperations = operations.indexOps(Person.class);
|
||||
personIndexOperations.createWithMapping();
|
||||
studentIndexOperations = operations.indexOps(Student.class);
|
||||
studentIndexOperations.createWithMapping();
|
||||
|
||||
operations.save( //
|
||||
new Person("1", "John", "Smith"), //
|
||||
new Person("2", "Willy", "Smith"), //
|
||||
new Person("3", "John", "Myers"));
|
||||
|
||||
operations.save(
|
||||
new Student("1", "Joey", "Dunlop"), //
|
||||
new Student("2", "Michael", "Dunlop"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -89,41 +127,35 @@ public abstract class SearchTemplateIntegrationTests {
|
||||
@Test // #1891
|
||||
@DisplayName("should store, retrieve and delete template script")
|
||||
void shouldStoreAndRetrieveAndDeleteTemplateScript() throws JSONException {
|
||||
|
||||
// we do all in this test because scripts aren't stored in an index but in the cluster and we need to clenaup.
|
||||
|
||||
var success = operations.putScript(script);
|
||||
var success = operations.putScript(SCRIPT_SEARCH_FIRSTNAME);
|
||||
assertThat(success).isTrue();
|
||||
|
||||
var savedScript = operations.getScript(script.id());
|
||||
var savedScript = operations.getScript(SCRIPT_SEARCH_FIRSTNAME.id());
|
||||
assertThat(savedScript).isNotNull();
|
||||
assertThat(savedScript.id()).isEqualTo(script.id());
|
||||
assertThat(savedScript.language()).isEqualTo(script.language());
|
||||
assertEquals(savedScript.source(), script.source(), false);
|
||||
assertThat(savedScript.id()).isEqualTo(SCRIPT_SEARCH_FIRSTNAME.id());
|
||||
assertThat(savedScript.language()).isEqualTo(SCRIPT_SEARCH_FIRSTNAME.language());
|
||||
assertEquals(savedScript.source(), SCRIPT_SEARCH_FIRSTNAME.source(), false);
|
||||
|
||||
success = operations.deleteScript(script.id());
|
||||
success = operations.deleteScript(SCRIPT_SEARCH_FIRSTNAME.id());
|
||||
assertThat(success).isTrue();
|
||||
|
||||
savedScript = operations.getScript(script.id());
|
||||
savedScript = operations.getScript(SCRIPT_SEARCH_FIRSTNAME.id());
|
||||
assertThat(savedScript).isNull();
|
||||
|
||||
assertThatThrownBy(() -> operations.deleteScript(script.id())) //
|
||||
assertThatThrownBy(() -> operations.deleteScript(SCRIPT_SEARCH_FIRSTNAME.id())) //
|
||||
.isInstanceOf(ResourceNotFoundException.class);
|
||||
}
|
||||
|
||||
@Test // #1891
|
||||
@DisplayName("should search with template")
|
||||
void shouldSearchWithTemplate() {
|
||||
|
||||
var success = operations.putScript(script);
|
||||
var success = operations.putScript(SCRIPT_SEARCH_FIRSTNAME);
|
||||
assertThat(success).isTrue();
|
||||
|
||||
operations.save( //
|
||||
new Person("1", "John", "Smith"), //
|
||||
new Person("2", "Willy", "Smith"), //
|
||||
new Person("3", "John", "Myers"));
|
||||
var query = SearchTemplateQuery.builder() //
|
||||
.withId(script.id()) //
|
||||
.withId(SCRIPT_SEARCH_FIRSTNAME.id()) //
|
||||
.withParams(Map.of("firstName", "John")) //
|
||||
.build();
|
||||
|
||||
@ -131,15 +163,169 @@ public abstract class SearchTemplateIntegrationTests {
|
||||
|
||||
assertThat(searchHits.getTotalHits()).isEqualTo(2);
|
||||
|
||||
success = operations.deleteScript(script.id());
|
||||
success = operations.deleteScript(SCRIPT_SEARCH_FIRSTNAME.id());
|
||||
assertThat(success).isTrue();
|
||||
}
|
||||
|
||||
@Document(indexName = "#{@indexNameProvider.indexName()}")
|
||||
@Test // #2704
|
||||
@DisplayName("should search with template multisearch")
|
||||
void shouldSearchWithTemplateMultiSearch() {
|
||||
var success = operations.putScript(SCRIPT_SEARCH_FIRSTNAME);
|
||||
assertThat(success).isTrue();
|
||||
|
||||
var q1 = SearchTemplateQuery.builder() //
|
||||
.withId(SCRIPT_SEARCH_FIRSTNAME.id()) //
|
||||
.withParams(Map.of("firstName", "John")) //
|
||||
.build();
|
||||
var q2 = SearchTemplateQuery.builder() //
|
||||
.withId(SCRIPT_SEARCH_FIRSTNAME.id()) //
|
||||
.withParams(Map.of("firstName", "Willy")) //
|
||||
.build();
|
||||
|
||||
var multiSearchHits = operations.multiSearch(List.of(q1, q2), Person.class);
|
||||
|
||||
assertThat(multiSearchHits.size()).isEqualTo(2);
|
||||
assertThat(multiSearchHits.get(0).getTotalHits()).isEqualTo(2);
|
||||
assertThat(multiSearchHits.get(1).getTotalHits()).isEqualTo(1);
|
||||
|
||||
assertThat(multiSearchHits.get(0).getSearchHits())
|
||||
.extracting(SearchHit::getContent)
|
||||
.extracting(Person::lastName)
|
||||
.contains("Smith", "Myers");
|
||||
assertThat(multiSearchHits.get(1).getSearchHits())
|
||||
.extracting(SearchHit::getContent)
|
||||
.extracting(Person::lastName)
|
||||
.containsExactly("Smith");
|
||||
|
||||
success = operations.deleteScript(SCRIPT_SEARCH_FIRSTNAME.id());
|
||||
assertThat(success).isTrue();
|
||||
}
|
||||
|
||||
@Test // #2704
|
||||
@DisplayName("should search with template multisearch including different scripts")
|
||||
void shouldSearchWithTemplateMultiSearchIncludingDifferentScripts() {
|
||||
assertThat(operations.putScript(SCRIPT_SEARCH_FIRSTNAME)).isTrue();
|
||||
assertThat(operations.putScript(SCRIPT_SEARCH_LASTNAME)).isTrue();
|
||||
|
||||
var q1 = SearchTemplateQuery.builder() //
|
||||
.withId(SCRIPT_SEARCH_FIRSTNAME.id()) //
|
||||
.withParams(Map.of("firstName", "John")) //
|
||||
.build();
|
||||
var q2 = SearchTemplateQuery.builder() //
|
||||
.withId(SCRIPT_SEARCH_LASTNAME.id()) //
|
||||
.withParams(Map.of("lastName", "smith")) //
|
||||
.build();
|
||||
|
||||
var multiSearchHits = operations.multiSearch(List.of(q1, q2), Person.class);
|
||||
|
||||
assertThat(multiSearchHits.size()).isEqualTo(2);
|
||||
assertThat(multiSearchHits.get(0).getTotalHits()).isEqualTo(2);
|
||||
assertThat(multiSearchHits.get(1).getTotalHits()).isEqualTo(2);
|
||||
|
||||
assertThat(multiSearchHits.get(0).getSearchHits())
|
||||
.extracting(SearchHit::getContent)
|
||||
.extracting(Person::lastName)
|
||||
.contains("Smith", "Myers");
|
||||
assertThat(multiSearchHits.get(1).getSearchHits())
|
||||
.extracting(SearchHit::getContent)
|
||||
.extracting(Person::firstName)
|
||||
.contains("John", "Willy");
|
||||
|
||||
assertThat(operations.deleteScript(SCRIPT_SEARCH_FIRSTNAME.id())).isTrue();
|
||||
assertThat(operations.deleteScript(SCRIPT_SEARCH_LASTNAME.id())).isTrue();
|
||||
}
|
||||
|
||||
@Test // #2704
|
||||
@DisplayName("should search with template multisearch with multiple classes")
|
||||
void shouldSearchWithTemplateMultiSearchWithMultipleClasses() {
|
||||
assertThat(operations.putScript(SCRIPT_SEARCH_FIRSTNAME)).isTrue();
|
||||
assertThat(operations.putScript(SCRIPT_SEARCH_LASTNAME)).isTrue();
|
||||
|
||||
var q1 = SearchTemplateQuery.builder() //
|
||||
.withId(SCRIPT_SEARCH_FIRSTNAME.id()) //
|
||||
.withParams(Map.of("firstName", "John")) //
|
||||
.build();
|
||||
var q2 = SearchTemplateQuery.builder() //
|
||||
.withId(SCRIPT_SEARCH_FIRSTNAME.id()) //
|
||||
.withParams(Map.of("firstName", "Joey")) //
|
||||
.build();
|
||||
|
||||
// search with multiple classes
|
||||
var multiSearchHits = operations.multiSearch(List.of(q1, q2), List.of(Person.class, Student.class));
|
||||
|
||||
assertThat(multiSearchHits.size()).isEqualTo(2);
|
||||
assertThat(multiSearchHits.get(0).getTotalHits()).isEqualTo(2);
|
||||
assertThat(multiSearchHits.get(1).getTotalHits()).isEqualTo(1);
|
||||
|
||||
assertThat(multiSearchHits.get(0).getSearchHits())
|
||||
// type casting is needed here
|
||||
.extracting(hits -> (Person) hits.getContent())
|
||||
.extracting(Person::lastName)
|
||||
.contains("Smith", "Myers");
|
||||
assertThat(multiSearchHits.get(1).getSearchHits())
|
||||
// type casting is needed here
|
||||
.extracting(hits -> (Student) hits.getContent())
|
||||
.extracting(Student::lastName)
|
||||
.containsExactly("Dunlop");
|
||||
|
||||
assertThat(operations.deleteScript(SCRIPT_SEARCH_FIRSTNAME.id())).isTrue();
|
||||
assertThat(operations.deleteScript(SCRIPT_SEARCH_LASTNAME.id())).isTrue();
|
||||
}
|
||||
|
||||
@Test // #2704
|
||||
@DisplayName("should search with template multisearch with multiple index coordinates")
|
||||
void shouldSearchWithTemplateMultiSearchWithMultipleIndexCoordinates() {
|
||||
assertThat(operations.putScript(SCRIPT_SEARCH_FIRSTNAME)).isTrue();
|
||||
assertThat(operations.putScript(SCRIPT_SEARCH_LASTNAME)).isTrue();
|
||||
|
||||
var q1 = SearchTemplateQuery.builder() //
|
||||
.withId(SCRIPT_SEARCH_FIRSTNAME.id()) //
|
||||
.withParams(Map.of("firstName", "John")) //
|
||||
.build();
|
||||
var q2 = SearchTemplateQuery.builder() //
|
||||
.withId(SCRIPT_SEARCH_LASTNAME.id()) //
|
||||
.withParams(Map.of("lastName", "Dunlop")) //
|
||||
.build();
|
||||
|
||||
// search with multiple index coordinates
|
||||
var multiSearchHits = operations.multiSearch(
|
||||
List.of(q1, q2),
|
||||
List.of(Person.class, Student.class),
|
||||
List.of(IndexCoordinates.of(indexNameProvider.indexName() + "-person"),
|
||||
IndexCoordinates.of(indexNameProvider.indexName() + "-student")));
|
||||
|
||||
assertThat(multiSearchHits.size()).isEqualTo(2);
|
||||
assertThat(multiSearchHits.get(0).getTotalHits()).isEqualTo(2);
|
||||
assertThat(multiSearchHits.get(1).getTotalHits()).isEqualTo(2);
|
||||
|
||||
assertThat(multiSearchHits.get(0).getSearchHits())
|
||||
// type casting is needed here
|
||||
.extracting(hits -> (Person) hits.getContent())
|
||||
.extracting(Person::lastName)
|
||||
.contains("Smith", "Myers");
|
||||
assertThat(multiSearchHits.get(1).getSearchHits())
|
||||
// type casting is needed here
|
||||
.extracting(hits -> (Student) hits.getContent())
|
||||
.extracting(Student::firstName)
|
||||
.contains("Joey", "Michael");
|
||||
|
||||
assertThat(operations.deleteScript(SCRIPT_SEARCH_FIRSTNAME.id())).isTrue();
|
||||
assertThat(operations.deleteScript(SCRIPT_SEARCH_LASTNAME.id())).isTrue();
|
||||
}
|
||||
|
||||
@Document(indexName = "#{@indexNameProvider.indexName()}-person")
|
||||
record Person( //
|
||||
@Nullable @Id String id, //
|
||||
@Field(type = FieldType.Text) String firstName, //
|
||||
@Field(type = FieldType.Text) String lastName //
|
||||
) {
|
||||
}
|
||||
|
||||
@Document(indexName = "#{@indexNameProvider.indexName()}-student")
|
||||
record Student( //
|
||||
@Nullable @Id String id, //
|
||||
@Field(type = FieldType.Text) String firstName, //
|
||||
@Field(type = FieldType.Text) String lastName //
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user