mirror of
https://github.com/spring-projects/spring-data-elasticsearch.git
synced 2025-06-12 07:02:10 +00:00
parent
4d7d0955f9
commit
efd394370a
1
.gitignore
vendored
1
.gitignore
vendored
@ -24,3 +24,4 @@ target
|
||||
|
||||
|
||||
/zap.env
|
||||
.localdocker-env
|
||||
|
@ -17,7 +17,6 @@ The following arguments are available:
|
||||
* `refreshIntervall`, defaults to _"1s"_
|
||||
* `indexStoreType`, defaults to _"fs"_
|
||||
|
||||
|
||||
It is as well possible to define https://www.elastic.co/guide/en/elasticsearch/reference/7.11/index-modules-index-sorting.html[index sorting] (check the linked Elasticsearch documentation for the possible field types and values):
|
||||
|
||||
====
|
||||
@ -133,9 +132,7 @@ stream.close();
|
||||
----
|
||||
====
|
||||
|
||||
There are no methods in the `SearchOperations` API to access the scroll id, if it should be necessary to access this,
|
||||
the following methods of the `AbstractElasticsearchTemplate` can be used (this is the base implementation for the
|
||||
different `ElasticsearchOperations` implementations):
|
||||
There are no methods in the `SearchOperations` API to access the scroll id, if it should be necessary to access this, the following methods of the `AbstractElasticsearchTemplate` can be used (this is the base implementation for the different `ElasticsearchOperations` implementations):
|
||||
|
||||
====
|
||||
[source,java]
|
||||
@ -281,7 +278,7 @@ This works with every implementation of the `Query` interface.
|
||||
[[elasticsearch.misc.point-in-time]]
|
||||
== Point In Time (PIT) API
|
||||
|
||||
`ElasticsearchOperations` supports the point in time API of Elasticsearch (see https://www.elastic.co/guide/en/elasticsearch/reference/8.3/point-in-time-api.html).
|
||||
`ElasticsearchOperations` supports the point in time API of Elasticsearch (see https://www.elastic.co/guide/en/elasticsearch/reference/8.3/point-in-time-api.html).
|
||||
The following code snippet shows how to use this feature with a fictional `Person` class:
|
||||
|
||||
====
|
||||
@ -310,8 +307,115 @@ SearchHits<Person> searchHits2 = operations.search(query2, Person.class);
|
||||
operations.closePointInTime(searchHits2.getPointInTimeId()); <.>
|
||||
|
||||
----
|
||||
|
||||
<.> create a point in time for an index (can be multiple names) and a keep-alive duration and retrieve its id
|
||||
<.> pass that id into the query to search together with the next keep-alive value
|
||||
<.> for the next query, use the id returned from the previous search
|
||||
<.> when done, close the point in time using the last returned id
|
||||
====
|
||||
|
||||
[[elasticsearch.misc.searchtemplates]]
|
||||
== Search Template support
|
||||
|
||||
Use of the search template API is supported.
|
||||
To use this, it first is necessary to create a stored script.
|
||||
The `ElasticsearchOperations` interface extends `ScriptOperations` which provides the necessary functions.
|
||||
The example used here assumes that we have `Person` entity with a property named `firstName`.
|
||||
A search template script can be saved like this:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.core.script.Script;
|
||||
|
||||
operations.putScript( <.>
|
||||
Script.builder()
|
||||
.withId("person-firstname") <.>
|
||||
.withLanguage("mustache") <.>
|
||||
.withSource(""" <.>
|
||||
{
|
||||
"query": {
|
||||
"bool": {
|
||||
"must": [
|
||||
{
|
||||
"match": {
|
||||
"firstName": "{{firstName}}" <.>
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"from": "{{from}}", <.>
|
||||
"size": "{{size}}" <.>
|
||||
}
|
||||
""")
|
||||
.build()
|
||||
);
|
||||
----
|
||||
|
||||
<.> Use the `putScript()` method to store a search template script
|
||||
<.> The name / id of the script
|
||||
<.> Scripts that are used in search templates must be in the _mustache_ language.
|
||||
<.> The script source
|
||||
<.> The search parameter in the script
|
||||
<.> Paging request offset
|
||||
<.> Paging request size
|
||||
====
|
||||
|
||||
To use a search template in a search query, Spring Data Elasticsearch provides the `SearchTemplateQuery`, an implementation of the `org.springframework.data.elasticsearch.core.query.Query` interface.
|
||||
|
||||
In the following code, we will add a call using a search template query to a custom repository implementation (see
|
||||
<<repositories.custom-implementations>>) as
|
||||
an example how this can be integrated into a repository call.
|
||||
|
||||
We first define the custom repository fragment interface:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
interface PersonCustomRepository {
|
||||
SearchPage<Person> findByFirstNameWithSearchTemplate(String firstName, Pageable pageable);
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
The implementation of this repository fragment looks like this:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
public class PersonCustomRepositoryImpl implements PersonCustomRepository {
|
||||
|
||||
private final ElasticsearchOperations operations;
|
||||
|
||||
public PersonCustomRepositoryImpl(ElasticsearchOperations operations) {
|
||||
this.operations = operations;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SearchPage<Person> findByFirstNameWithSearchTemplate(String firstName, Pageable pageable) {
|
||||
|
||||
var query = SearchTemplateQuery.builder() <.>
|
||||
.withId("person-firstname") <.>
|
||||
.withParams(
|
||||
Map.of( <.>
|
||||
"firstName", firstName,
|
||||
"from", pageable.getOffset(),
|
||||
"size", pageable.getPageSize()
|
||||
)
|
||||
)
|
||||
.build();
|
||||
|
||||
SearchHits<Person> searchHits = operations.search(query, Person.class); <.>
|
||||
|
||||
return SearchHitSupport.searchPageFor(searchHits, pageable);
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
<.> Create a `SearchTemplateQuery`
|
||||
<.> Provide the id of the search template
|
||||
<.> The parameters are passed in a `Map<String,Object>`
|
||||
<.> Do the search in the same way as with the other query types.
|
||||
====
|
||||
|
@ -234,3 +234,9 @@ Query query = NativeQuery.builder()
|
||||
SearchHits<Person> searchHits = operations.search(query, Person.class);
|
||||
----
|
||||
====
|
||||
|
||||
[[elasticsearch.operations.searchtemplateScOp§query]]
|
||||
=== SearchTemplateQuery
|
||||
|
||||
This is a special implementation of the `Query` interface to be used in combination with a stored search template.
|
||||
See <<elasticsearch.misc.searchtemplates>> for further information.
|
||||
|
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2022 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;
|
||||
|
||||
import org.springframework.dao.NonTransientDataAccessResourceException;
|
||||
|
||||
/**
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 5.1
|
||||
*/
|
||||
public class ResourceNotFoundException extends NonTransientDataAccessResourceException {
|
||||
|
||||
public ResourceNotFoundException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
@ -30,6 +30,7 @@ import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.dao.OptimisticLockingFailureException;
|
||||
import org.springframework.dao.support.PersistenceExceptionTranslator;
|
||||
import org.springframework.data.elasticsearch.NoSuchIndexException;
|
||||
import org.springframework.data.elasticsearch.ResourceNotFoundException;
|
||||
import org.springframework.data.elasticsearch.UncategorizedElasticsearchException;
|
||||
|
||||
/**
|
||||
@ -77,16 +78,20 @@ public class ElasticsearchExceptionTranslator implements PersistenceExceptionTra
|
||||
var errorType = response.error().type();
|
||||
var errorReason = response.error().reason() != null ? response.error().reason() : "undefined reason";
|
||||
|
||||
if (response.status() == 404 && "index_not_found_exception".equals(errorType)) {
|
||||
if (response.status() == 404) {
|
||||
|
||||
// noinspection RegExpRedundantEscape
|
||||
Pattern pattern = Pattern.compile(".*no such index \\[(.*)\\]");
|
||||
String index = "";
|
||||
Matcher matcher = pattern.matcher(errorReason);
|
||||
if (matcher.matches()) {
|
||||
index = matcher.group(1);
|
||||
if ("index_not_found_exception".equals(errorType)) {
|
||||
// noinspection RegExpRedundantEscape
|
||||
Pattern pattern = Pattern.compile(".*no such index \\[(.*)\\]");
|
||||
String index = "";
|
||||
Matcher matcher = pattern.matcher(errorReason);
|
||||
if (matcher.matches()) {
|
||||
index = matcher.group(1);
|
||||
}
|
||||
return new NoSuchIndexException(index);
|
||||
}
|
||||
return new NoSuchIndexException(index);
|
||||
|
||||
return new ResourceNotFoundException(errorReason);
|
||||
}
|
||||
String body = JsonUtils.toJson(response, jsonpMapper);
|
||||
|
||||
|
@ -55,10 +55,12 @@ import org.springframework.data.elasticsearch.core.query.ByQueryResponse;
|
||||
import org.springframework.data.elasticsearch.core.query.IndexQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.MoreLikeThisQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.Query;
|
||||
import org.springframework.data.elasticsearch.core.query.SearchTemplateQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.UpdateQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.UpdateResponse;
|
||||
import org.springframework.data.elasticsearch.core.reindex.ReindexRequest;
|
||||
import org.springframework.data.elasticsearch.core.reindex.ReindexResponse;
|
||||
import org.springframework.data.elasticsearch.core.script.Script;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@ -317,11 +319,21 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
|
||||
public <T> SearchHits<T> search(Query query, Class<T> clazz, IndexCoordinates index) {
|
||||
|
||||
Assert.notNull(query, "query must not be null");
|
||||
Assert.notNull(clazz, "clazz must not be null");
|
||||
Assert.notNull(index, "index must not be null");
|
||||
|
||||
if (query instanceof SearchTemplateQuery searchTemplateQuery) {
|
||||
return doSearch(searchTemplateQuery, clazz, index);
|
||||
} else {
|
||||
return doSearch(query, clazz, index);
|
||||
}
|
||||
}
|
||||
|
||||
protected <T> SearchHits<T> doSearch(Query query, Class<T> clazz, IndexCoordinates index) {
|
||||
SearchRequest searchRequest = requestConverter.searchRequest(query, clazz, index, false);
|
||||
SearchResponse<EntityAsMap> searchResponse = execute(client -> client.search(searchRequest, EntityAsMap.class));
|
||||
|
||||
// noinspection DuplicatedCode
|
||||
ReadDocumentCallback<T> readDocumentCallback = new ReadDocumentCallback<>(elasticsearchConverter, clazz, index);
|
||||
SearchDocumentResponse.EntityCreator<T> entityCreator = getEntityCreator(readDocumentCallback);
|
||||
SearchDocumentResponseCallback<SearchHits<T>> callback = new ReadSearchDocumentResponseCallback<>(clazz, index);
|
||||
@ -329,6 +341,18 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
|
||||
return callback.doWith(SearchDocumentResponseBuilder.from(searchResponse, entityCreator, jsonpMapper));
|
||||
}
|
||||
|
||||
protected <T> SearchHits<T> doSearch(SearchTemplateQuery query, Class<T> clazz, IndexCoordinates index) {
|
||||
var searchTemplateRequest = requestConverter.searchTemplate(query, index);
|
||||
var searchTemplateResponse = execute(client -> client.searchTemplate(searchTemplateRequest, EntityAsMap.class));
|
||||
|
||||
// noinspection DuplicatedCode
|
||||
ReadDocumentCallback<T> readDocumentCallback = new ReadDocumentCallback<>(elasticsearchConverter, clazz, index);
|
||||
SearchDocumentResponse.EntityCreator<T> entityCreator = getEntityCreator(readDocumentCallback);
|
||||
SearchDocumentResponseCallback<SearchHits<T>> callback = new ReadSearchDocumentResponseCallback<>(clazz, index);
|
||||
|
||||
return callback.doWith(SearchDocumentResponseBuilder.from(searchTemplateResponse, entityCreator, jsonpMapper));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected <T> SearchHits<T> doSearch(MoreLikeThisQuery query, Class<T> clazz, IndexCoordinates index) {
|
||||
|
||||
@ -513,6 +537,35 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
|
||||
|
||||
// endregion
|
||||
|
||||
// region script methods
|
||||
@Override
|
||||
public boolean putScript(Script script) {
|
||||
|
||||
Assert.notNull(script, "script must not be null");
|
||||
|
||||
var request = requestConverter.scriptPut(script);
|
||||
return execute(client -> client.putScript(request)).acknowledged();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Script getScript(String name) {
|
||||
|
||||
Assert.notNull(name, "name must not be null");
|
||||
|
||||
var request = requestConverter.scriptGet(name);
|
||||
return responseConverter.scriptResponse(execute(client -> client.getScript(request)));
|
||||
}
|
||||
|
||||
public boolean deleteScript(String name) {
|
||||
|
||||
Assert.notNull(name, "name must not be null");
|
||||
|
||||
DeleteScriptRequest request = requestConverter.scriptDelete(name);
|
||||
return execute(client -> client.deleteScript(request)).acknowledged();
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region client callback
|
||||
/**
|
||||
* Callback interface to be used with {@link #execute(ElasticsearchTemplate.ClientCallback)} for operating directly on
|
||||
|
@ -237,6 +237,29 @@ public class ReactiveElasticsearchClient extends ApiClient<ElasticsearchTranspor
|
||||
return search(fn.apply(new SearchRequest.Builder()).build(), tDocumentClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 5.1
|
||||
*/
|
||||
public <T> Mono<SearchTemplateResponse<T>> searchTemplate(SearchTemplateRequest request, Class<T> tDocumentClass) {
|
||||
|
||||
Assert.notNull(request, "request must not be null");
|
||||
Assert.notNull(tDocumentClass, "tDocumentClass must not be null");
|
||||
|
||||
return Mono.fromFuture(transport.performRequestAsync(request,
|
||||
SearchTemplateRequest.createSearchTemplateEndpoint(this.getDeserializer(tDocumentClass)), transportOptions));
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 5.1
|
||||
*/
|
||||
public <T> Mono<SearchTemplateResponse<T>> searchTemplate(
|
||||
Function<SearchTemplateRequest.Builder, ObjectBuilder<SearchTemplateRequest>> fn, Class<T> tDocumentClass) {
|
||||
|
||||
Assert.notNull(fn, "fn must not be null");
|
||||
|
||||
return searchTemplate(fn.apply(new SearchTemplateRequest.Builder()).build(), tDocumentClass);
|
||||
}
|
||||
|
||||
public <T> Mono<ScrollResponse<T>> scroll(ScrollRequest request, Class<T> tDocumentClass) {
|
||||
|
||||
Assert.notNull(request, "request must not be null");
|
||||
@ -320,4 +343,67 @@ public class ReactiveElasticsearchClient extends ApiClient<ElasticsearchTranspor
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region script api
|
||||
/**
|
||||
* @since 5.1
|
||||
*/
|
||||
public Mono<PutScriptResponse> putScript(PutScriptRequest request) {
|
||||
|
||||
Assert.notNull(request, "request must not be null");
|
||||
|
||||
return Mono.fromFuture(transport.performRequestAsync(request, PutScriptRequest._ENDPOINT, transportOptions));
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 5.1
|
||||
*/
|
||||
public Mono<PutScriptResponse> putScript(Function<PutScriptRequest.Builder, ObjectBuilder<PutScriptRequest>> fn) {
|
||||
|
||||
Assert.notNull(fn, "fn must not be null");
|
||||
|
||||
return putScript(fn.apply(new PutScriptRequest.Builder()).build());
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 5.1
|
||||
*/
|
||||
public Mono<GetScriptResponse> getScript(GetScriptRequest request) {
|
||||
|
||||
Assert.notNull(request, "request must not be null");
|
||||
|
||||
return Mono.fromFuture(transport.performRequestAsync(request, GetScriptRequest._ENDPOINT, transportOptions));
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 5.1
|
||||
*/
|
||||
public Mono<GetScriptResponse> getScript(Function<GetScriptRequest.Builder, ObjectBuilder<GetScriptRequest>> fn) {
|
||||
|
||||
Assert.notNull(fn, "fn must not be null");
|
||||
|
||||
return getScript(fn.apply(new GetScriptRequest.Builder()).build());
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 5.1
|
||||
*/
|
||||
public Mono<DeleteScriptResponse> deleteScript(DeleteScriptRequest request) {
|
||||
|
||||
Assert.notNull(request, "request must not be null");
|
||||
|
||||
return Mono.fromFuture(transport.performRequestAsync(request, DeleteScriptRequest._ENDPOINT, transportOptions));
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 5.1
|
||||
*/
|
||||
public Mono<DeleteScriptResponse> deleteScript(
|
||||
Function<DeleteScriptRequest.Builder, ObjectBuilder<DeleteScriptRequest>> fn) {
|
||||
|
||||
Assert.notNull(fn, "fn must not be null");
|
||||
|
||||
return deleteScript(fn.apply(new DeleteScriptRequest.Builder()).build());
|
||||
}
|
||||
// endregion
|
||||
|
||||
}
|
||||
|
@ -62,10 +62,12 @@ import org.springframework.data.elasticsearch.core.query.BaseQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.BulkOptions;
|
||||
import org.springframework.data.elasticsearch.core.query.ByQueryResponse;
|
||||
import org.springframework.data.elasticsearch.core.query.Query;
|
||||
import org.springframework.data.elasticsearch.core.query.SearchTemplateQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.UpdateQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.UpdateResponse;
|
||||
import org.springframework.data.elasticsearch.core.reindex.ReindexRequest;
|
||||
import org.springframework.data.elasticsearch.core.reindex.ReindexResponse;
|
||||
import org.springframework.data.elasticsearch.core.script.Script;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
@ -341,12 +343,18 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
|
||||
@Override
|
||||
protected Flux<SearchDocument> doFind(Query query, Class<?> clazz, IndexCoordinates index) {
|
||||
|
||||
return Flux.defer(() -> {
|
||||
boolean queryIsUnbounded = !(query.getPageable().isPaged() || query.isLimiting());
|
||||
|
||||
return queryIsUnbounded ? doFindUnbounded(query, clazz, index) : doFindBounded(query, clazz, index);
|
||||
});
|
||||
Assert.notNull(query, "query must not be null");
|
||||
Assert.notNull(clazz, "clazz must not be null");
|
||||
Assert.notNull(index, "index must not be null");
|
||||
|
||||
if (query instanceof SearchTemplateQuery searchTemplateQuery) {
|
||||
return Flux.defer(() -> doSearch(searchTemplateQuery, clazz, index));
|
||||
} else {
|
||||
return Flux.defer(() -> {
|
||||
boolean queryIsUnbounded = !(query.getPageable().isPaged() || query.isLimiting());
|
||||
return queryIsUnbounded ? doFindUnbounded(query, clazz, index) : doFindBounded(query, clazz, index);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private Flux<SearchDocument> doFindUnbounded(Query query, Class<?> clazz, IndexCoordinates index) {
|
||||
@ -465,6 +473,17 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
|
||||
.map(entityAsMapHit -> DocumentAdapters.from(entityAsMapHit, jsonpMapper));
|
||||
}
|
||||
|
||||
private Flux<SearchDocument> doSearch(SearchTemplateQuery query, Class<?> clazz, IndexCoordinates index) {
|
||||
|
||||
var request = requestConverter.searchTemplate(query, index);
|
||||
|
||||
return Mono
|
||||
.from(execute((ClientCallback<Publisher<SearchTemplateResponse<EntityAsMap>>>) client -> client
|
||||
.searchTemplate(request, EntityAsMap.class))) //
|
||||
.flatMapIterable(entityAsMapSearchResponse -> entityAsMapSearchResponse.hits().hits()) //
|
||||
.map(entityAsMapHit -> DocumentAdapters.from(entityAsMapHit, jsonpMapper));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected <T> Mono<SearchDocumentResponse> doFindForResponse(Query query, Class<?> clazz, IndexCoordinates index) {
|
||||
|
||||
@ -519,6 +538,37 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
|
||||
|
||||
// endregion
|
||||
|
||||
// region script operations
|
||||
@Override
|
||||
public Mono<Boolean> putScript(Script script) {
|
||||
|
||||
Assert.notNull(script, "script must not be null");
|
||||
|
||||
var request = requestConverter.scriptPut(script);
|
||||
return Mono.from(execute((ClientCallback<Publisher<PutScriptResponse>>) client -> client.putScript(request)))
|
||||
.map(PutScriptResponse::acknowledged);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Script> getScript(String name) {
|
||||
|
||||
Assert.notNull(name, "name must not be null");
|
||||
|
||||
var request = requestConverter.scriptGet(name);
|
||||
return Mono.from(execute((ClientCallback<Publisher<GetScriptResponse>>) client -> client.getScript(request)))
|
||||
.mapNotNull(responseConverter::scriptResponse);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> deleteScript(String name) {
|
||||
Assert.notNull(name, "name must not be null");
|
||||
|
||||
var request = requestConverter.scriptDelete(name);
|
||||
return Mono.from(execute((ClientCallback<Publisher<DeleteScriptResponse>>) client -> client.deleteScript(request)))
|
||||
.map(DeleteScriptResponse::acknowledged);
|
||||
}
|
||||
// endregion
|
||||
|
||||
@Override
|
||||
public Mono<String> getVendor() {
|
||||
return Mono.just("Elasticsearch");
|
||||
|
@ -85,9 +85,11 @@ import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
import org.springframework.data.elasticsearch.core.query.*;
|
||||
import org.springframework.data.elasticsearch.core.reindex.ReindexRequest;
|
||||
import org.springframework.data.elasticsearch.core.reindex.Remote;
|
||||
import org.springframework.data.elasticsearch.core.script.Script;
|
||||
import org.springframework.data.elasticsearch.support.DefaultStringObjectMap;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
@ -99,6 +101,7 @@ import org.springframework.util.StringUtils;
|
||||
* @author scoobyzhang
|
||||
* @since 4.4
|
||||
*/
|
||||
@SuppressWarnings("ClassCanBeRecord")
|
||||
class RequestConverter {
|
||||
|
||||
// the default max result window size of Elasticsearch
|
||||
@ -1163,13 +1166,26 @@ class RequestConverter {
|
||||
|
||||
builder //
|
||||
.version(true) //
|
||||
.trackScores(query.getTrackScores());
|
||||
.trackScores(query.getTrackScores()) //
|
||||
.allowNoIndices(query.getAllowNoIndices()) //
|
||||
.source(getSourceConfig(query)) //
|
||||
.searchType(searchType(query.getSearchType())) //
|
||||
.timeout(timeStringMs(query.getTimeout())) //
|
||||
.requestCache(query.getRequestCache()) //
|
||||
;
|
||||
|
||||
var pointInTime = query.getPointInTime();
|
||||
if (pointInTime != null) {
|
||||
builder.pit(pb -> pb.id(pointInTime.id()).keepAlive(time(pointInTime.keepAlive())));
|
||||
} else {
|
||||
builder.index(Arrays.asList(indexNames));
|
||||
builder //
|
||||
.index(Arrays.asList(indexNames)) //
|
||||
;
|
||||
|
||||
var expandWildcards = query.getExpandWildcards();
|
||||
if (expandWildcards != null && !expandWildcards.isEmpty()) {
|
||||
builder.expandWildcards(expandWildcards(expandWildcards));
|
||||
}
|
||||
|
||||
if (query.getRoute() != null) {
|
||||
builder.routing(query.getRoute());
|
||||
@ -1192,8 +1208,6 @@ class RequestConverter {
|
||||
builder.from(0).size(INDEX_MAX_RESULT_WINDOW);
|
||||
}
|
||||
|
||||
builder.source(getSourceConfig(query));
|
||||
|
||||
if (!isEmpty(query.getFields())) {
|
||||
builder.fields(fb -> {
|
||||
query.getFields().forEach(fb::field);
|
||||
@ -1217,8 +1231,6 @@ class RequestConverter {
|
||||
builder.minScore((double) query.getMinScore());
|
||||
}
|
||||
|
||||
builder.searchType(searchType(query.getSearchType()));
|
||||
|
||||
if (query.getSort() != null) {
|
||||
List<SortOptions> sortOptions = getSortOptions(query.getSort(), persistentEntity);
|
||||
|
||||
@ -1241,8 +1253,6 @@ class RequestConverter {
|
||||
builder.trackTotalHits(th -> th.count(query.getTrackTotalHitsUpTo()));
|
||||
}
|
||||
|
||||
builder.timeout(timeStringMs(query.getTimeout()));
|
||||
|
||||
if (query.getExplain()) {
|
||||
builder.explain(true);
|
||||
}
|
||||
@ -1254,8 +1264,6 @@ class RequestConverter {
|
||||
|
||||
query.getRescorerQueries().forEach(rescorerQuery -> builder.rescore(getRescore(rescorerQuery)));
|
||||
|
||||
builder.requestCache(query.getRequestCache());
|
||||
|
||||
if (!query.getRuntimeFields().isEmpty()) {
|
||||
|
||||
Map<String, RuntimeField> runtimeMappings = new HashMap<>();
|
||||
@ -1540,8 +1548,69 @@ class RequestConverter {
|
||||
return ClosePointInTimeRequest.of(cpit -> cpit.id(pit));
|
||||
}
|
||||
|
||||
public SearchTemplateRequest searchTemplate(SearchTemplateQuery query, IndexCoordinates index) {
|
||||
|
||||
Assert.notNull(query, "query must not be null");
|
||||
|
||||
return SearchTemplateRequest.of(builder -> {
|
||||
builder //
|
||||
.allowNoIndices(query.getAllowNoIndices()) //
|
||||
.explain(query.getExplain()) //
|
||||
.id(query.getId()) //
|
||||
.index(Arrays.asList(index.getIndexNames())) //
|
||||
.preference(query.getPreference()) //
|
||||
.routing(query.getRoute()) //
|
||||
.searchType(searchType(query.getSearchType()))
|
||||
.source(query.getSource()) //
|
||||
;
|
||||
|
||||
var expandWildcards = query.getExpandWildcards();
|
||||
if (!expandWildcards.isEmpty()) {
|
||||
builder.expandWildcards(expandWildcards(expandWildcards));
|
||||
}
|
||||
|
||||
if (query.hasScrollTime()) {
|
||||
builder.scroll(time(query.getScrollTime()));
|
||||
}
|
||||
|
||||
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));
|
||||
builder.params(params);
|
||||
}
|
||||
|
||||
return builder;
|
||||
});
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
public PutScriptRequest scriptPut(Script script) {
|
||||
|
||||
Assert.notNull(script, "script must not be null");
|
||||
|
||||
return PutScriptRequest.of(b -> b //
|
||||
.id(script.id()) //
|
||||
.script(sb -> sb //
|
||||
.lang(script.language()) //
|
||||
.source(script.source())));
|
||||
}
|
||||
|
||||
public GetScriptRequest scriptGet(String name) {
|
||||
|
||||
Assert.notNull(name, "name must not be null");
|
||||
|
||||
return GetScriptRequest.of(b -> b.id(name));
|
||||
}
|
||||
|
||||
public DeleteScriptRequest scriptDelete(String name) {
|
||||
|
||||
Assert.notNull(name, "name must not be null");
|
||||
|
||||
return DeleteScriptRequest.of(b -> b.id(name));
|
||||
}
|
||||
// region helper functions
|
||||
|
||||
public <T> T fromJson(String json, JsonpDeserializer<T> deserializer) {
|
||||
|
@ -15,7 +15,7 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.client.elc;
|
||||
|
||||
import static org.springframework.data.elasticsearch.client.elc.JsonUtils.toJson;
|
||||
import static org.springframework.data.elasticsearch.client.elc.JsonUtils.*;
|
||||
|
||||
import co.elastic.clients.elasticsearch._types.BulkIndexByScrollFailure;
|
||||
import co.elastic.clients.elasticsearch._types.ErrorCause;
|
||||
@ -23,6 +23,7 @@ import co.elastic.clients.elasticsearch._types.Time;
|
||||
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
|
||||
import co.elastic.clients.elasticsearch.cluster.HealthResponse;
|
||||
import co.elastic.clients.elasticsearch.core.DeleteByQueryResponse;
|
||||
import co.elastic.clients.elasticsearch.core.GetScriptResponse;
|
||||
import co.elastic.clients.elasticsearch.core.UpdateByQueryResponse;
|
||||
import co.elastic.clients.elasticsearch.core.mget.MultiGetError;
|
||||
import co.elastic.clients.elasticsearch.core.mget.MultiGetResponseItem;
|
||||
@ -62,6 +63,7 @@ import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
import org.springframework.data.elasticsearch.core.query.ByQueryResponse;
|
||||
import org.springframework.data.elasticsearch.core.query.StringQuery;
|
||||
import org.springframework.data.elasticsearch.core.reindex.ReindexResponse;
|
||||
import org.springframework.data.elasticsearch.core.script.Script;
|
||||
import org.springframework.data.elasticsearch.support.DefaultStringObjectMap;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
@ -421,6 +423,22 @@ class ResponseConverter {
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region script API
|
||||
@Nullable
|
||||
public Script scriptResponse(GetScriptResponse response) {
|
||||
|
||||
Assert.notNull(response, "response must not be null");
|
||||
|
||||
return response.found() //
|
||||
? Script.builder() //
|
||||
.withId(response.id()) //
|
||||
.withLanguage(response.script().lang()) //
|
||||
.withSource(response.script().source()).build() //
|
||||
: null;
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region helper functions
|
||||
|
||||
private long timeToLong(Time time) {
|
||||
|
@ -17,6 +17,7 @@ package org.springframework.data.elasticsearch.client.elc;
|
||||
|
||||
import co.elastic.clients.elasticsearch._types.aggregations.Aggregate;
|
||||
import co.elastic.clients.elasticsearch.core.SearchResponse;
|
||||
import co.elastic.clients.elasticsearch.core.SearchTemplateResponse;
|
||||
import co.elastic.clients.elasticsearch.core.search.CompletionSuggest;
|
||||
import co.elastic.clients.elasticsearch.core.search.CompletionSuggestOption;
|
||||
import co.elastic.clients.elasticsearch.core.search.Hit;
|
||||
@ -70,6 +71,7 @@ class SearchDocumentResponseBuilder {
|
||||
|
||||
Assert.notNull(responseBody, "responseBody must not be null");
|
||||
Assert.notNull(entityCreator, "entityCreator must not be null");
|
||||
Assert.notNull(jsonpMapper, "jsonpMapper must not be null");
|
||||
|
||||
HitsMetadata<EntityAsMap> hitsMetadata = responseBody.hits();
|
||||
String scrollId = responseBody.scrollId();
|
||||
@ -80,6 +82,31 @@ class SearchDocumentResponseBuilder {
|
||||
return from(hitsMetadata, scrollId, pointInTimeId, aggregations, suggest, entityCreator, jsonpMapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* creates a SearchDocumentResponse from the {@link SearchTemplateResponse}
|
||||
*
|
||||
* @param response the Elasticsearch response body
|
||||
* @param entityCreator function to create an entity from a {@link SearchDocument}
|
||||
* @param jsonpMapper to map JsonData objects
|
||||
* @return the SearchDocumentResponse
|
||||
* @since 5.1
|
||||
*/
|
||||
public static <T> SearchDocumentResponse from(SearchTemplateResponse<EntityAsMap> response,
|
||||
SearchDocumentResponse.EntityCreator<T> entityCreator, JsonpMapper jsonpMapper) {
|
||||
|
||||
Assert.notNull(response, "response must not be null");
|
||||
Assert.notNull(entityCreator, "entityCreator must not be null");
|
||||
Assert.notNull(jsonpMapper, "jsonpMapper must not be null");
|
||||
|
||||
var hitsMetadata = response.hits();
|
||||
var scrollId = response.scrollId();
|
||||
var aggregations = response.aggregations();
|
||||
var suggest = response.suggest();
|
||||
var pointInTimeId = response.pitId();
|
||||
|
||||
return from(hitsMetadata, scrollId, pointInTimeId, aggregations, suggest, entityCreator, jsonpMapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* creates a {@link SearchDocumentResponseBuilder} from {@link HitsMetadata} with the given scrollId aggregations and
|
||||
* suggestES
|
||||
|
@ -26,10 +26,14 @@ import co.elastic.clients.elasticsearch.core.search.HighlighterType;
|
||||
import co.elastic.clients.elasticsearch.core.search.ScoreMode;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.data.elasticsearch.core.RefreshPolicy;
|
||||
import org.springframework.data.elasticsearch.core.query.GeoDistanceOrder;
|
||||
import org.springframework.data.elasticsearch.core.query.IndexQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.IndicesOptions;
|
||||
import org.springframework.data.elasticsearch.core.query.Order;
|
||||
import org.springframework.data.elasticsearch.core.query.Query;
|
||||
import org.springframework.data.elasticsearch.core.query.RescorerQuery;
|
||||
@ -125,6 +129,7 @@ final class TypeUtils {
|
||||
default -> throw new IllegalStateException("Unexpected value: " + fieldValue._kind());
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static Object toObject(@Nullable FieldValue fieldValue) {
|
||||
|
||||
@ -391,4 +396,14 @@ final class TypeUtils {
|
||||
static Float toFloat(@Nullable Long value) {
|
||||
return value != null ? Float.valueOf(value) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @sice 5.1
|
||||
*/
|
||||
@Nullable
|
||||
public static List<ExpandWildcard> expandWildcards(@Nullable EnumSet<IndicesOptions.WildcardStates> wildcardStates) {
|
||||
return (wildcardStates != null && !wildcardStates.isEmpty()) ? wildcardStates.stream()
|
||||
.map(wildcardState -> ExpandWildcard.valueOf(wildcardState.name().toLowerCase())).collect(Collectors.toList())
|
||||
: null;
|
||||
}
|
||||
}
|
||||
|
@ -228,11 +228,6 @@ public class NativeSearchQueryBuilder extends BaseQueryBuilder<NativeSearchQuery
|
||||
return this;
|
||||
}
|
||||
|
||||
public NativeSearchQueryBuilder withIndicesOptions(IndicesOptions indicesOptions) {
|
||||
this.indicesOptions = indicesOptions;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 4.2
|
||||
*/
|
||||
@ -306,8 +301,8 @@ public class NativeSearchQueryBuilder extends BaseQueryBuilder<NativeSearchQuery
|
||||
nativeSearchQuery.setSearchType(Query.SearchType.valueOf(searchType.name()));
|
||||
}
|
||||
|
||||
if (indicesOptions != null) {
|
||||
nativeSearchQuery.setIndicesOptions(indicesOptions);
|
||||
if (getIndicesOptions() != null) {
|
||||
nativeSearchQuery.setIndicesOptions(getIndicesOptions());
|
||||
}
|
||||
|
||||
nativeSearchQuery.setTrackTotalHits(trackTotalHits);
|
||||
|
@ -52,6 +52,7 @@ import org.springframework.data.elasticsearch.core.query.UpdateQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.UpdateResponse;
|
||||
import org.springframework.data.elasticsearch.core.routing.DefaultRoutingResolver;
|
||||
import org.springframework.data.elasticsearch.core.routing.RoutingResolver;
|
||||
import org.springframework.data.elasticsearch.core.script.Script;
|
||||
import org.springframework.data.elasticsearch.support.VersionInfo;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.data.mapping.callback.EntityCallbacks;
|
||||
@ -736,6 +737,29 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
|
||||
}
|
||||
}
|
||||
|
||||
// region script operations
|
||||
|
||||
@Override
|
||||
public boolean putScript(Script script) {
|
||||
throw new UnsupportedOperationException(
|
||||
"putScript() operation not implemented by " + getClass().getCanonicalName());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Script getScript(String name) {
|
||||
throw new UnsupportedOperationException(
|
||||
"getScript() operation not implemented by " + getClass().getCanonicalName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deleteScript(String name) {
|
||||
throw new UnsupportedOperationException(
|
||||
"deleteScript() operation not implemented by " + getClass().getCanonicalName());
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Document callbacks
|
||||
protected interface DocumentCallback<T> {
|
||||
@Nullable
|
||||
|
@ -50,6 +50,7 @@ import org.springframework.data.elasticsearch.core.query.Query;
|
||||
import org.springframework.data.elasticsearch.core.query.SeqNoPrimaryTerm;
|
||||
import org.springframework.data.elasticsearch.core.routing.DefaultRoutingResolver;
|
||||
import org.springframework.data.elasticsearch.core.routing.RoutingResolver;
|
||||
import org.springframework.data.elasticsearch.core.script.Script;
|
||||
import org.springframework.data.elasticsearch.core.suggest.response.Suggest;
|
||||
import org.springframework.data.elasticsearch.support.VersionInfo;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
@ -584,12 +585,14 @@ abstract public class AbstractReactiveElasticsearchTemplate
|
||||
|
||||
/**
|
||||
* Callback to convert a {@link SearchDocument} into different other classes
|
||||
*
|
||||
* @param <T> the entity type
|
||||
*/
|
||||
protected interface SearchDocumentCallback<T> {
|
||||
|
||||
/**
|
||||
* converts a {@link SearchDocument} to an entity
|
||||
*
|
||||
* @param searchDocument
|
||||
* @return the entity in a MOno
|
||||
*/
|
||||
@ -597,6 +600,7 @@ abstract public class AbstractReactiveElasticsearchTemplate
|
||||
|
||||
/**
|
||||
* converts a {@link SearchDocument} into a SearchHit
|
||||
*
|
||||
* @param searchDocument
|
||||
* @return
|
||||
*/
|
||||
@ -627,6 +631,26 @@ abstract public class AbstractReactiveElasticsearchTemplate
|
||||
|
||||
// endregion
|
||||
|
||||
// region script operations
|
||||
@Override
|
||||
public Mono<Boolean> putScript(Script script) {
|
||||
throw new UnsupportedOperationException(
|
||||
"putScript() operation not implemented by " + getClass().getCanonicalName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Script> getScript(String name) {
|
||||
throw new UnsupportedOperationException(
|
||||
"getScript() operation not implemented by " + getClass().getCanonicalName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> deleteScript(String name) {
|
||||
throw new UnsupportedOperationException(
|
||||
"deleteScript() operation not implemented by " + getClass().getCanonicalName());
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region Helper methods
|
||||
@Override
|
||||
public IndexCoordinates getIndexCoordinatesFor(Class<?> clazz) {
|
||||
|
@ -21,6 +21,7 @@ import org.springframework.data.elasticsearch.core.cluster.ClusterOperations;
|
||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
import org.springframework.data.elasticsearch.core.routing.RoutingResolver;
|
||||
import org.springframework.data.elasticsearch.core.script.ScriptOperations;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
@ -36,7 +37,7 @@ import org.springframework.lang.Nullable;
|
||||
* @author Dmitriy Yakovlev
|
||||
* @author Peter-Josef Meisch
|
||||
*/
|
||||
public interface ElasticsearchOperations extends DocumentOperations, SearchOperations {
|
||||
public interface ElasticsearchOperations extends DocumentOperations, SearchOperations, ScriptOperations {
|
||||
|
||||
/**
|
||||
* get an {@link IndexOperations} that is bound to the given class
|
||||
|
@ -23,6 +23,7 @@ import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverte
|
||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
|
||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
import org.springframework.data.elasticsearch.core.routing.RoutingResolver;
|
||||
import org.springframework.data.elasticsearch.core.script.ReactiveScriptOperations;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
@ -37,7 +38,8 @@ import org.springframework.lang.Nullable;
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 3.2
|
||||
*/
|
||||
public interface ReactiveElasticsearchOperations extends ReactiveDocumentOperations, ReactiveSearchOperations {
|
||||
public interface ReactiveElasticsearchOperations
|
||||
extends ReactiveDocumentOperations, ReactiveSearchOperations, ReactiveScriptOperations {
|
||||
|
||||
/**
|
||||
* Execute within a {@link ClientCallback} managing resources and translating errors.
|
||||
|
@ -22,6 +22,7 @@ import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
@ -75,9 +76,11 @@ public class BaseQuery implements Query {
|
||||
protected List<IdWithRouting> idsWithRouting = Collections.emptyList();
|
||||
protected final List<RuntimeField> runtimeFields = new ArrayList<>();
|
||||
@Nullable protected PointInTime pointInTime;
|
||||
|
||||
private boolean queryIsUpdatedByConverter = false;
|
||||
@Nullable private Integer reactiveBatchSize = null;
|
||||
@Nullable private Boolean allowNoIndices = null;
|
||||
|
||||
private EnumSet<IndicesOptions.WildcardStates> expandWildcards;
|
||||
|
||||
public BaseQuery() {}
|
||||
|
||||
@ -109,6 +112,8 @@ public class BaseQuery implements Query {
|
||||
this.idsWithRouting = builder.getIdsWithRouting();
|
||||
this.pointInTime = builder.getPointInTime();
|
||||
this.reactiveBatchSize = builder.getReactiveBatchSize();
|
||||
this.allowNoIndices = builder.getAllowNoIndices();
|
||||
this.expandWildcards = builder.getExpandWildcards();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -509,4 +514,14 @@ public class BaseQuery implements Query {
|
||||
public void setReactiveBatchSize(Integer reactiveBatchSize) {
|
||||
this.reactiveBatchSize = reactiveBatchSize;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Boolean getAllowNoIndices() {
|
||||
return allowNoIndices;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EnumSet<IndicesOptions.WildcardStates> getExpandWildcards() {
|
||||
return expandWildcards;
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.domain.Pageable;
|
||||
@ -45,26 +46,28 @@ public abstract class BaseQueryBuilder<Q extends BaseQuery, SELF extends BaseQue
|
||||
private float minScore;
|
||||
private final Collection<String> ids = new ArrayList<>();
|
||||
@Nullable private String route;
|
||||
protected Query.SearchType searchType = Query.SearchType.QUERY_THEN_FETCH;
|
||||
@Nullable protected IndicesOptions indicesOptions;
|
||||
private Query.SearchType searchType = Query.SearchType.QUERY_THEN_FETCH;
|
||||
@Nullable private IndicesOptions indicesOptions;
|
||||
private boolean trackScores;
|
||||
@Nullable private String preference;
|
||||
@Nullable private Integer maxResults;
|
||||
@Nullable protected HighlightQuery highlightQuery;
|
||||
@Nullable private HighlightQuery highlightQuery;
|
||||
@Nullable private Boolean trackTotalHits;
|
||||
@Nullable protected Integer trackTotalHitsUpTo;
|
||||
@Nullable protected Duration scrollTime;
|
||||
@Nullable protected Duration timeout;
|
||||
@Nullable private Integer trackTotalHitsUpTo;
|
||||
@Nullable private Duration scrollTime;
|
||||
@Nullable private Duration timeout;
|
||||
boolean explain = false;
|
||||
@Nullable protected List<Object> searchAfter;
|
||||
@Nullable private List<Object> searchAfter;
|
||||
|
||||
@Nullable private List<IndexBoost> indicesBoost;
|
||||
protected final List<RescorerQuery> rescorerQueries = new ArrayList<>();
|
||||
|
||||
@Nullable protected Boolean requestCache;
|
||||
protected final List<Query.IdWithRouting> idsWithRouting = new ArrayList<>();
|
||||
protected final List<RuntimeField> runtimeFields = new ArrayList<>();
|
||||
@Nullable protected Query.PointInTime pointInTime;
|
||||
@Nullable private Boolean requestCache;
|
||||
private final List<Query.IdWithRouting> idsWithRouting = new ArrayList<>();
|
||||
private final List<RuntimeField> runtimeFields = new ArrayList<>();
|
||||
@Nullable private Query.PointInTime pointInTime;
|
||||
@Nullable private Boolean allowNoIndices;
|
||||
private EnumSet<IndicesOptions.WildcardStates> expandWildcards = EnumSet.noneOf(IndicesOptions.WildcardStates.class);
|
||||
|
||||
@Nullable Integer reactiveBatchSize;
|
||||
|
||||
@ -200,6 +203,21 @@ public abstract class BaseQueryBuilder<Q extends BaseQuery, SELF extends BaseQue
|
||||
return reactiveBatchSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 5.1
|
||||
*/
|
||||
@Nullable
|
||||
public Boolean getAllowNoIndices() {
|
||||
return allowNoIndices;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 5.1
|
||||
*/
|
||||
public EnumSet<IndicesOptions.WildcardStates> getExpandWildcards() {
|
||||
return expandWildcards;
|
||||
}
|
||||
|
||||
public SELF withPageable(Pageable pageable) {
|
||||
this.pageable = pageable;
|
||||
return self();
|
||||
@ -392,6 +410,19 @@ public abstract class BaseQueryBuilder<Q extends BaseQuery, SELF extends BaseQue
|
||||
return self();
|
||||
}
|
||||
|
||||
public SELF witAllowNoIndices(@Nullable Boolean allowNoIndices) {
|
||||
this.allowNoIndices = allowNoIndices;
|
||||
return self();
|
||||
}
|
||||
|
||||
public SELF withExpandWildcards(EnumSet<IndicesOptions.WildcardStates> expandWildcards) {
|
||||
|
||||
Assert.notNull(expandWildcards, "expandWildcards must not be null");
|
||||
|
||||
this.expandWildcards = expandWildcards;
|
||||
return self();
|
||||
}
|
||||
|
||||
public abstract Q build();
|
||||
|
||||
private SELF self() {
|
||||
|
@ -80,7 +80,7 @@ public class IndicesOptions {
|
||||
}
|
||||
|
||||
public enum WildcardStates {
|
||||
OPEN, CLOSED, HIDDEN;
|
||||
OPEN, CLOSED, HIDDEN, ALL, NONE;
|
||||
}
|
||||
|
||||
public enum Option {
|
||||
|
@ -18,6 +18,7 @@ package org.springframework.data.elasticsearch.core.query;
|
||||
import java.time.Duration;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@ -460,8 +461,19 @@ public interface Query {
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 4.3
|
||||
* @since 5.1
|
||||
*/
|
||||
@Nullable
|
||||
Boolean getAllowNoIndices();
|
||||
|
||||
/**
|
||||
* @since 5.1
|
||||
*/
|
||||
EnumSet<IndicesOptions.WildcardStates> getExpandWildcards();
|
||||
|
||||
/**
|
||||
* @since 4.3
|
||||
*/
|
||||
enum SearchType {
|
||||
QUERY_THEN_FETCH, DFS_QUERY_THEN_FETCH
|
||||
}
|
||||
|
@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2022 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.query;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 5.1
|
||||
*/
|
||||
public class SearchTemplateQuery extends BaseQuery {
|
||||
|
||||
@Nullable final private String id;
|
||||
@Nullable final String source;
|
||||
@Nullable final Map<String, Object> params;
|
||||
|
||||
public static SearchTemplateQueryBuilder builder() {
|
||||
return new SearchTemplateQueryBuilder();
|
||||
}
|
||||
|
||||
public SearchTemplateQuery(SearchTemplateQueryBuilder builder) {
|
||||
super(builder);
|
||||
|
||||
this.id = builder.getId();
|
||||
this.source = builder.getSource();
|
||||
this.params = builder.getParams();
|
||||
|
||||
if (id == null && source == null) {
|
||||
throw new IllegalArgumentException("Either id or source must be set");
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getSource() {
|
||||
return source;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Map<String, Object> getParams() {
|
||||
return params;
|
||||
}
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2022 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.query;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 5.1
|
||||
*/
|
||||
public class SearchTemplateQueryBuilder extends BaseQueryBuilder<SearchTemplateQuery, SearchTemplateQueryBuilder> {
|
||||
|
||||
@Nullable
|
||||
private String id;
|
||||
@Nullable String source;
|
||||
|
||||
@Nullable
|
||||
Map<String, Object> params;
|
||||
|
||||
@Nullable
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getSource() {
|
||||
return source;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Map<String, Object> getParams() {
|
||||
return params;
|
||||
}
|
||||
|
||||
public SearchTemplateQueryBuilder withId(@Nullable String id) {
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SearchTemplateQueryBuilder withSource(@Nullable String source) {
|
||||
this.source = source;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SearchTemplateQueryBuilder withParams(@Nullable Map<String, Object> params) {
|
||||
this.params = params;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SearchTemplateQuery build() {
|
||||
return new SearchTemplateQuery(this);
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2022 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.script;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* This interfaces defines the operations to access the
|
||||
* <a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.5/script-apis.html">Elasticsearch script API</a>.
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 5.1
|
||||
*/
|
||||
public interface ReactiveScriptOperations {
|
||||
/**
|
||||
* Stores the given script in the Elasticsearch cluster.
|
||||
*
|
||||
* @return {{@literal true} if successful
|
||||
*/
|
||||
Mono<Boolean> putScript(Script script);
|
||||
|
||||
/**
|
||||
* Gest the script with the given name.
|
||||
*
|
||||
* @param name the name of the script
|
||||
* @return Script or null when a script with this name does not exist.
|
||||
*/
|
||||
Mono<Script> getScript(String name);
|
||||
|
||||
/**
|
||||
* Deletes the script with the given name
|
||||
*
|
||||
* @param name the name of the script.
|
||||
* @return true if the request was acknowledged by the cluster.
|
||||
*/
|
||||
Mono<Boolean> deleteScript(String name);
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2022 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.script;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 5.1
|
||||
*/
|
||||
public record Script(String id, String language, String source) {
|
||||
public Script {
|
||||
|
||||
Assert.notNull(id, "id must not be null");
|
||||
Assert.notNull(language, "language must not be null");
|
||||
Assert.notNull(source, "source must not be null");
|
||||
|
||||
}
|
||||
|
||||
public static ScriptBuilder builder() {
|
||||
return new ScriptBuilder();
|
||||
}
|
||||
|
||||
public static final class ScriptBuilder {
|
||||
@Nullable private String id;
|
||||
@Nullable private String language;
|
||||
|
||||
@Nullable private String source;
|
||||
|
||||
private ScriptBuilder() {}
|
||||
|
||||
public ScriptBuilder withId(String id) {
|
||||
|
||||
Assert.notNull(id, "id must not be null");
|
||||
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ScriptBuilder withLanguage(String language) {
|
||||
|
||||
Assert.notNull(language, "language must not be null");
|
||||
|
||||
this.language = language;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ScriptBuilder withSource(String source) {
|
||||
|
||||
Assert.notNull(source, "source must not be null");
|
||||
|
||||
this.source = source;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Script build() {
|
||||
return new Script(id, language, source);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2022 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.script;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* This interfaces defines the operations to access the
|
||||
* <a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.5/script-apis.html">Elasticsearch script API</a>.
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 5.1
|
||||
*/
|
||||
public interface ScriptOperations {
|
||||
/**
|
||||
* Stores the given script in the Elasticsearch cluster.
|
||||
*
|
||||
* @return {{@literal true} if successful
|
||||
*/
|
||||
boolean putScript(Script script);
|
||||
|
||||
/**
|
||||
* Gest the script with the given name.
|
||||
*
|
||||
* @param name the name of the script
|
||||
* @return Script or null when a script with this name does not exist.
|
||||
*/
|
||||
@Nullable
|
||||
Script getScript(String name);
|
||||
|
||||
/**
|
||||
* Deletes the script with the given name
|
||||
*
|
||||
* @param name the name of the script.
|
||||
* @return true if the request was acknowledged by the cluster.
|
||||
*/
|
||||
boolean deleteScript(String name);
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
/**
|
||||
* Classes and interfaces to access to script API of Elasticsearch (https://www.elastic.co/guide/en/elasticsearch/reference/8.5/script-apis.html).
|
||||
*/
|
||||
@org.springframework.lang.NonNullApi
|
||||
@org.springframework.lang.NonNullFields
|
||||
package org.springframework.data.elasticsearch.core.script;
|
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2022 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 org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchTemplateConfiguration;
|
||||
import org.springframework.data.elasticsearch.utils.IndexNameProvider;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
|
||||
/**
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 5.1
|
||||
*/
|
||||
@ContextConfiguration(classes = ReactiveSearchTemplateELCIntegrationTests.Config.class)
|
||||
public class ReactiveSearchTemplateELCIntegrationTests extends ReactiveSearchTemplateIntegrationTests {
|
||||
|
||||
@Configuration
|
||||
@Import({ ReactiveElasticsearchTemplateConfiguration.class })
|
||||
static class Config {
|
||||
@Bean
|
||||
IndexNameProvider indexNameProvider() {
|
||||
return new IndexNameProvider("reactive-searchtemplate");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright 2022 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 static org.assertj.core.api.Assertions.*;
|
||||
import static org.skyscreamer.jsonassert.JSONAssert.*;
|
||||
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Order;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.elasticsearch.ResourceNotFoundException;
|
||||
import org.springframework.data.elasticsearch.annotations.Document;
|
||||
import org.springframework.data.elasticsearch.annotations.Field;
|
||||
import org.springframework.data.elasticsearch.annotations.FieldType;
|
||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
import org.springframework.data.elasticsearch.core.query.SearchTemplateQuery;
|
||||
import org.springframework.data.elasticsearch.core.script.Script;
|
||||
import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest;
|
||||
import org.springframework.data.elasticsearch.utils.IndexNameProvider;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Integration tests for the point in time API.
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 5.1
|
||||
*/
|
||||
@SpringIntegrationTest
|
||||
public abstract class ReactiveSearchTemplateIntegrationTests {
|
||||
private static final String SCRIPT = """
|
||||
{
|
||||
"query": {
|
||||
"bool": {
|
||||
"must": [
|
||||
{
|
||||
"match": {
|
||||
"firstName": "{{firstName}}"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"from": 0,
|
||||
"size": 100
|
||||
}
|
||||
""";
|
||||
private Script script = Script.builder() //
|
||||
.withId("testScript") //
|
||||
.withLanguage("mustache") //
|
||||
.withSource(SCRIPT) //
|
||||
.build();
|
||||
|
||||
@Autowired ReactiveElasticsearchOperations operations;
|
||||
@Autowired IndexNameProvider indexNameProvider;
|
||||
@Nullable ReactiveIndexOperations indexOperations;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
indexNameProvider.increment();
|
||||
indexOperations = operations.indexOps(Person.class);
|
||||
indexOperations.createWithMapping().block();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(Integer.MAX_VALUE)
|
||||
void cleanup() {
|
||||
operations.indexOps(IndexCoordinates.of(indexNameProvider.getPrefix() + '*')).delete().block();
|
||||
}
|
||||
|
||||
@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).block();
|
||||
assertThat(success).isTrue();
|
||||
|
||||
var savedScript = operations.getScript(script.id()).block();
|
||||
assertThat(savedScript).isNotNull();
|
||||
assertThat(savedScript.id()).isEqualTo(script.id());
|
||||
assertThat(savedScript.language()).isEqualTo(script.language());
|
||||
assertEquals(savedScript.source(), script.source(), false);
|
||||
|
||||
success = operations.deleteScript(script.id()).block();
|
||||
assertThat(success).isTrue();
|
||||
|
||||
savedScript = operations.getScript(script.id()).block();
|
||||
assertThat(savedScript).isNull();
|
||||
|
||||
operations.deleteScript(script.id()) //
|
||||
.as(StepVerifier::create) //
|
||||
.verifyError(ResourceNotFoundException.class);
|
||||
}
|
||||
|
||||
@Test // #1891
|
||||
@DisplayName("should search with template")
|
||||
void shouldSearchWithTemplate() {
|
||||
|
||||
var success = operations.putScript(script).block();
|
||||
assertThat(success).isTrue();
|
||||
|
||||
operations.saveAll( //
|
||||
Arrays.asList(new Person("1", "John", "Smith"), //
|
||||
new Person("2", "Willy", "Smith"), //
|
||||
new Person("3", "John", "Myers")), //
|
||||
Person.class).blockLast();
|
||||
var query = SearchTemplateQuery.builder() //
|
||||
.withId(script.id()) //
|
||||
.withParams(Map.of("firstName", "John")) //
|
||||
.build();
|
||||
|
||||
operations.search(query, Person.class)//
|
||||
.as(StepVerifier::create) //
|
||||
.expectNextCount(2L) //
|
||||
.verifyComplete();
|
||||
|
||||
success = operations.deleteScript(script.id()).block();
|
||||
assertThat(success).isTrue();
|
||||
}
|
||||
|
||||
@Document(indexName = "#{@indexNameProvider.indexName()}")
|
||||
record Person( //
|
||||
@Nullable @Id String id, //
|
||||
@Field(type = FieldType.Text) String firstName, //
|
||||
@Field(type = FieldType.Text) String lastName //
|
||||
) {
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2022 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 org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration;
|
||||
import org.springframework.data.elasticsearch.utils.IndexNameProvider;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
|
||||
/**
|
||||
* @author Peter-Josef Meisch
|
||||
*/
|
||||
@ContextConfiguration(classes = {SearchTemplateELCIntegrationTests.Config.class })
|
||||
public class SearchTemplateELCIntegrationTests extends SearchTemplateIntegrationTests {
|
||||
|
||||
@Configuration
|
||||
@Import({ElasticsearchTemplateConfiguration.class })
|
||||
static class Config {
|
||||
@Bean
|
||||
IndexNameProvider indexNameProvider() {
|
||||
return new IndexNameProvider("search-templates");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,145 @@
|
||||
/*
|
||||
* Copyright 2022 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 static org.assertj.core.api.Assertions.*;
|
||||
import static org.skyscreamer.jsonassert.JSONAssert.*;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Order;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.elasticsearch.ResourceNotFoundException;
|
||||
import org.springframework.data.elasticsearch.annotations.Document;
|
||||
import org.springframework.data.elasticsearch.annotations.Field;
|
||||
import org.springframework.data.elasticsearch.annotations.FieldType;
|
||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
import org.springframework.data.elasticsearch.core.query.SearchTemplateQuery;
|
||||
import org.springframework.data.elasticsearch.core.script.Script;
|
||||
import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest;
|
||||
import org.springframework.data.elasticsearch.utils.IndexNameProvider;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Integration tests search template API.
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
*/
|
||||
@SpringIntegrationTest
|
||||
public abstract class SearchTemplateIntegrationTests {
|
||||
|
||||
private static final String SCRIPT = """
|
||||
{
|
||||
"query": {
|
||||
"bool": {
|
||||
"must": [
|
||||
{
|
||||
"match": {
|
||||
"firstName": "{{firstName}}"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"from": 0,
|
||||
"size": 100
|
||||
}
|
||||
""";
|
||||
private Script script = Script.builder() //
|
||||
.withId("testScript") //
|
||||
.withLanguage("mustache") //
|
||||
.withSource(SCRIPT) //
|
||||
.build();
|
||||
|
||||
@Autowired ElasticsearchOperations operations;
|
||||
@Autowired IndexNameProvider indexNameProvider;
|
||||
@Nullable IndexOperations indexOperations;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
indexNameProvider.increment();
|
||||
indexOperations = operations.indexOps(Person.class);
|
||||
indexOperations.createWithMapping();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(Integer.MAX_VALUE)
|
||||
void cleanup() {
|
||||
operations.indexOps(IndexCoordinates.of(indexNameProvider.getPrefix() + '*')).delete();
|
||||
}
|
||||
|
||||
@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);
|
||||
assertThat(success).isTrue();
|
||||
|
||||
var savedScript = operations.getScript(script.id());
|
||||
assertThat(savedScript).isNotNull();
|
||||
assertThat(savedScript.id()).isEqualTo(script.id());
|
||||
assertThat(savedScript.language()).isEqualTo(script.language());
|
||||
assertEquals(savedScript.source(), script.source(), false);
|
||||
|
||||
success = operations.deleteScript(script.id());
|
||||
assertThat(success).isTrue();
|
||||
|
||||
savedScript = operations.getScript(script.id());
|
||||
assertThat(savedScript).isNull();
|
||||
|
||||
assertThatThrownBy(() -> operations.deleteScript(script.id())) //
|
||||
.isInstanceOf(ResourceNotFoundException.class);
|
||||
}
|
||||
|
||||
@Test // #1891
|
||||
@DisplayName("should search with template")
|
||||
void shouldSearchWithTemplate() {
|
||||
|
||||
var success = operations.putScript(script);
|
||||
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()) //
|
||||
.withParams(Map.of("firstName", "John")) //
|
||||
.build();
|
||||
|
||||
var searchHits = operations.search(query, Person.class);
|
||||
|
||||
assertThat(searchHits.getTotalHits()).isEqualTo(2);
|
||||
|
||||
success = operations.deleteScript(script.id());
|
||||
assertThat(success).isTrue();
|
||||
}
|
||||
|
||||
@Document(indexName = "#{@indexNameProvider.indexName()}")
|
||||
record Person( //
|
||||
@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