mirror of
https://github.com/spring-projects/spring-data-elasticsearch.git
synced 2025-06-16 00:52:10 +00:00
parent
4d7d0955f9
commit
efd394370a
1
.gitignore
vendored
1
.gitignore
vendored
@ -24,3 +24,4 @@ target
|
|||||||
|
|
||||||
|
|
||||||
/zap.env
|
/zap.env
|
||||||
|
.localdocker-env
|
||||||
|
@ -17,7 +17,6 @@ The following arguments are available:
|
|||||||
* `refreshIntervall`, defaults to _"1s"_
|
* `refreshIntervall`, defaults to _"1s"_
|
||||||
* `indexStoreType`, defaults to _"fs"_
|
* `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):
|
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,
|
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):
|
||||||
the following methods of the `AbstractElasticsearchTemplate` can be used (this is the base implementation for the
|
|
||||||
different `ElasticsearchOperations` implementations):
|
|
||||||
|
|
||||||
====
|
====
|
||||||
[source,java]
|
[source,java]
|
||||||
@ -310,8 +307,115 @@ SearchHits<Person> searchHits2 = operations.search(query2, Person.class);
|
|||||||
operations.closePointInTime(searchHits2.getPointInTimeId()); <.>
|
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
|
<.> 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
|
<.> 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
|
<.> for the next query, use the id returned from the previous search
|
||||||
<.> when done, close the point in time using the last returned id
|
<.> 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);
|
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.OptimisticLockingFailureException;
|
||||||
import org.springframework.dao.support.PersistenceExceptionTranslator;
|
import org.springframework.dao.support.PersistenceExceptionTranslator;
|
||||||
import org.springframework.data.elasticsearch.NoSuchIndexException;
|
import org.springframework.data.elasticsearch.NoSuchIndexException;
|
||||||
|
import org.springframework.data.elasticsearch.ResourceNotFoundException;
|
||||||
import org.springframework.data.elasticsearch.UncategorizedElasticsearchException;
|
import org.springframework.data.elasticsearch.UncategorizedElasticsearchException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -77,8 +78,9 @@ public class ElasticsearchExceptionTranslator implements PersistenceExceptionTra
|
|||||||
var errorType = response.error().type();
|
var errorType = response.error().type();
|
||||||
var errorReason = response.error().reason() != null ? response.error().reason() : "undefined reason";
|
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) {
|
||||||
|
|
||||||
|
if ("index_not_found_exception".equals(errorType)) {
|
||||||
// noinspection RegExpRedundantEscape
|
// noinspection RegExpRedundantEscape
|
||||||
Pattern pattern = Pattern.compile(".*no such index \\[(.*)\\]");
|
Pattern pattern = Pattern.compile(".*no such index \\[(.*)\\]");
|
||||||
String index = "";
|
String index = "";
|
||||||
@ -88,6 +90,9 @@ public class ElasticsearchExceptionTranslator implements PersistenceExceptionTra
|
|||||||
}
|
}
|
||||||
return new NoSuchIndexException(index);
|
return new NoSuchIndexException(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return new ResourceNotFoundException(errorReason);
|
||||||
|
}
|
||||||
String body = JsonUtils.toJson(response, jsonpMapper);
|
String body = JsonUtils.toJson(response, jsonpMapper);
|
||||||
|
|
||||||
if (errorType != null && errorType.contains("validation_exception")) {
|
if (errorType != null && errorType.contains("validation_exception")) {
|
||||||
|
@ -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.IndexQuery;
|
||||||
import org.springframework.data.elasticsearch.core.query.MoreLikeThisQuery;
|
import org.springframework.data.elasticsearch.core.query.MoreLikeThisQuery;
|
||||||
import org.springframework.data.elasticsearch.core.query.Query;
|
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.UpdateQuery;
|
||||||
import org.springframework.data.elasticsearch.core.query.UpdateResponse;
|
import org.springframework.data.elasticsearch.core.query.UpdateResponse;
|
||||||
import org.springframework.data.elasticsearch.core.reindex.ReindexRequest;
|
import org.springframework.data.elasticsearch.core.reindex.ReindexRequest;
|
||||||
import org.springframework.data.elasticsearch.core.reindex.ReindexResponse;
|
import org.springframework.data.elasticsearch.core.reindex.ReindexResponse;
|
||||||
|
import org.springframework.data.elasticsearch.core.script.Script;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.util.Assert;
|
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) {
|
public <T> SearchHits<T> search(Query query, Class<T> clazz, IndexCoordinates index) {
|
||||||
|
|
||||||
Assert.notNull(query, "query must not be null");
|
Assert.notNull(query, "query must not be null");
|
||||||
|
Assert.notNull(clazz, "clazz must not be null");
|
||||||
Assert.notNull(index, "index 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);
|
SearchRequest searchRequest = requestConverter.searchRequest(query, clazz, index, false);
|
||||||
SearchResponse<EntityAsMap> searchResponse = execute(client -> client.search(searchRequest, EntityAsMap.class));
|
SearchResponse<EntityAsMap> searchResponse = execute(client -> client.search(searchRequest, EntityAsMap.class));
|
||||||
|
|
||||||
|
// noinspection DuplicatedCode
|
||||||
ReadDocumentCallback<T> readDocumentCallback = new ReadDocumentCallback<>(elasticsearchConverter, clazz, index);
|
ReadDocumentCallback<T> readDocumentCallback = new ReadDocumentCallback<>(elasticsearchConverter, clazz, index);
|
||||||
SearchDocumentResponse.EntityCreator<T> entityCreator = getEntityCreator(readDocumentCallback);
|
SearchDocumentResponse.EntityCreator<T> entityCreator = getEntityCreator(readDocumentCallback);
|
||||||
SearchDocumentResponseCallback<SearchHits<T>> callback = new ReadSearchDocumentResponseCallback<>(clazz, index);
|
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));
|
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
|
@Override
|
||||||
protected <T> SearchHits<T> doSearch(MoreLikeThisQuery query, Class<T> clazz, IndexCoordinates index) {
|
protected <T> SearchHits<T> doSearch(MoreLikeThisQuery query, Class<T> clazz, IndexCoordinates index) {
|
||||||
|
|
||||||
@ -513,6 +537,35 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
|
|||||||
|
|
||||||
// endregion
|
// 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
|
// region client callback
|
||||||
/**
|
/**
|
||||||
* Callback interface to be used with {@link #execute(ElasticsearchTemplate.ClientCallback)} for operating directly on
|
* 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);
|
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) {
|
public <T> Mono<ScrollResponse<T>> scroll(ScrollRequest request, Class<T> tDocumentClass) {
|
||||||
|
|
||||||
Assert.notNull(request, "request must not be null");
|
Assert.notNull(request, "request must not be null");
|
||||||
@ -320,4 +343,67 @@ public class ReactiveElasticsearchClient extends ApiClient<ElasticsearchTranspor
|
|||||||
}
|
}
|
||||||
// endregion
|
// 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.BulkOptions;
|
||||||
import org.springframework.data.elasticsearch.core.query.ByQueryResponse;
|
import org.springframework.data.elasticsearch.core.query.ByQueryResponse;
|
||||||
import org.springframework.data.elasticsearch.core.query.Query;
|
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.UpdateQuery;
|
||||||
import org.springframework.data.elasticsearch.core.query.UpdateResponse;
|
import org.springframework.data.elasticsearch.core.query.UpdateResponse;
|
||||||
import org.springframework.data.elasticsearch.core.reindex.ReindexRequest;
|
import org.springframework.data.elasticsearch.core.reindex.ReindexRequest;
|
||||||
import org.springframework.data.elasticsearch.core.reindex.ReindexResponse;
|
import org.springframework.data.elasticsearch.core.reindex.ReindexResponse;
|
||||||
|
import org.springframework.data.elasticsearch.core.script.Script;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
@ -341,12 +343,18 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
|
|||||||
@Override
|
@Override
|
||||||
protected Flux<SearchDocument> doFind(Query query, Class<?> clazz, IndexCoordinates index) {
|
protected Flux<SearchDocument> doFind(Query query, Class<?> 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 Flux.defer(() -> doSearch(searchTemplateQuery, clazz, index));
|
||||||
|
} else {
|
||||||
return Flux.defer(() -> {
|
return Flux.defer(() -> {
|
||||||
boolean queryIsUnbounded = !(query.getPageable().isPaged() || query.isLimiting());
|
boolean queryIsUnbounded = !(query.getPageable().isPaged() || query.isLimiting());
|
||||||
|
|
||||||
return queryIsUnbounded ? doFindUnbounded(query, clazz, index) : doFindBounded(query, clazz, index);
|
return queryIsUnbounded ? doFindUnbounded(query, clazz, index) : doFindBounded(query, clazz, index);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Flux<SearchDocument> doFindUnbounded(Query query, Class<?> clazz, IndexCoordinates 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));
|
.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
|
@Override
|
||||||
protected <T> Mono<SearchDocumentResponse> doFindForResponse(Query query, Class<?> clazz, IndexCoordinates index) {
|
protected <T> Mono<SearchDocumentResponse> doFindForResponse(Query query, Class<?> clazz, IndexCoordinates index) {
|
||||||
|
|
||||||
@ -519,6 +538,37 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
|
|||||||
|
|
||||||
// endregion
|
// 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
|
@Override
|
||||||
public Mono<String> getVendor() {
|
public Mono<String> getVendor() {
|
||||||
return Mono.just("Elasticsearch");
|
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.query.*;
|
||||||
import org.springframework.data.elasticsearch.core.reindex.ReindexRequest;
|
import org.springframework.data.elasticsearch.core.reindex.ReindexRequest;
|
||||||
import org.springframework.data.elasticsearch.core.reindex.Remote;
|
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.data.elasticsearch.support.DefaultStringObjectMap;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
import org.springframework.util.CollectionUtils;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -99,6 +101,7 @@ import org.springframework.util.StringUtils;
|
|||||||
* @author scoobyzhang
|
* @author scoobyzhang
|
||||||
* @since 4.4
|
* @since 4.4
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings("ClassCanBeRecord")
|
||||||
class RequestConverter {
|
class RequestConverter {
|
||||||
|
|
||||||
// the default max result window size of Elasticsearch
|
// the default max result window size of Elasticsearch
|
||||||
@ -1163,13 +1166,26 @@ class RequestConverter {
|
|||||||
|
|
||||||
builder //
|
builder //
|
||||||
.version(true) //
|
.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();
|
var pointInTime = query.getPointInTime();
|
||||||
if (pointInTime != null) {
|
if (pointInTime != null) {
|
||||||
builder.pit(pb -> pb.id(pointInTime.id()).keepAlive(time(pointInTime.keepAlive())));
|
builder.pit(pb -> pb.id(pointInTime.id()).keepAlive(time(pointInTime.keepAlive())));
|
||||||
} else {
|
} 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) {
|
if (query.getRoute() != null) {
|
||||||
builder.routing(query.getRoute());
|
builder.routing(query.getRoute());
|
||||||
@ -1192,8 +1208,6 @@ class RequestConverter {
|
|||||||
builder.from(0).size(INDEX_MAX_RESULT_WINDOW);
|
builder.from(0).size(INDEX_MAX_RESULT_WINDOW);
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.source(getSourceConfig(query));
|
|
||||||
|
|
||||||
if (!isEmpty(query.getFields())) {
|
if (!isEmpty(query.getFields())) {
|
||||||
builder.fields(fb -> {
|
builder.fields(fb -> {
|
||||||
query.getFields().forEach(fb::field);
|
query.getFields().forEach(fb::field);
|
||||||
@ -1217,8 +1231,6 @@ class RequestConverter {
|
|||||||
builder.minScore((double) query.getMinScore());
|
builder.minScore((double) query.getMinScore());
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.searchType(searchType(query.getSearchType()));
|
|
||||||
|
|
||||||
if (query.getSort() != null) {
|
if (query.getSort() != null) {
|
||||||
List<SortOptions> sortOptions = getSortOptions(query.getSort(), persistentEntity);
|
List<SortOptions> sortOptions = getSortOptions(query.getSort(), persistentEntity);
|
||||||
|
|
||||||
@ -1241,8 +1253,6 @@ class RequestConverter {
|
|||||||
builder.trackTotalHits(th -> th.count(query.getTrackTotalHitsUpTo()));
|
builder.trackTotalHits(th -> th.count(query.getTrackTotalHitsUpTo()));
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.timeout(timeStringMs(query.getTimeout()));
|
|
||||||
|
|
||||||
if (query.getExplain()) {
|
if (query.getExplain()) {
|
||||||
builder.explain(true);
|
builder.explain(true);
|
||||||
}
|
}
|
||||||
@ -1254,8 +1264,6 @@ class RequestConverter {
|
|||||||
|
|
||||||
query.getRescorerQueries().forEach(rescorerQuery -> builder.rescore(getRescore(rescorerQuery)));
|
query.getRescorerQueries().forEach(rescorerQuery -> builder.rescore(getRescore(rescorerQuery)));
|
||||||
|
|
||||||
builder.requestCache(query.getRequestCache());
|
|
||||||
|
|
||||||
if (!query.getRuntimeFields().isEmpty()) {
|
if (!query.getRuntimeFields().isEmpty()) {
|
||||||
|
|
||||||
Map<String, RuntimeField> runtimeMappings = new HashMap<>();
|
Map<String, RuntimeField> runtimeMappings = new HashMap<>();
|
||||||
@ -1540,8 +1548,69 @@ class RequestConverter {
|
|||||||
return ClosePointInTimeRequest.of(cpit -> cpit.id(pit));
|
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
|
// 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
|
// region helper functions
|
||||||
|
|
||||||
public <T> T fromJson(String json, JsonpDeserializer<T> deserializer) {
|
public <T> T fromJson(String json, JsonpDeserializer<T> deserializer) {
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.springframework.data.elasticsearch.client.elc;
|
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.BulkIndexByScrollFailure;
|
||||||
import co.elastic.clients.elasticsearch._types.ErrorCause;
|
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._types.query_dsl.Query;
|
||||||
import co.elastic.clients.elasticsearch.cluster.HealthResponse;
|
import co.elastic.clients.elasticsearch.cluster.HealthResponse;
|
||||||
import co.elastic.clients.elasticsearch.core.DeleteByQueryResponse;
|
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.UpdateByQueryResponse;
|
||||||
import co.elastic.clients.elasticsearch.core.mget.MultiGetError;
|
import co.elastic.clients.elasticsearch.core.mget.MultiGetError;
|
||||||
import co.elastic.clients.elasticsearch.core.mget.MultiGetResponseItem;
|
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.ByQueryResponse;
|
||||||
import org.springframework.data.elasticsearch.core.query.StringQuery;
|
import org.springframework.data.elasticsearch.core.query.StringQuery;
|
||||||
import org.springframework.data.elasticsearch.core.reindex.ReindexResponse;
|
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.data.elasticsearch.support.DefaultStringObjectMap;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
@ -421,6 +423,22 @@ class ResponseConverter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// endregion
|
// 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
|
// region helper functions
|
||||||
|
|
||||||
private long timeToLong(Time time) {
|
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._types.aggregations.Aggregate;
|
||||||
import co.elastic.clients.elasticsearch.core.SearchResponse;
|
import co.elastic.clients.elasticsearch.core.SearchResponse;
|
||||||
|
import co.elastic.clients.elasticsearch.core.SearchTemplateResponse;
|
||||||
import co.elastic.clients.elasticsearch.core.search.CompletionSuggest;
|
import co.elastic.clients.elasticsearch.core.search.CompletionSuggest;
|
||||||
import co.elastic.clients.elasticsearch.core.search.CompletionSuggestOption;
|
import co.elastic.clients.elasticsearch.core.search.CompletionSuggestOption;
|
||||||
import co.elastic.clients.elasticsearch.core.search.Hit;
|
import co.elastic.clients.elasticsearch.core.search.Hit;
|
||||||
@ -70,6 +71,7 @@ class SearchDocumentResponseBuilder {
|
|||||||
|
|
||||||
Assert.notNull(responseBody, "responseBody must not be null");
|
Assert.notNull(responseBody, "responseBody must not be null");
|
||||||
Assert.notNull(entityCreator, "entityCreator 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();
|
HitsMetadata<EntityAsMap> hitsMetadata = responseBody.hits();
|
||||||
String scrollId = responseBody.scrollId();
|
String scrollId = responseBody.scrollId();
|
||||||
@ -80,6 +82,31 @@ class SearchDocumentResponseBuilder {
|
|||||||
return from(hitsMetadata, scrollId, pointInTimeId, aggregations, suggest, entityCreator, jsonpMapper);
|
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
|
* creates a {@link SearchDocumentResponseBuilder} from {@link HitsMetadata} with the given scrollId aggregations and
|
||||||
* suggestES
|
* suggestES
|
||||||
|
@ -26,10 +26,14 @@ import co.elastic.clients.elasticsearch.core.search.HighlighterType;
|
|||||||
import co.elastic.clients.elasticsearch.core.search.ScoreMode;
|
import co.elastic.clients.elasticsearch.core.search.ScoreMode;
|
||||||
|
|
||||||
import java.time.Duration;
|
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.RefreshPolicy;
|
||||||
import org.springframework.data.elasticsearch.core.query.GeoDistanceOrder;
|
import org.springframework.data.elasticsearch.core.query.GeoDistanceOrder;
|
||||||
import org.springframework.data.elasticsearch.core.query.IndexQuery;
|
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.Order;
|
||||||
import org.springframework.data.elasticsearch.core.query.Query;
|
import org.springframework.data.elasticsearch.core.query.Query;
|
||||||
import org.springframework.data.elasticsearch.core.query.RescorerQuery;
|
import org.springframework.data.elasticsearch.core.query.RescorerQuery;
|
||||||
@ -125,6 +129,7 @@ final class TypeUtils {
|
|||||||
default -> throw new IllegalStateException("Unexpected value: " + fieldValue._kind());
|
default -> throw new IllegalStateException("Unexpected value: " + fieldValue._kind());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
static Object toObject(@Nullable FieldValue fieldValue) {
|
static Object toObject(@Nullable FieldValue fieldValue) {
|
||||||
|
|
||||||
@ -391,4 +396,14 @@ final class TypeUtils {
|
|||||||
static Float toFloat(@Nullable Long value) {
|
static Float toFloat(@Nullable Long value) {
|
||||||
return value != null ? Float.valueOf(value) : null;
|
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;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public NativeSearchQueryBuilder withIndicesOptions(IndicesOptions indicesOptions) {
|
|
||||||
this.indicesOptions = indicesOptions;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @since 4.2
|
* @since 4.2
|
||||||
*/
|
*/
|
||||||
@ -306,8 +301,8 @@ public class NativeSearchQueryBuilder extends BaseQueryBuilder<NativeSearchQuery
|
|||||||
nativeSearchQuery.setSearchType(Query.SearchType.valueOf(searchType.name()));
|
nativeSearchQuery.setSearchType(Query.SearchType.valueOf(searchType.name()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (indicesOptions != null) {
|
if (getIndicesOptions() != null) {
|
||||||
nativeSearchQuery.setIndicesOptions(indicesOptions);
|
nativeSearchQuery.setIndicesOptions(getIndicesOptions());
|
||||||
}
|
}
|
||||||
|
|
||||||
nativeSearchQuery.setTrackTotalHits(trackTotalHits);
|
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.query.UpdateResponse;
|
||||||
import org.springframework.data.elasticsearch.core.routing.DefaultRoutingResolver;
|
import org.springframework.data.elasticsearch.core.routing.DefaultRoutingResolver;
|
||||||
import org.springframework.data.elasticsearch.core.routing.RoutingResolver;
|
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.elasticsearch.support.VersionInfo;
|
||||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||||
import org.springframework.data.mapping.callback.EntityCallbacks;
|
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
|
// region Document callbacks
|
||||||
protected interface DocumentCallback<T> {
|
protected interface DocumentCallback<T> {
|
||||||
@Nullable
|
@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.query.SeqNoPrimaryTerm;
|
||||||
import org.springframework.data.elasticsearch.core.routing.DefaultRoutingResolver;
|
import org.springframework.data.elasticsearch.core.routing.DefaultRoutingResolver;
|
||||||
import org.springframework.data.elasticsearch.core.routing.RoutingResolver;
|
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.core.suggest.response.Suggest;
|
||||||
import org.springframework.data.elasticsearch.support.VersionInfo;
|
import org.springframework.data.elasticsearch.support.VersionInfo;
|
||||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||||
@ -584,12 +585,14 @@ abstract public class AbstractReactiveElasticsearchTemplate
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback to convert a {@link SearchDocument} into different other classes
|
* Callback to convert a {@link SearchDocument} into different other classes
|
||||||
|
*
|
||||||
* @param <T> the entity type
|
* @param <T> the entity type
|
||||||
*/
|
*/
|
||||||
protected interface SearchDocumentCallback<T> {
|
protected interface SearchDocumentCallback<T> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* converts a {@link SearchDocument} to an entity
|
* converts a {@link SearchDocument} to an entity
|
||||||
|
*
|
||||||
* @param searchDocument
|
* @param searchDocument
|
||||||
* @return the entity in a MOno
|
* @return the entity in a MOno
|
||||||
*/
|
*/
|
||||||
@ -597,6 +600,7 @@ abstract public class AbstractReactiveElasticsearchTemplate
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* converts a {@link SearchDocument} into a SearchHit
|
* converts a {@link SearchDocument} into a SearchHit
|
||||||
|
*
|
||||||
* @param searchDocument
|
* @param searchDocument
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
@ -627,6 +631,26 @@ abstract public class AbstractReactiveElasticsearchTemplate
|
|||||||
|
|
||||||
// endregion
|
// 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
|
// region Helper methods
|
||||||
@Override
|
@Override
|
||||||
public IndexCoordinates getIndexCoordinatesFor(Class<?> clazz) {
|
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.convert.ElasticsearchConverter;
|
||||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||||
import org.springframework.data.elasticsearch.core.routing.RoutingResolver;
|
import org.springframework.data.elasticsearch.core.routing.RoutingResolver;
|
||||||
|
import org.springframework.data.elasticsearch.core.script.ScriptOperations;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -36,7 +37,7 @@ import org.springframework.lang.Nullable;
|
|||||||
* @author Dmitriy Yakovlev
|
* @author Dmitriy Yakovlev
|
||||||
* @author Peter-Josef Meisch
|
* @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
|
* 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.ElasticsearchPersistentEntity;
|
||||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||||
import org.springframework.data.elasticsearch.core.routing.RoutingResolver;
|
import org.springframework.data.elasticsearch.core.routing.RoutingResolver;
|
||||||
|
import org.springframework.data.elasticsearch.core.script.ReactiveScriptOperations;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -37,7 +38,8 @@ import org.springframework.lang.Nullable;
|
|||||||
* @author Peter-Josef Meisch
|
* @author Peter-Josef Meisch
|
||||||
* @since 3.2
|
* @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.
|
* 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.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.EnumSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@ -75,9 +76,11 @@ public class BaseQuery implements Query {
|
|||||||
protected List<IdWithRouting> idsWithRouting = Collections.emptyList();
|
protected List<IdWithRouting> idsWithRouting = Collections.emptyList();
|
||||||
protected final List<RuntimeField> runtimeFields = new ArrayList<>();
|
protected final List<RuntimeField> runtimeFields = new ArrayList<>();
|
||||||
@Nullable protected PointInTime pointInTime;
|
@Nullable protected PointInTime pointInTime;
|
||||||
|
|
||||||
private boolean queryIsUpdatedByConverter = false;
|
private boolean queryIsUpdatedByConverter = false;
|
||||||
@Nullable private Integer reactiveBatchSize = null;
|
@Nullable private Integer reactiveBatchSize = null;
|
||||||
|
@Nullable private Boolean allowNoIndices = null;
|
||||||
|
|
||||||
|
private EnumSet<IndicesOptions.WildcardStates> expandWildcards;
|
||||||
|
|
||||||
public BaseQuery() {}
|
public BaseQuery() {}
|
||||||
|
|
||||||
@ -109,6 +112,8 @@ public class BaseQuery implements Query {
|
|||||||
this.idsWithRouting = builder.getIdsWithRouting();
|
this.idsWithRouting = builder.getIdsWithRouting();
|
||||||
this.pointInTime = builder.getPointInTime();
|
this.pointInTime = builder.getPointInTime();
|
||||||
this.reactiveBatchSize = builder.getReactiveBatchSize();
|
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) {
|
public void setReactiveBatchSize(Integer reactiveBatchSize) {
|
||||||
this.reactiveBatchSize = 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.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.EnumSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.springframework.data.domain.Pageable;
|
import org.springframework.data.domain.Pageable;
|
||||||
@ -45,26 +46,28 @@ public abstract class BaseQueryBuilder<Q extends BaseQuery, SELF extends BaseQue
|
|||||||
private float minScore;
|
private float minScore;
|
||||||
private final Collection<String> ids = new ArrayList<>();
|
private final Collection<String> ids = new ArrayList<>();
|
||||||
@Nullable private String route;
|
@Nullable private String route;
|
||||||
protected Query.SearchType searchType = Query.SearchType.QUERY_THEN_FETCH;
|
private Query.SearchType searchType = Query.SearchType.QUERY_THEN_FETCH;
|
||||||
@Nullable protected IndicesOptions indicesOptions;
|
@Nullable private IndicesOptions indicesOptions;
|
||||||
private boolean trackScores;
|
private boolean trackScores;
|
||||||
@Nullable private String preference;
|
@Nullable private String preference;
|
||||||
@Nullable private Integer maxResults;
|
@Nullable private Integer maxResults;
|
||||||
@Nullable protected HighlightQuery highlightQuery;
|
@Nullable private HighlightQuery highlightQuery;
|
||||||
@Nullable private Boolean trackTotalHits;
|
@Nullable private Boolean trackTotalHits;
|
||||||
@Nullable protected Integer trackTotalHitsUpTo;
|
@Nullable private Integer trackTotalHitsUpTo;
|
||||||
@Nullable protected Duration scrollTime;
|
@Nullable private Duration scrollTime;
|
||||||
@Nullable protected Duration timeout;
|
@Nullable private Duration timeout;
|
||||||
boolean explain = false;
|
boolean explain = false;
|
||||||
@Nullable protected List<Object> searchAfter;
|
@Nullable private List<Object> searchAfter;
|
||||||
|
|
||||||
@Nullable private List<IndexBoost> indicesBoost;
|
@Nullable private List<IndexBoost> indicesBoost;
|
||||||
protected final List<RescorerQuery> rescorerQueries = new ArrayList<>();
|
protected final List<RescorerQuery> rescorerQueries = new ArrayList<>();
|
||||||
|
|
||||||
@Nullable protected Boolean requestCache;
|
@Nullable private Boolean requestCache;
|
||||||
protected final List<Query.IdWithRouting> idsWithRouting = new ArrayList<>();
|
private final List<Query.IdWithRouting> idsWithRouting = new ArrayList<>();
|
||||||
protected final List<RuntimeField> runtimeFields = new ArrayList<>();
|
private final List<RuntimeField> runtimeFields = new ArrayList<>();
|
||||||
@Nullable protected Query.PointInTime pointInTime;
|
@Nullable private Query.PointInTime pointInTime;
|
||||||
|
@Nullable private Boolean allowNoIndices;
|
||||||
|
private EnumSet<IndicesOptions.WildcardStates> expandWildcards = EnumSet.noneOf(IndicesOptions.WildcardStates.class);
|
||||||
|
|
||||||
@Nullable Integer reactiveBatchSize;
|
@Nullable Integer reactiveBatchSize;
|
||||||
|
|
||||||
@ -200,6 +203,21 @@ public abstract class BaseQueryBuilder<Q extends BaseQuery, SELF extends BaseQue
|
|||||||
return reactiveBatchSize;
|
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) {
|
public SELF withPageable(Pageable pageable) {
|
||||||
this.pageable = pageable;
|
this.pageable = pageable;
|
||||||
return self();
|
return self();
|
||||||
@ -392,6 +410,19 @@ public abstract class BaseQueryBuilder<Q extends BaseQuery, SELF extends BaseQue
|
|||||||
return self();
|
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();
|
public abstract Q build();
|
||||||
|
|
||||||
private SELF self() {
|
private SELF self() {
|
||||||
|
@ -80,7 +80,7 @@ public class IndicesOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public enum WildcardStates {
|
public enum WildcardStates {
|
||||||
OPEN, CLOSED, HIDDEN;
|
OPEN, CLOSED, HIDDEN, ALL, NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum Option {
|
public enum Option {
|
||||||
|
@ -18,6 +18,7 @@ package org.springframework.data.elasticsearch.core.query;
|
|||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.EnumSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@ -459,6 +460,17 @@ public interface Query {
|
|||||||
return 500;
|
return 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 5.1
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
Boolean getAllowNoIndices();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 5.1
|
||||||
|
*/
|
||||||
|
EnumSet<IndicesOptions.WildcardStates> getExpandWildcards();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @since 4.3
|
* @since 4.3
|
||||||
*/
|
*/
|
||||||
|
@ -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