Implement support for reindex API.

Original Pull Request #2070 
Closes #1529
This commit is contained in:
Onizuka 2022-01-27 03:54:00 +08:00 committed by GitHub
parent cf3e46bf68
commit c5db583048
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 1479 additions and 20 deletions

View File

@ -83,11 +83,13 @@ import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.GetAliasesResponse;
import org.elasticsearch.client.Request;
import org.elasticsearch.client.indices.*;
import org.elasticsearch.client.tasks.TaskSubmissionResponse;
import org.elasticsearch.core.TimeValue;
import org.elasticsearch.index.get.GetResult;
import org.elasticsearch.index.reindex.BulkByScrollResponse;
import org.elasticsearch.index.reindex.DeleteByQueryRequest;
import org.elasticsearch.index.reindex.UpdateByQueryRequest;
import org.elasticsearch.index.reindex.ReindexRequest;
import org.elasticsearch.rest.BytesRestResponse;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.script.mustache.SearchTemplateRequest;
@ -143,6 +145,7 @@ import org.springframework.web.reactive.function.client.WebClient.RequestBodySpe
* @author Brian Clozel
* @author Farid Faoudi
* @author George Popides
* @author Sijia Liu
* @since 3.2
* @see ClientConfiguration
* @see ReactiveRestClients
@ -509,6 +512,19 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
.next();
}
@Override
public Mono<BulkByScrollResponse> reindex(HttpHeaders headers, ReindexRequest reindexRequest) {
return sendRequest(reindexRequest, requestCreator.reindex(), BulkByScrollResponse.class, headers)
.next();
}
@Override
public Mono<String> submitReindex(HttpHeaders headers, ReindexRequest reindexRequest) {
return sendRequest(reindexRequest, requestCreator.submitReindex(), TaskSubmissionResponse.class, headers)
.next()
.map(TaskSubmissionResponse::getTask);
}
@Override
public <T> Mono<T> execute(ReactiveElasticsearchClientCallback<T> callback) {

View File

@ -54,6 +54,7 @@ import org.elasticsearch.index.get.GetResult;
import org.elasticsearch.index.reindex.BulkByScrollResponse;
import org.elasticsearch.index.reindex.DeleteByQueryRequest;
import org.elasticsearch.index.reindex.UpdateByQueryRequest;
import org.elasticsearch.index.reindex.ReindexRequest;
import org.elasticsearch.script.mustache.SearchTemplateRequest;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.aggregations.Aggregation;
@ -76,6 +77,7 @@ import org.springframework.web.reactive.function.client.WebClient;
* @author Henrique Amaral
* @author Thomas Geese
* @author Farid Faoudi
* @author Sijia Liu
* @since 3.2
* @see ClientConfiguration
* @see ReactiveRestClients
@ -713,6 +715,75 @@ public interface ReactiveElasticsearchClient {
*/
Mono<BulkResponse> bulk(HttpHeaders headers, BulkRequest bulkRequest);
/**
* Execute the given {@link ReindexRequest} against the {@literal reindex} API.
*
* @param consumer must not be {@literal null}
* @return the {@link Mono} emitting the response
* @since 4.4
*/
default Mono<BulkByScrollResponse> reindex(Consumer<ReindexRequest> consumer){
ReindexRequest reindexRequest = new ReindexRequest();
consumer.accept(reindexRequest);
return reindex(reindexRequest);
}
/**
* Execute the given {@link ReindexRequest} against the {@literal reindex} API.
*
* @param reindexRequest must not be {@literal null}
* @return the {@link Mono} emitting the response
* @since 4.4
*/
default Mono<BulkByScrollResponse> reindex(ReindexRequest reindexRequest){
return reindex(HttpHeaders.EMPTY, reindexRequest);
}
/**
* Execute the given {@link ReindexRequest} against the {@literal reindex} API.
*
* @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}.
* @param reindexRequest must not be {@literal null}
* @return the {@link Mono} emitting the response
* @since 4.4
*/
Mono<BulkByScrollResponse> reindex(HttpHeaders headers, ReindexRequest reindexRequest);
/**
* Execute the given {@link ReindexRequest} against the {@literal reindex} API.
*
* @param consumer must not be {@literal null}
* @return the {@link Mono} emitting the task id
* @since 4.4
*/
default Mono<String> submitReindex(Consumer<ReindexRequest> consumer){
ReindexRequest reindexRequest = new ReindexRequest();
consumer.accept(reindexRequest);
return submitReindex(reindexRequest);
}
/**
* Execute the given {@link ReindexRequest} against the {@literal reindex} API.
*
* @param reindexRequest must not be {@literal null}
* @return the {@link Mono} emitting the task id
* @since 4.4
*/
default Mono<String> submitReindex(ReindexRequest reindexRequest){
return submitReindex(HttpHeaders.EMPTY, reindexRequest);
}
/**
* Execute the given {@link ReindexRequest} against the {@literal reindex} API.
*
* @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}.
* @param reindexRequest must not be {@literal null}
* @return the {@link Mono} emitting the task id
* @since 4.4
*/
Mono<String> submitReindex(HttpHeaders headers, ReindexRequest reindexRequest);
/**
* Compose the actual command/s to run against Elasticsearch using the underlying {@link WebClient connection}.
* {@link #execute(ReactiveElasticsearchClientCallback) Execute} selects an active server from the available ones and

View File

@ -49,6 +49,7 @@ import org.elasticsearch.client.indices.IndexTemplatesExistRequest;
import org.elasticsearch.client.indices.PutIndexTemplateRequest;
import org.elasticsearch.client.indices.PutMappingRequest;
import org.elasticsearch.index.reindex.DeleteByQueryRequest;
import org.elasticsearch.index.reindex.ReindexRequest;
import org.elasticsearch.index.reindex.UpdateByQueryRequest;
import org.elasticsearch.script.mustache.SearchTemplateRequest;
import org.springframework.data.elasticsearch.UncategorizedElasticsearchException;
@ -289,4 +290,14 @@ public interface RequestCreator {
default Function<ClusterHealthRequest, Request> clusterHealth() {
return RequestConverters::clusterHealth;
}
/**
* @since 4.4
*/
default Function<ReindexRequest, Request> reindex() { return RequestConverters::reindex; }
/**
* @since 4.4
*/
default Function<ReindexRequest, Request> submitReindex() { return RequestConverters::submitReindex; }
}

View File

@ -532,11 +532,11 @@ public class RequestConverters {
return request;
}
public static Request reindex(ReindexRequest reindexRequest) throws IOException {
public static Request reindex(ReindexRequest reindexRequest) {
return prepareReindexRequest(reindexRequest, true);
}
static Request submitReindex(ReindexRequest reindexRequest) throws IOException {
public static Request submitReindex(ReindexRequest reindexRequest) {
return prepareReindexRequest(reindexRequest, false);
}
@ -547,9 +547,16 @@ public class RequestConverters {
.withTimeout(reindexRequest.getTimeout()).withWaitForActiveShards(reindexRequest.getWaitForActiveShards())
.withRequestsPerSecond(reindexRequest.getRequestsPerSecond());
if(reindexRequest.getDestination().isRequireAlias()){
params.putParam("require_alias", Boolean.TRUE.toString());
}
if (reindexRequest.getScrollTime() != null) {
params.putParam("scroll", reindexRequest.getScrollTime());
}
params.putParam("slices", Integer.toString(reindexRequest.getSlices()));
if(reindexRequest.getMaxDocs() > -1){
params.putParam("max_docs", Integer.toString(reindexRequest.getMaxDocs()));
}
request.setEntity(createEntity(reindexRequest, REQUEST_BODY_CONTENT_TYPE));
return request;
}

View File

@ -18,6 +18,8 @@ package org.springframework.data.elasticsearch.core;
import java.util.Collection;
import java.util.List;
import org.springframework.data.elasticsearch.core.reindex.ReindexRequest;
import org.springframework.data.elasticsearch.core.reindex.ReindexResponse;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.BulkOptions;
import org.springframework.data.elasticsearch.core.query.ByQueryResponse;
@ -34,6 +36,7 @@ import org.springframework.lang.Nullable;
*
* @author Peter-Josef Meisch
* @author Farid Faoudi
* @author Sijia Liu
* @since 4.0
*/
public interface DocumentOperations {
@ -322,4 +325,26 @@ public interface DocumentOperations {
* @since 4.2
*/
ByQueryResponse updateByQuery(UpdateQuery updateQuery, IndexCoordinates index);
/**
* Copies documents from a source to a destination.
* The source can be any existing index, alias, or data stream. The destination must differ from the source.
* For example, you cannot reindex a data stream into itself.
* (@see https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-reindex.html)
*
* @param reindexRequest reindex request parameters
* @return the reindex response
* @since 4.4
*/
ReindexResponse reindex(ReindexRequest reindexRequest);
/**
* Submits a reindex task.
* (@see https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-reindex.html)
*
* @param reindexRequest reindex request parameters
* @return the task id
* @since 4.4
*/
String submitReindex(ReindexRequest reindexRequest);
}

View File

@ -61,7 +61,9 @@ import org.springframework.data.elasticsearch.core.cluster.ClusterOperations;
import org.springframework.data.elasticsearch.core.cluster.ElasticsearchClusterOperations;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
import org.springframework.data.elasticsearch.core.document.DocumentAdapters;
import org.springframework.data.elasticsearch.core.reindex.ReindexRequest;
import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse;
import org.springframework.data.elasticsearch.core.reindex.ReindexResponse;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.BulkOptions;
import org.springframework.data.elasticsearch.core.query.ByQueryResponse;
@ -106,6 +108,7 @@ import org.springframework.util.Assert;
* @author Gyula Attila Csorogi
* @author Massimiliano Poggi
* @author Farid Faoudi
* @author Sijia Liu
* @since 4.4
*/
public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
@ -277,6 +280,26 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
return ResponseConverter.byQueryResponseOf(bulkByScrollResponse);
}
@Override
public ReindexResponse reindex(ReindexRequest postReindexRequest) {
Assert.notNull(postReindexRequest, "postReindexRequest must not be null");
final org.elasticsearch.index.reindex.ReindexRequest reindexRequest = requestFactory.reindexRequest(postReindexRequest);
final BulkByScrollResponse bulkByScrollResponse = execute(
client -> client.reindex(reindexRequest, RequestOptions.DEFAULT));
return ResponseConverter.reindexResponseOf(bulkByScrollResponse);
}
@Override
public String submitReindex(ReindexRequest postReindexRequest) {
Assert.notNull(postReindexRequest, "postReindexRequest must not be null");
final org.elasticsearch.index.reindex.ReindexRequest reindexRequest = requestFactory.reindexRequest(postReindexRequest);
return execute(
client -> client.submitReindexTask(reindexRequest, RequestOptions.DEFAULT).getTask());
}
public List<IndexedObjectInformation> doBulkOperation(List<?> queries, BulkOptions bulkOptions,
IndexCoordinates index) {
BulkRequest bulkRequest = prepareWriteRequest(requestFactory.bulkRequest(queries, bulkOptions, index));

View File

@ -15,6 +15,8 @@
*/
package org.springframework.data.elasticsearch.core;
import org.springframework.data.elasticsearch.core.reindex.ReindexRequest;
import org.springframework.data.elasticsearch.core.reindex.ReindexResponse;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@ -38,6 +40,7 @@ import org.springframework.util.Assert;
* @author Aleksei Arsenev
* @author Roman Puchkovskiy
* @author Farid Faoudi
* @author Sijia Liu
* @since 4.0
*/
public interface ReactiveDocumentOperations {
@ -302,4 +305,26 @@ public interface ReactiveDocumentOperations {
* @since 4.2
*/
Mono<ByQueryResponse> updateByQuery(UpdateQuery updateQuery, IndexCoordinates index);
/**
* Copies documents from a source to a destination.
* The source can be any existing index, alias, or data stream. The destination must differ from the source.
* For example, you cannot reindex a data stream into itself.
* (@see https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-reindex.html)
*
* @param reindexRequest reindex request parameters
* @return a {@link Mono} emitting the reindex response
* @since 4.4
*/
Mono<ReindexResponse> reindex(ReindexRequest reindexRequest);
/**
* Submits a reindex task.
* (@see https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-reindex.html)
*
* @param reindexRequest reindex request parameters
* @return a {@link Mono} emitting the {@literal task} id.
* @since 4.4
*/
Mono<String> submitReindex(ReindexRequest reindexRequest);
}

View File

@ -70,6 +70,8 @@ import org.springframework.data.elasticsearch.core.event.ReactiveAfterConvertCal
import org.springframework.data.elasticsearch.core.event.ReactiveAfterLoadCallback;
import org.springframework.data.elasticsearch.core.event.ReactiveAfterSaveCallback;
import org.springframework.data.elasticsearch.core.event.ReactiveBeforeConvertCallback;
import org.springframework.data.elasticsearch.core.reindex.ReindexRequest;
import org.springframework.data.elasticsearch.core.reindex.ReindexResponse;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
@ -105,6 +107,7 @@ import org.springframework.util.Assert;
* @author Russell Parry
* @author Thomas Geese
* @author Farid Faoudi
* @author Sijia Liu
* @since 3.2
*/
public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOperations, ApplicationContextAware {
@ -609,6 +612,28 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
});
}
@Override
public Mono<ReindexResponse> reindex(ReindexRequest postReindexRequest) {
Assert.notNull(postReindexRequest, "postReindexRequest must not be null");
return Mono.defer(() -> {
final org.elasticsearch.index.reindex.ReindexRequest reindexRequest = requestFactory.reindexRequest(postReindexRequest);
return Mono.from(execute(client -> client.reindex(reindexRequest))).map(ResponseConverter::reindexResponseOf);
});
}
@Override
public Mono<String> submitReindex(ReindexRequest postReindexRequest) {
Assert.notNull(postReindexRequest, "postReindexRequest must not be null");
return Mono.defer(() -> {
final org.elasticsearch.index.reindex.ReindexRequest reindexRequest = requestFactory.reindexRequest(postReindexRequest);
return Mono.from(execute(client -> client.submitReindex(reindexRequest)));
});
}
@Override
public Mono<ByQueryResponse> delete(Query query, Class<?> entityType) {
return delete(query, entityType, getIndexCoordinatesFor(entityType));

View File

@ -15,19 +15,15 @@
*/
package org.springframework.data.elasticsearch.core;
import static org.elasticsearch.core.TimeValue.*;
import static org.elasticsearch.index.query.QueryBuilders.*;
import static org.elasticsearch.index.reindex.RemoteInfo.*;
import static org.elasticsearch.script.Script.*;
import static org.springframework.util.CollectionUtils.*;
import java.io.IOException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.*;
import java.util.stream.Collectors;
import org.elasticsearch.action.DocWriteRequest;
@ -58,6 +54,7 @@ import org.elasticsearch.client.indices.GetMappingsRequest;
import org.elasticsearch.client.indices.IndexTemplatesExistRequest;
import org.elasticsearch.client.indices.PutIndexTemplateRequest;
import org.elasticsearch.client.indices.PutMappingRequest;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.geo.GeoDistance;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.core.TimeValue;
@ -66,6 +63,7 @@ import org.elasticsearch.index.query.MoreLikeThisQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.reindex.DeleteByQueryRequest;
import org.elasticsearch.index.reindex.RemoteInfo;
import org.elasticsearch.index.reindex.UpdateByQueryRequest;
import org.elasticsearch.script.Script;
import org.elasticsearch.search.builder.SearchSourceBuilder;
@ -73,6 +71,7 @@ import org.elasticsearch.search.fetch.subphase.FetchSourceContext;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.rescore.QueryRescoreMode;
import org.elasticsearch.search.rescore.QueryRescorerBuilder;
import org.elasticsearch.search.slice.SliceBuilder;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.GeoDistanceSortBuilder;
import org.elasticsearch.search.sort.ScoreSortBuilder;
@ -81,6 +80,8 @@ import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortMode;
import org.elasticsearch.search.sort.SortOrder;
import org.elasticsearch.search.suggest.SuggestBuilder;
import org.elasticsearch.xcontent.ToXContent;
import org.elasticsearch.xcontent.XContentBuilder;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
@ -91,7 +92,12 @@ import org.springframework.data.elasticsearch.core.index.AliasActions;
import org.springframework.data.elasticsearch.core.index.DeleteTemplateRequest;
import org.springframework.data.elasticsearch.core.index.ExistsTemplateRequest;
import org.springframework.data.elasticsearch.core.index.GetTemplateRequest;
import org.springframework.data.elasticsearch.core.reindex.ReindexRequest;
import org.springframework.data.elasticsearch.core.index.PutTemplateRequest;
import org.springframework.data.elasticsearch.core.reindex.ReindexRequest.Source;
import org.springframework.data.elasticsearch.core.reindex.Remote;
import org.springframework.data.elasticsearch.core.reindex.ReindexRequest.Dest;
import org.springframework.data.elasticsearch.core.reindex.ReindexRequest.Slice;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
@ -387,6 +393,117 @@ class RequestFactory {
return new DeleteIndexTemplateRequest(deleteTemplateRequest.getTemplateName());
}
/**
* @since 4.4
*/
public org.elasticsearch.index.reindex.ReindexRequest reindexRequest(ReindexRequest reindexRequest){
final org.elasticsearch.index.reindex.ReindexRequest request = new org.elasticsearch.index.reindex.ReindexRequest();
if(reindexRequest.getConflicts() != null){
request.setConflicts(reindexRequest.getConflicts().name().toLowerCase(Locale.ROOT));
}
if(reindexRequest.getMaxDocs() != null){
request.setMaxDocs(reindexRequest.getMaxDocs());
}
// region source build
final Source source = reindexRequest.getSource();
request.setSourceIndices(source.getIndexes().getIndexNames());
// source query will build from RemoteInfo if remote exist
if(source.getQuery() != null && source.getRemote() == null){
request.setSourceQuery(getQuery(source.getQuery()));
}
if(source.getSize() != null){
request.setSourceBatchSize(source.getSize());
}
if(source.getRemote() != null){
Remote remote = source.getRemote();
QueryBuilder queryBuilder = source.getQuery() == null ? QueryBuilders.matchAllQuery() : getQuery(source.getQuery());
BytesReference query;
try {
XContentBuilder builder = XContentBuilder.builder(QUERY_CONTENT_TYPE).prettyPrint();
query = BytesReference.bytes(queryBuilder.toXContent(builder, ToXContent.EMPTY_PARAMS));
} catch (IOException e) {
throw new IllegalArgumentException("an IOException occurs while building the source query content",e);
}
request.setRemoteInfo(new RemoteInfo(
remote.getScheme(),
remote.getHost(),
remote.getPort(),
remote.getPathPrefix(),
query,
remote.getUsername(),
remote.getPassword(),
Collections.emptyMap(),
remote.getSocketTimeout() == null ? DEFAULT_SOCKET_TIMEOUT : timeValueSeconds(remote.getSocketTimeout().getSeconds()),
remote.getConnectTimeout() == null ? DEFAULT_CONNECT_TIMEOUT : timeValueSeconds(remote.getConnectTimeout().getSeconds())
));
}
final Slice slice = source.getSlice();
if(slice != null){
request.getSearchRequest().source().slice(new SliceBuilder(slice.getId(), slice.getMax()));
}
final SourceFilter sourceFilter = source.getSourceFilter();
if(sourceFilter != null){
request.getSearchRequest().source().fetchSource(sourceFilter.getIncludes(), sourceFilter.getExcludes());
}
// endregion
// region dest build
final Dest dest = reindexRequest.getDest();
request.setDestIndex(dest.getIndex().getIndexName())
.setDestRouting(dest.getRouting())
.setDestPipeline(dest.getPipeline());
final org.springframework.data.elasticsearch.annotations.Document.VersionType versionType = dest.getVersionType();
if(versionType != null){
request.setDestVersionType(VersionType.fromString(versionType.name().toLowerCase(Locale.ROOT)));
}
final IndexQuery.OpType opType = dest.getOpType();
if(opType != null){
request.setDestOpType(opType.name().toLowerCase(Locale.ROOT));
}
// endregion
// region script build
final ReindexRequest.Script script = reindexRequest.getScript();
if(script != null){
request.setScript(new Script(DEFAULT_SCRIPT_TYPE,
script.getLang(),
script.getSource(),
Collections.emptyMap()
));
}
// endregion
// region query parameters build
final Duration timeout = reindexRequest.getTimeout();
if(timeout != null){
request.setTimeout(timeValueSeconds(timeout.getSeconds()));
}
if(reindexRequest.getRefresh() != null){
request.setRefresh(reindexRequest.getRefresh());
}
if(reindexRequest.getRequireAlias() != null){
request.setRequireAlias(reindexRequest.getRequireAlias());
}
if(reindexRequest.getRequestsPerSecond() != null){
request.setRequestsPerSecond(reindexRequest.getRequestsPerSecond());
}
final Duration scroll = reindexRequest.getScroll();
if(scroll != null){
request.setScroll(timeValueSeconds(scroll.getSeconds()));
}
if(reindexRequest.getWaitForActiveShards() != null){
request.setWaitForActiveShards(ActiveShardCount.parseString(reindexRequest.getWaitForActiveShards()));
}
if(reindexRequest.getSlices() != null){
request.setSlices(reindexRequest.getSlices());
}
// endregion
return request;
}
// endregion
// region delete

View File

@ -45,6 +45,7 @@ import org.springframework.data.elasticsearch.core.document.Document;
import org.springframework.data.elasticsearch.core.index.AliasData;
import org.springframework.data.elasticsearch.core.index.Settings;
import org.springframework.data.elasticsearch.core.index.TemplateData;
import org.springframework.data.elasticsearch.core.reindex.ReindexResponse;
import org.springframework.data.elasticsearch.core.query.ByQueryResponse;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@ -54,6 +55,7 @@ import org.springframework.util.Assert;
*
* @author George Popides
* @author Peter-Josef Meisch
* @author Sijia Liu
* @since 4.2
*/
public class ResponseConverter {
@ -384,4 +386,52 @@ public class ResponseConverter {
}
// endregion
// region postReindexResponse
/**
* @since 4.4
*/
public static ReindexResponse reindexResponseOf(BulkByScrollResponse bulkByScrollResponse){
final List<ReindexResponse.Failure> failures = bulkByScrollResponse.getBulkFailures() //
.stream() //
.map(ResponseConverter::reindexResponseFailureOf) //
.collect(Collectors.toList()); //
return ReindexResponse.builder() //
.withTook(bulkByScrollResponse.getTook().getMillis()) //
.withTimedOut(bulkByScrollResponse.isTimedOut()) //
.withTotal(bulkByScrollResponse.getTotal()) //
.withUpdated(bulkByScrollResponse.getUpdated()) //
.withDeleted(bulkByScrollResponse.getDeleted()) //
.withBatches(bulkByScrollResponse.getBatches()) //
.withVersionConflicts(bulkByScrollResponse.getVersionConflicts()) //
.withNoops(bulkByScrollResponse.getNoops()) //
.withBulkRetries(bulkByScrollResponse.getBulkRetries()) //
.withSearchRetries(bulkByScrollResponse.getSearchRetries()) //
.withThrottledMillis(bulkByScrollResponse.getStatus().getThrottled().getMillis()) //
.withRequestsPerSecond(bulkByScrollResponse.getStatus().getRequestsPerSecond()) //
.withThrottledUntilMillis(bulkByScrollResponse.getStatus().getThrottledUntil().getMillis()) //
.withFailures(failures) //
.build(); //
}
/**
* @since 4.4
*/
public static ReindexResponse.Failure reindexResponseFailureOf(BulkItemResponse.Failure failure) {
return ReindexResponse.Failure.builder() //
.withIndex(failure.getIndex()) //
.withType(failure.getType()) //
.withId(failure.getId()) //
.withStatus(failure.getStatus().getStatus()) //
.withAborted(failure.isAborted()) //
.withCause(failure.getCause()) //
.withSeqNo(failure.getSeqNo()) //
.withTerm(failure.getTerm()) //
.build(); //
}
// endregion
}

View File

@ -0,0 +1,386 @@
/*
* Copyright 2019-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.reindex;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.IndexQuery;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.core.query.SourceFilter;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import java.time.Duration;
/**
* Request to reindex some documents from one index to another.
* (@see https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-reindex.html)
*
* @author Sijia Liu
* @since 4.4
*/
public class ReindexRequest {
// Request body
private final Source source;
private final Dest dest;
@Nullable private final Integer maxDocs;
@Nullable private final Conflicts conflicts;
@Nullable private final Script script;
// Query parameters
@Nullable private final Duration timeout;
@Nullable private final Boolean requireAlias;
@Nullable private final Boolean refresh;
@Nullable private final String waitForActiveShards;
@Nullable private final Integer requestsPerSecond;
@Nullable private final Duration scroll;
@Nullable private final Integer slices;
private ReindexRequest(Source source, Dest dest, @Nullable Integer maxDocs, @Nullable Conflicts conflicts, @Nullable Script script, @Nullable Duration timeout, @Nullable Boolean requireAlias, @Nullable Boolean refresh, @Nullable String waitForActiveShards, @Nullable Integer requestsPerSecond, @Nullable Duration scroll, @Nullable Integer slices) {
Assert.notNull(source, "source must not be null");
Assert.notNull(dest, "dest must not be null");
this.source = source;
this.dest = dest;
this.maxDocs = maxDocs;
this.conflicts = conflicts;
this.script = script;
this.timeout = timeout;
this.requireAlias = requireAlias;
this.refresh = refresh;
this.waitForActiveShards = waitForActiveShards;
this.requestsPerSecond = requestsPerSecond;
this.scroll = scroll;
this.slices = slices;
}
@Nullable
public Integer getMaxDocs() {
return maxDocs;
}
public Source getSource() {
return source;
}
public Dest getDest() {
return dest;
}
@Nullable
public Script getScript() {
return script;
}
@Nullable
public Conflicts getConflicts() {
return conflicts;
}
@Nullable
public Boolean getRequireAlias() {
return requireAlias;
}
@Nullable
public Duration getTimeout() {
return timeout;
}
@Nullable
public Boolean getRefresh() {
return refresh;
}
@Nullable
public String getWaitForActiveShards() {
return waitForActiveShards;
}
@Nullable
public Integer getRequestsPerSecond() {
return requestsPerSecond;
}
@Nullable
public Duration getScroll() {
return scroll;
}
@Nullable
public Integer getSlices() {
return slices;
}
public static ReindexRequestBuilder builder(IndexCoordinates sourceIndex, IndexCoordinates destIndex) {
return new ReindexRequestBuilder(sourceIndex, destIndex);
}
public enum Conflicts {
PROCEED, ABORT
}
public static class Source {
private final IndexCoordinates indexes;
@Nullable private Query query;
@Nullable private Remote remote;
@Nullable private Slice slice;
@Nullable private Integer size;
@Nullable private SourceFilter sourceFilter;
private Source(IndexCoordinates indexes){
Assert.notNull(indexes, "indexes must not be null");
this.indexes = indexes;
}
public IndexCoordinates getIndexes() {
return indexes;
}
@Nullable
public Remote getRemote() {
return remote;
}
@Nullable
public Query getQuery() {
return query;
}
@Nullable
public Integer getSize() {
return size;
}
@Nullable
public Slice getSlice() {
return slice;
}
@Nullable
public SourceFilter getSourceFilter() {
return sourceFilter;
}
}
public static class Slice {
private final int id;
private final int max;
private Slice(int id, int max) {
this.id = id;
this.max = max;
}
public int getId() {
return id;
}
public int getMax() {
return max;
}
}
public static class Dest {
private final IndexCoordinates index;
@Nullable private String pipeline;
@Nullable private String routing;
@Nullable private Document.VersionType versionType;
@Nullable private IndexQuery.OpType opType;
private Dest(IndexCoordinates index) {
Assert.notNull(index, "dest index must not be null");
this.index = index;
}
public IndexCoordinates getIndex() {
return index;
}
@Nullable
public Document.VersionType getVersionType() {
return versionType;
}
@Nullable
public IndexQuery.OpType getOpType() {
return opType;
}
@Nullable
public String getPipeline() {
return pipeline;
}
@Nullable
public String getRouting() {
return routing;
}
}
public static class Script {
private final String source;
@Nullable private final String lang;
private Script(String source, @Nullable String lang) {
Assert.notNull(source, "source must not be null");
this.source = source;
this.lang = lang;
}
public String getSource() {
return source;
}
@Nullable
public String getLang() {
return lang;
}
}
public static final class ReindexRequestBuilder {
private final Source source;
private final Dest dest;
@Nullable private Integer maxDocs;
@Nullable private Conflicts conflicts;
@Nullable private Script script;
@Nullable private Duration timeout;
@Nullable private Boolean requireAlias;
@Nullable private Boolean refresh;
@Nullable private String waitForActiveShards;
@Nullable private Integer requestsPerSecond;
@Nullable private Duration scroll;
@Nullable private Integer slices;
public ReindexRequestBuilder(IndexCoordinates sourceIndex, IndexCoordinates destIndex) {
Assert.notNull(sourceIndex, "sourceIndex must not be null");
Assert.notNull(destIndex, "destIndex must not be null");
this.source = new Source(sourceIndex);
this.dest = new Dest(destIndex);
}
// region setter
public ReindexRequestBuilder withMaxDocs(@Nullable Integer maxDocs) {
this.maxDocs = maxDocs;
return this;
}
public ReindexRequestBuilder withConflicts(Conflicts conflicts) {
this.conflicts = conflicts;
return this;
}
public ReindexRequestBuilder withSourceQuery(Query query) {
this.source.query = query;
return this;
}
public ReindexRequestBuilder withSourceSlice(int id, int max){
this.source.slice = new Slice(id, max);
return this;
}
public ReindexRequestBuilder withSourceRemote(Remote remote) {
this.source.remote = remote;
return this;
}
public ReindexRequestBuilder withSourceSize(int size) {
this.source.size = size;
return this;
}
public ReindexRequestBuilder withSourceSourceFilter(SourceFilter sourceFilter){
this.source.sourceFilter = sourceFilter;
return this;
}
public ReindexRequestBuilder withDestPipeline(String pipelineName){
this.dest.pipeline = pipelineName;
return this;
}
public ReindexRequestBuilder withDestRouting(String routing){
this.dest.routing = routing;
return this;
}
public ReindexRequestBuilder withDestVersionType(Document.VersionType versionType) {
this.dest.versionType = versionType;
return this;
}
public ReindexRequestBuilder withDestOpType(IndexQuery.OpType opType) {
this.dest.opType = opType;
return this;
}
public ReindexRequestBuilder withScript(String source, @Nullable String lang) {
this.script = new Script(source, lang);
return this;
}
public ReindexRequestBuilder withTimeout(Duration timeout){
this.timeout = timeout;
return this;
}
public ReindexRequestBuilder withRequireAlias(boolean requireAlias){
this.requireAlias = requireAlias;
return this;
}
public ReindexRequestBuilder withRefresh(boolean refresh){
this.refresh = refresh;
return this;
}
public ReindexRequestBuilder withWaitForActiveShards(String waitForActiveShards){
this.waitForActiveShards = waitForActiveShards;
return this;
}
public ReindexRequestBuilder withRequestsPerSecond(int requestsPerSecond){
this.requestsPerSecond = requestsPerSecond;
return this;
}
public ReindexRequestBuilder withScroll(Duration scroll){
this.scroll = scroll;
return this;
}
public ReindexRequestBuilder withSlices(int slices){
this.slices = slices;
return this;
}
// endregion
public ReindexRequest build() {
return new ReindexRequest(source, dest, maxDocs, conflicts, script, timeout, requireAlias, refresh, waitForActiveShards, requestsPerSecond, scroll, slices);
}
}
}

View File

@ -0,0 +1,403 @@
/*
* Copyright 2019-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.reindex;
import org.springframework.lang.Nullable;
import java.util.Collections;
import java.util.List;
/**
* Response of reindex request.
* (@see https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-reindex.html#docs-reindex-api-response-body)
*
* @author Sijia Liu
* @since 4.4
*/
public class ReindexResponse {
private final long took;
private final boolean timedOut;
private final long total;
private final long updated;
private final long deleted;
private final int batches;
private final long versionConflicts;
private final long noops;
private final long bulkRetries;
private final long searchRetries;
private final long throttledMillis;
private final double requestsPerSecond;
private final long throttledUntilMillis;
private final List<Failure> failures;
private ReindexResponse(long took, boolean timedOut, long total, long updated, long deleted, int batches,
long versionConflicts, long noops, long bulkRetries, long searchRetries,
long throttledMillis, double requestsPerSecond, long throttledUntilMillis, List<Failure> failures) {
this.took = took;
this.timedOut = timedOut;
this.total = total;
this.updated = updated;
this.deleted = deleted;
this.batches = batches;
this.versionConflicts = versionConflicts;
this.noops = noops;
this.bulkRetries = bulkRetries;
this.searchRetries = searchRetries;
this.throttledMillis = throttledMillis;
this.requestsPerSecond = requestsPerSecond;
this.throttledUntilMillis = throttledUntilMillis;
this.failures = failures;
}
/**
* The number of milliseconds from start to end of the whole operation.
*/
public long getTook() {
return took;
}
/**
* Did any of the sub-requests that were part of this request timeout?
*/
public boolean isTimedOut() {
return timedOut;
}
/**
* The number of documents that were successfully processed.
*/
public long getTotal() {
return total;
}
/**
* The number of documents that were successfully updated.
*/
public long getUpdated() {
return updated;
}
/**
* The number of documents that were successfully deleted.
*/
public long getDeleted() {
return deleted;
}
/**
* The number of scroll responses pulled back by the update by query.
*/
public int getBatches() {
return batches;
}
/**
* The number of version conflicts that the update by query hit.
*/
public long getVersionConflicts() {
return versionConflicts;
}
/**
* The number of documents that were ignored because the script used for the update by query returned a noop value for
* ctx.op.
*/
public long getNoops() {
return noops;
}
/**
* The number of times that the request had retry bulk actions.
*/
public long getBulkRetries() {
return bulkRetries;
}
/**
* The number of times that the request had retry search actions.
*/
public long getSearchRetries() {
return searchRetries;
}
/**
* Number of milliseconds the request slept to conform to requests_per_second.
*/
public long getThrottledMillis() {
return throttledMillis;
}
/**
* The number of requests per second effectively executed during the reindex.
*/
public double getRequestsPerSecond() {
return requestsPerSecond;
}
/**
* This field should always be equal to zero in a _reindex response.
* It only has meaning when using the Task API, where it indicates the next time (in milliseconds since epoch)
* a throttled request will be executed again in order to conform to requests_per_second.
*/
public long getThrottledUntilMillis() {
return throttledUntilMillis;
}
/**
* All of the bulk failures. Version conflicts are only included if the request sets abortOnVersionConflict to true
* (the default).
*/
public List<Failure> getFailures() {
return failures;
}
/**
* Create a new {@link ReindexResponseBuilder} to build {@link ReindexResponse}
*
* @return a new {@link ReindexResponseBuilder} to build {@link ReindexResponse}
*/
public static ReindexResponseBuilder builder() {
return new ReindexResponseBuilder();
}
public static class Failure {
@Nullable private final String index;
@Nullable private final String type;
@Nullable private final String id;
@Nullable private final Exception cause;
@Nullable private final Integer status;
@Nullable private final Long seqNo;
@Nullable private final Long term;
@Nullable private final Boolean aborted;
private Failure(@Nullable String index, @Nullable String type, @Nullable String id, @Nullable Exception cause,
@Nullable Integer status, @Nullable Long seqNo, @Nullable Long term, @Nullable Boolean aborted) {
this.index = index;
this.type = type;
this.id = id;
this.cause = cause;
this.status = status;
this.seqNo = seqNo;
this.term = term;
this.aborted = aborted;
}
@Nullable
public String getIndex() {
return index;
}
@Nullable
public String getType() {
return type;
}
@Nullable
public String getId() {
return id;
}
@Nullable
public Exception getCause() {
return cause;
}
@Nullable
public Integer getStatus() {
return status;
}
@Nullable
public Long getSeqNo() {
return seqNo;
}
@Nullable
public Long getTerm() {
return term;
}
@Nullable
public Boolean getAborted() {
return aborted;
}
/**
* Create a new {@link Failure.FailureBuilder} to build {@link Failure}
*
* @return a new {@link Failure.FailureBuilder} to build {@link Failure}
*/
public static Failure.FailureBuilder builder() {
return new Failure.FailureBuilder();
}
/**
* Builder for {@link Failure}
*/
public static final class FailureBuilder {
@Nullable private String index;
@Nullable private String type;
@Nullable private String id;
@Nullable private Exception cause;
@Nullable private Integer status;
@Nullable private Long seqNo;
@Nullable private Long term;
@Nullable private Boolean aborted;
private FailureBuilder() {}
public Failure.FailureBuilder withIndex(String index) {
this.index = index;
return this;
}
public Failure.FailureBuilder withType(String type) {
this.type = type;
return this;
}
public Failure.FailureBuilder withId(String id) {
this.id = id;
return this;
}
public Failure.FailureBuilder withCause(Exception cause) {
this.cause = cause;
return this;
}
public Failure.FailureBuilder withStatus(Integer status) {
this.status = status;
return this;
}
public Failure.FailureBuilder withSeqNo(Long seqNo) {
this.seqNo = seqNo;
return this;
}
public Failure.FailureBuilder withTerm(Long term) {
this.term = term;
return this;
}
public Failure.FailureBuilder withAborted(Boolean aborted) {
this.aborted = aborted;
return this;
}
public Failure build() {
return new Failure(index, type, id, cause, status, seqNo, term, aborted);
}
}
}
public static final class ReindexResponseBuilder {
private long took;
private boolean timedOut;
private long total;
private long updated;
private long deleted;
private int batches;
private long versionConflicts;
private long noops;
private long bulkRetries;
private long searchRetries;
private long throttledMillis;
private double requestsPerSecond;
private long throttledUntilMillis;
private List<Failure> failures = Collections.emptyList();
private ReindexResponseBuilder() {}
public ReindexResponseBuilder withTook(long took) {
this.took = took;
return this;
}
public ReindexResponseBuilder withTimedOut(boolean timedOut) {
this.timedOut = timedOut;
return this;
}
public ReindexResponseBuilder withTotal(long total) {
this.total = total;
return this;
}
public ReindexResponseBuilder withUpdated(long updated) {
this.updated = updated;
return this;
}
public ReindexResponseBuilder withDeleted(long deleted) {
this.deleted = deleted;
return this;
}
public ReindexResponseBuilder withBatches(int batches) {
this.batches = batches;
return this;
}
public ReindexResponseBuilder withVersionConflicts(long versionConflicts) {
this.versionConflicts = versionConflicts;
return this;
}
public ReindexResponseBuilder withNoops(long noops) {
this.noops = noops;
return this;
}
public ReindexResponseBuilder withBulkRetries(long bulkRetries) {
this.bulkRetries = bulkRetries;
return this;
}
public ReindexResponseBuilder withSearchRetries(long searchRetries) {
this.searchRetries = searchRetries;
return this;
}
public ReindexResponseBuilder withThrottledMillis(long throttledMillis){
this.throttledMillis = throttledMillis;
return this;
}
public ReindexResponseBuilder withRequestsPerSecond(double requestsPerSecond){
this.requestsPerSecond = requestsPerSecond;
return this;
}
public ReindexResponseBuilder withThrottledUntilMillis(long throttledUntilMillis){
this.throttledUntilMillis = throttledUntilMillis;
return this;
}
public ReindexResponseBuilder withFailures(List<Failure> failures) {
this.failures = failures;
return this;
}
public ReindexResponse build() {
return new ReindexResponse(took, timedOut, total, updated, deleted, batches, versionConflicts, noops, bulkRetries,
searchRetries, throttledMillis, requestsPerSecond, throttledUntilMillis, failures);
}
}
}

View File

@ -0,0 +1,142 @@
/*
* Copyright 2019-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.reindex;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import java.time.Duration;
/**
* Remote info
* (@see https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-reindex.html#source)
*
* @author Sijia Liu
* @since 4.4
*/
public class Remote {
private final String scheme;
private final String host;
private final int port;
@Nullable private final String pathPrefix;
@Nullable private final String username;
@Nullable private final String password;
@Nullable private final Duration socketTimeout;
@Nullable private final Duration connectTimeout;
private Remote(String scheme, String host, int port, @Nullable String pathPrefix, @Nullable String username, @Nullable String password, @Nullable Duration socketTimeout, @Nullable Duration connectTimeout) {
Assert.notNull(scheme, "scheme must not be null");
Assert.notNull(host, "host must not be null");
this.scheme = scheme;
this.host = host;
this.port = port;
this.pathPrefix = pathPrefix;
this.username = username;
this.password = password;
this.socketTimeout = socketTimeout;
this.connectTimeout = connectTimeout;
}
public String getHost() {
return host;
}
@Nullable
public String getUsername() {
return username;
}
@Nullable
public String getPassword() {
return password;
}
@Nullable
public Duration getSocketTimeout() {
return socketTimeout;
}
@Nullable
public Duration getConnectTimeout() {
return connectTimeout;
}
public String getScheme() {
return scheme;
}
public int getPort() {
return port;
}
@Nullable
public String getPathPrefix() {
return pathPrefix;
}
public static RemoteBuilder builder(String scheme, String host, int port){
return new RemoteBuilder(scheme, host, port);
}
public static class RemoteBuilder{
private final String scheme;
private final String host;
private final int port;
@Nullable private String pathPrefix;
@Nullable private String username;
@Nullable private String password;
@Nullable private Duration socketTimeout;
@Nullable private Duration connectTimeout;
public RemoteBuilder(String scheme, String host, int port) {
this.scheme = scheme;
this.host = host;
this.port = port;
}
public RemoteBuilder withPathPrefix(String pathPrefix){
this.pathPrefix = pathPrefix;
return this;
}
public RemoteBuilder withUsername(String username){
this.username = username;
return this;
}
public RemoteBuilder withPassword(String password){
this.password = password;
return this;
}
public RemoteBuilder withSocketTimeout(Duration socketTimeout){
this.socketTimeout = socketTimeout;
return this;
}
public RemoteBuilder withConnectTimeout(Duration connectTimeout){
this.connectTimeout = connectTimeout;
return this;
}
public Remote build(){
return new Remote(scheme, host, port , pathPrefix, username, password, socketTimeout, connectTimeout);
}
}
}

View File

@ -0,0 +1,3 @@
@org.springframework.lang.NonNullApi
@org.springframework.lang.NonNullFields
package org.springframework.data.elasticsearch.core.reindex;

View File

@ -83,6 +83,11 @@ import org.springframework.data.elasticsearch.annotations.JoinTypeRelations;
import org.springframework.data.elasticsearch.annotations.MultiField;
import org.springframework.data.elasticsearch.annotations.ScriptedField;
import org.springframework.data.elasticsearch.annotations.Setting;
import org.springframework.data.elasticsearch.core.reindex.ReindexRequest;
import org.springframework.data.elasticsearch.core.reindex.ReindexResponse;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.ScriptField;
import org.springframework.data.elasticsearch.core.document.Explanation;
import org.springframework.data.elasticsearch.core.geo.GeoPoint;
import org.springframework.data.elasticsearch.core.index.AliasAction;
@ -123,6 +128,7 @@ import org.springframework.lang.Nullable;
* @author Subhobrata Dey
* @author Farid Faoudi
* @author Peer Mueller
* @author Sijia Liu
*/
@SpringIntegrationTest
public abstract class ElasticsearchTemplateTests {
@ -3643,6 +3649,38 @@ public abstract class ElasticsearchTemplateTests {
operations.search(query, SampleEntity.class);
}
@Test // #1529
void shouldWorkReindexForExistingIndex() {
String sourceIndexName = indexNameProvider.indexName();
String documentId = nextIdAsString();
SampleEntity sampleEntity = SampleEntity.builder().id(documentId).message("abc").build();
operations.save(sampleEntity);
indexNameProvider.increment();
String destIndexName = indexNameProvider.indexName();
operations.indexOps(IndexCoordinates.of(destIndexName)).create();
final ReindexRequest reindexRequest = ReindexRequest.builder(IndexCoordinates.of(sourceIndexName), IndexCoordinates.of(destIndexName))
.withRefresh(true).build();
final ReindexResponse reindex = operations.reindex(reindexRequest);
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).build();
assertThat(reindex.getTotal()).isEqualTo(1);
assertThat(operations.count(searchQuery, IndexCoordinates.of(destIndexName))).isEqualTo(1);
}
@Test // #1529
void shouldWorkSubmitReindexTask(){
String sourceIndexName = indexNameProvider.indexName();
indexNameProvider.increment();
String destIndexName = indexNameProvider.indexName();
operations.indexOps(IndexCoordinates.of(destIndexName)).create();
final ReindexRequest reindexRequest = ReindexRequest
.builder(IndexCoordinates.of(sourceIndexName), IndexCoordinates.of(destIndexName)).build();
String task = operations.submitReindex(reindexRequest);
// Maybe there should be a task api to detect whether the task exists
assertThat(task).isNotBlank();
}
// region entities
@Document(indexName = "#{@indexNameProvider.indexName()}")
@Setting(shards = 1, replicas = 0, refreshInterval = "-1")

View File

@ -19,7 +19,9 @@ import static java.util.Collections.*;
import static org.assertj.core.api.Assertions.*;
import static org.elasticsearch.index.query.QueryBuilders.*;
import static org.springframework.data.elasticsearch.annotations.FieldType.*;
import static org.springframework.data.elasticsearch.utils.IdGenerator.*;
import org.springframework.data.elasticsearch.core.reindex.ReindexRequest;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
@ -96,6 +98,7 @@ import org.springframework.util.StringUtils;
* @author Russell Parry
* @author Roman Puchkovskiy
* @author George Popides
* @author Sijia Liu
*/
@SuppressWarnings("SpringJavaAutowiredMembersInspection")
@SpringIntegrationTest
@ -1188,6 +1191,43 @@ public class ReactiveElasticsearchTemplateIntegrationTests {
.verifyComplete();
}
@Test // #1529
void shouldWorkReindexForExistingIndex() {
String sourceIndexName = indexNameProvider.indexName();
SampleEntity sampleEntity = randomEntity("abc");
operations.save(sampleEntity).block();
indexNameProvider.increment();
String destIndexName = indexNameProvider.indexName();
operations.indexOps(IndexCoordinates.of(destIndexName)).create();
final ReindexRequest reindexRequest = ReindexRequest
.builder(IndexCoordinates.of(sourceIndexName), IndexCoordinates.of(destIndexName))
.withRefresh(true)
.build();
operations.reindex(reindexRequest)
.as(StepVerifier::create)
.consumeNextWith(postReindexResponse -> assertThat(postReindexResponse.getTotal()).isEqualTo(1L))
.verifyComplete();
operations.count(operations.matchAllQuery(), SampleEntity.class, IndexCoordinates.of(destIndexName))
.as(StepVerifier::create)
.expectNext(1L)
.verifyComplete();
}
@Test // #1529
void shouldWorkSubmitReindexTask(){
String sourceIndexName = indexNameProvider.indexName();
indexNameProvider.increment();
String destIndexName = indexNameProvider.indexName();
operations.indexOps(IndexCoordinates.of(destIndexName)).create();
final ReindexRequest reindexRequest = ReindexRequest
.builder(IndexCoordinates.of(sourceIndexName), IndexCoordinates.of(destIndexName))
.build();
operations.submitReindex(reindexRequest)
.as(StepVerifier::create)
.consumeNextWith(task -> assertThat(task).isNotBlank())
.verifyComplete();
}
// endregion
// region Helper functions

View File

@ -48,6 +48,7 @@ import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.data.annotation.Id;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter;
import org.springframework.data.elasticsearch.core.geo.GeoPoint;
@ -55,18 +56,12 @@ import org.springframework.data.elasticsearch.core.index.AliasAction;
import org.springframework.data.elasticsearch.core.index.AliasActionParameters;
import org.springframework.data.elasticsearch.core.index.AliasActions;
import org.springframework.data.elasticsearch.core.index.PutTemplateRequest;
import org.springframework.data.elasticsearch.core.reindex.ReindexRequest;
import org.springframework.data.elasticsearch.core.reindex.Remote;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
import org.springframework.data.elasticsearch.core.query.Criteria;
import org.springframework.data.elasticsearch.core.query.CriteriaQuery;
import org.springframework.data.elasticsearch.core.query.GeoDistanceOrder;
import org.springframework.data.elasticsearch.core.query.IndexQuery;
import org.springframework.data.elasticsearch.core.query.IndexQueryBuilder;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.core.query.RescorerQuery;
import org.springframework.data.elasticsearch.core.query.*;
import org.springframework.data.elasticsearch.core.query.RescorerQuery.ScoreMode;
import org.springframework.data.elasticsearch.core.query.SeqNoPrimaryTerm;
import org.springframework.lang.Nullable;
/**
@ -74,6 +69,7 @@ import org.springframework.lang.Nullable;
* @author Roman Puchkovskiy
* @author Peer Mueller
* @author vdisk
* @author Sijia Liu
*/
@SuppressWarnings("ConstantConditions")
@ExtendWith(MockitoExtension.class)
@ -562,6 +558,87 @@ class RequestFactoryTests {
.isEqualTo(Arrays.asList("last-name", "current-location"));
}
@Test // #1529
void shouldCreateReindexRequest() throws IOException, JSONException {
final String expected = "{\n" +
" \"source\":{\n" +
" \"remote\":{\n" +
" \"username\":\"admin\",\n" +
" \"password\":\"password\",\n" +
" \"host\":\"http://localhost:9200/elasticsearch\",\n" +
" \"socket_timeout\":\"30s\",\n" +
" \"connect_timeout\":\"30s\"\n" +
" },\n" +
" \"index\":[\"source_1\",\"source_2\"],\n" +
" \"size\":5,\n" +
" \"query\":{\"match_all\":{}},\n" +
" \"_source\":{\"includes\":[\"name\"],\"excludes\":[]},\n" +
" \"slice\":{\"id\":1,\"max\":20}\n" +
" },\n" +
" \"dest\":{\n" +
" \"index\":\"destination\",\n" +
" \"routing\":\"routing\",\n" +
" \"op_type\":\"create\",\n" +
" \"pipeline\":\"pipeline\",\n" +
" \"version_type\":\"external\"\n" +
" },\n" +
" \"max_docs\":10,\n" +
" \"script\":{\"source\":\"Math.max(1,2)\",\"lang\":\"java\"},\n" +
" \"conflicts\":\"proceed\"\n" +
"}";
Remote remote = Remote.builder("http", "localhost",9200)
.withPathPrefix("elasticsearch")
.withUsername("admin")
.withPassword("password")
.withConnectTimeout(Duration.ofSeconds(30))
.withSocketTimeout(Duration.ofSeconds(30)).build();
ReindexRequest reindexRequest = ReindexRequest.builder(IndexCoordinates.of("source_1", "source_2"),
IndexCoordinates.of("destination"))
.withConflicts(ReindexRequest.Conflicts.PROCEED)
.withMaxDocs(10)
.withSourceQuery(new NativeSearchQueryBuilder().withQuery(matchAllQuery()).build())
.withSourceSize(5)
.withSourceSourceFilter(new FetchSourceFilterBuilder().withIncludes("name").build())
.withSourceRemote(remote)
.withSourceSlice(1,20)
.withDestOpType(IndexQuery.OpType.CREATE)
.withDestVersionType(Document.VersionType.EXTERNAL)
.withDestPipeline("pipeline")
.withDestRouting("routing")
.withScript("Math.max(1,2)", "java")
.build();
final String json = requestToString(requestFactory.reindexRequest(reindexRequest));
assertEquals(expected, json, false);
}
@Test
void shouldAllowSourceQueryForReindexWithoutRemote() throws IOException, JSONException {
final String expected = "{\n" +
" \"source\":{\n" +
" \"index\":[\"source\"],\n" +
" \"query\":{\"match_all\":{}}\n" +
" },\n" +
" \"dest\":{\n" +
" \"index\":\"destination\",\n" +
" \"op_type\":\"index\",\n" +
" \"version_type\":\"internal\"\n" +
" }\n" +
"}";
ReindexRequest reindexRequest = ReindexRequest.builder(IndexCoordinates.of("source"),
IndexCoordinates.of("destination"))
.withSourceQuery(new NativeSearchQueryBuilder().withQuery(matchAllQuery()).build())
.build();
final String json = requestToString(requestFactory.reindexRequest(reindexRequest));
assertEquals(expected, json, false);
}
// region entities
static class Person {
@Nullable