Implement update by query.

Original Pull Request #1446 
Closes #1644
This commit is contained in:
Farid Faoudi 2021-01-23 11:31:15 +01:00 committed by GitHub
parent 169015fb3b
commit 0ee0164a58
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 1119 additions and 38 deletions

View File

@ -24,6 +24,8 @@ import io.netty.handler.timeout.ReadTimeoutHandler;
import io.netty.handler.timeout.WriteTimeoutHandler; import io.netty.handler.timeout.WriteTimeoutHandler;
import org.elasticsearch.client.indices.GetFieldMappingsRequest; import org.elasticsearch.client.indices.GetFieldMappingsRequest;
import org.elasticsearch.client.indices.GetFieldMappingsResponse; import org.elasticsearch.client.indices.GetFieldMappingsResponse;
import org.elasticsearch.index.reindex.UpdateByQueryRequest;
import org.springframework.data.elasticsearch.core.query.UpdateByQueryResponse;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import reactor.netty.http.client.HttpClient; import reactor.netty.http.client.HttpClient;
@ -521,6 +523,13 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
.next(); .next();
} }
@Override
public Mono<UpdateByQueryResponse> updateBy(HttpHeaders headers, UpdateByQueryRequest updateRequest) {
return sendRequest(updateRequest, requestCreator.updateByQuery(), BulkByScrollResponse.class, headers) //
.next() //
.map(UpdateByQueryResponse::of);
}
/* /*
* (non-Javadoc) * (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#bulk(org.springframework.http.HttpHeaders, org.elasticsearch.action.bulk.BulkRequest) * @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#bulk(org.springframework.http.HttpHeaders, org.elasticsearch.action.bulk.BulkRequest)

View File

@ -17,6 +17,8 @@ package org.springframework.data.elasticsearch.client.reactive;
import org.elasticsearch.client.indices.GetFieldMappingsRequest; import org.elasticsearch.client.indices.GetFieldMappingsRequest;
import org.elasticsearch.client.indices.GetFieldMappingsResponse; import org.elasticsearch.client.indices.GetFieldMappingsResponse;
import org.elasticsearch.index.reindex.UpdateByQueryRequest;
import org.springframework.data.elasticsearch.core.query.UpdateByQueryResponse;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
@ -596,6 +598,44 @@ public interface ReactiveElasticsearchClient {
*/ */
Mono<BulkByScrollResponse> deleteBy(HttpHeaders headers, DeleteByQueryRequest deleteRequest); Mono<BulkByScrollResponse> deleteBy(HttpHeaders headers, DeleteByQueryRequest deleteRequest);
/**
* Execute a {@link UpdateByQueryRequest} against the {@literal update by query} API.
*
* @param consumer never {@literal null}.
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-update-by-query.html">Update By
* * Query API on elastic.co</a>
* @return a {@link Mono} emitting operation response.
*/
default Mono<UpdateByQueryResponse> updateBy(Consumer<UpdateByQueryRequest> consumer){
final UpdateByQueryRequest request = new UpdateByQueryRequest();
consumer.accept(request);
return updateBy(request);
}
/**
* Execute a {@link UpdateByQueryRequest} against the {@literal update by query} API.
*
* @param updateRequest must not be {@literal null}.
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-update-by-query.html">Update By
* * Query API on elastic.co</a>
* @return a {@link Mono} emitting operation response.
*/
default Mono<UpdateByQueryResponse> updateBy(UpdateByQueryRequest updateRequest){
return updateBy(HttpHeaders.EMPTY, updateRequest);
}
/**
* Execute a {@link UpdateByQueryRequest} against the {@literal update by query} API.
*
* @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}.
* @param updateRequest must not be {@literal null}.
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-update-by-query.html">Update By
* * Query API on elastic.co</a>
* @return a {@link Mono} emitting operation response.
*/
Mono<UpdateByQueryResponse> updateBy(HttpHeaders headers, UpdateByQueryRequest updateRequest);
/** /**
* Execute a {@link BulkRequest} against the {@literal bulk} API. * Execute a {@link BulkRequest} against the {@literal bulk} API.
* *

View File

@ -33,6 +33,7 @@ import org.elasticsearch.client.indices.GetIndexTemplatesRequest;
import org.elasticsearch.client.indices.IndexTemplatesExistRequest; import org.elasticsearch.client.indices.IndexTemplatesExistRequest;
import org.elasticsearch.client.indices.PutIndexTemplateRequest; import org.elasticsearch.client.indices.PutIndexTemplateRequest;
import org.elasticsearch.index.reindex.DeleteByQueryRequest; import org.elasticsearch.index.reindex.DeleteByQueryRequest;
import org.elasticsearch.index.reindex.UpdateByQueryRequest;
import org.springframework.data.elasticsearch.UncategorizedElasticsearchException; import org.springframework.data.elasticsearch.UncategorizedElasticsearchException;
import org.springframework.data.elasticsearch.client.util.RequestConverters; import org.springframework.data.elasticsearch.client.util.RequestConverters;
@ -91,6 +92,10 @@ public interface RequestCreator {
return RequestConverters::deleteByQuery; return RequestConverters::deleteByQuery;
} }
default Function<UpdateByQueryRequest, Request> updateByQuery() {
return RequestConverters::updateByQuery;
}
default Function<BulkRequest, Request> bulk() { default Function<BulkRequest, Request> bulk() {
return request -> { return request -> {

View File

@ -540,12 +540,15 @@ public class RequestConverters {
public static Request updateByQuery(UpdateByQueryRequest updateByQueryRequest) { public static Request updateByQuery(UpdateByQueryRequest updateByQueryRequest) {
String endpoint = endpoint(updateByQueryRequest.indices(), updateByQueryRequest.getDocTypes(), "_update_by_query"); String endpoint = endpoint(updateByQueryRequest.indices(), updateByQueryRequest.getDocTypes(), "_update_by_query");
Request request = new Request(HttpMethod.POST.name(), endpoint); Request request = new Request(HttpMethod.POST.name(), endpoint);
Params params = new Params(request).withRouting(updateByQueryRequest.getRouting()) Params params = new Params(request)
.withPipeline(updateByQueryRequest.getPipeline()).withRefresh(updateByQueryRequest.isRefresh()) .withRouting(updateByQueryRequest.getRouting()) //
.withTimeout(updateByQueryRequest.getTimeout()) .withPipeline(updateByQueryRequest.getPipeline()) //
.withWaitForActiveShards(updateByQueryRequest.getWaitForActiveShards()) .withRefresh(updateByQueryRequest.isRefresh()) //
.withRequestsPerSecond(updateByQueryRequest.getRequestsPerSecond()) .withTimeout(updateByQueryRequest.getTimeout()) //
.withIndicesOptions(updateByQueryRequest.indicesOptions()); .withWaitForActiveShards(updateByQueryRequest.getWaitForActiveShards()) //
.withRequestsPerSecond(updateByQueryRequest.getRequestsPerSecond()) //
.withIndicesOptions(updateByQueryRequest.indicesOptions()); //
if (!updateByQueryRequest.isAbortOnVersionConflict()) { if (!updateByQueryRequest.isAbortOnVersionConflict()) {
params.putParam("conflicts", "proceed"); params.putParam("conflicts", "proceed");
} }
@ -555,8 +558,8 @@ public class RequestConverters {
if (updateByQueryRequest.getScrollTime() != AbstractBulkByScrollRequest.DEFAULT_SCROLL_TIMEOUT) { if (updateByQueryRequest.getScrollTime() != AbstractBulkByScrollRequest.DEFAULT_SCROLL_TIMEOUT) {
params.putParam("scroll", updateByQueryRequest.getScrollTime()); params.putParam("scroll", updateByQueryRequest.getScrollTime());
} }
if (updateByQueryRequest.getSize() > 0) { if (updateByQueryRequest.getMaxDocs() > 0) {
params.putParam("size", Integer.toString(updateByQueryRequest.getSize())); params.putParam("max_docs", Integer.toString(updateByQueryRequest.getMaxDocs()));
} }
request.setEntity(createEntity(updateByQueryRequest, REQUEST_BODY_CONTENT_TYPE)); request.setEntity(createEntity(updateByQueryRequest, REQUEST_BODY_CONTENT_TYPE));
return request; return request;

View File

@ -18,6 +18,7 @@ package org.springframework.data.elasticsearch.core;
import java.util.List; import java.util.List;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.UpdateByQueryResponse;
import org.springframework.data.elasticsearch.core.query.BulkOptions; import org.springframework.data.elasticsearch.core.query.BulkOptions;
import org.springframework.data.elasticsearch.core.query.DeleteQuery; import org.springframework.data.elasticsearch.core.query.DeleteQuery;
import org.springframework.data.elasticsearch.core.query.GetQuery; import org.springframework.data.elasticsearch.core.query.GetQuery;
@ -33,6 +34,7 @@ import org.springframework.lang.Nullable;
* <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs.html">Elasticsearch Document APIs</a>. * <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs.html">Elasticsearch Document APIs</a>.
* *
* @author Peter-Josef Meisch * @author Peter-Josef Meisch
* @author Farid Faoudi
* @since 4.0 * @since 4.0
*/ */
public interface DocumentOperations { public interface DocumentOperations {
@ -299,6 +301,16 @@ public interface DocumentOperations {
*/ */
UpdateResponse update(UpdateQuery updateQuery, IndexCoordinates index); UpdateResponse update(UpdateQuery updateQuery, IndexCoordinates index);
/**
* Update document(s) by query
*
* @param updateQuery query defining the update
* @param index the index where to update the records
* @return the update response
* @since 4.2
*/
UpdateByQueryResponse updateByQuery(UpdateQuery updateQuery, IndexCoordinates index);
// region deprecated // region deprecated
/** /**
* Delete all records matching the query. * Delete all records matching the query.

View File

@ -37,7 +37,9 @@ import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.client.RequestOptions; import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient; import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.index.reindex.BulkByScrollResponse;
import org.elasticsearch.index.reindex.DeleteByQueryRequest; import org.elasticsearch.index.reindex.DeleteByQueryRequest;
import org.elasticsearch.index.reindex.UpdateByQueryRequest;
import org.elasticsearch.search.fetch.subphase.FetchSourceContext; import org.elasticsearch.search.fetch.subphase.FetchSourceContext;
import org.elasticsearch.search.suggest.SuggestBuilder; import org.elasticsearch.search.suggest.SuggestBuilder;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -46,6 +48,7 @@ import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverte
import org.springframework.data.elasticsearch.core.document.DocumentAdapters; import org.springframework.data.elasticsearch.core.document.DocumentAdapters;
import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse; import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.UpdateByQueryResponse;
import org.springframework.data.elasticsearch.core.query.BulkOptions; import org.springframework.data.elasticsearch.core.query.BulkOptions;
import org.springframework.data.elasticsearch.core.query.DeleteQuery; import org.springframework.data.elasticsearch.core.query.DeleteQuery;
import org.springframework.data.elasticsearch.core.query.IndexQuery; import org.springframework.data.elasticsearch.core.query.IndexQuery;
@ -87,6 +90,7 @@ import org.springframework.util.Assert;
* @author Mathias Teier * @author Mathias Teier
* @author Gyula Attila Csorogi * @author Gyula Attila Csorogi
* @author Massimiliano Poggi * @author Massimiliano Poggi
* @author Farid Faoudi
*/ */
public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate { public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
@ -226,6 +230,13 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
return new UpdateResponse(result); return new UpdateResponse(result);
} }
@Override
public UpdateByQueryResponse updateByQuery(UpdateQuery query, IndexCoordinates index) {
final UpdateByQueryRequest updateByQueryRequest = requestFactory.updateByQueryRequest(query, index);
final BulkByScrollResponse bulkByScrollResponse = execute(client -> client.updateByQuery(updateByQueryRequest, RequestOptions.DEFAULT));
return UpdateByQueryResponse.of(bulkByScrollResponse);
}
public List<IndexedObjectInformation> doBulkOperation(List<?> queries, BulkOptions bulkOptions, public List<IndexedObjectInformation> doBulkOperation(List<?> queries, BulkOptions bulkOptions,
IndexCoordinates index) { IndexCoordinates index) {
BulkRequest bulkRequest = prepareWriteRequest(requestFactory.bulkRequest(queries, bulkOptions, index)); BulkRequest bulkRequest = prepareWriteRequest(requestFactory.bulkRequest(queries, bulkOptions, index));

View File

@ -36,6 +36,8 @@ import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.update.UpdateRequestBuilder; import org.elasticsearch.action.update.UpdateRequestBuilder;
import org.elasticsearch.client.Client; import org.elasticsearch.client.Client;
import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.index.reindex.BulkByScrollResponse;
import org.elasticsearch.index.reindex.UpdateByQueryRequestBuilder;
import org.elasticsearch.search.suggest.SuggestBuilder; import org.elasticsearch.search.suggest.SuggestBuilder;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -44,6 +46,7 @@ import org.springframework.data.elasticsearch.core.document.Document;
import org.springframework.data.elasticsearch.core.document.DocumentAdapters; import org.springframework.data.elasticsearch.core.document.DocumentAdapters;
import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse; import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.UpdateByQueryResponse;
import org.springframework.data.elasticsearch.core.query.BulkOptions; import org.springframework.data.elasticsearch.core.query.BulkOptions;
import org.springframework.data.elasticsearch.core.query.DeleteQuery; import org.springframework.data.elasticsearch.core.query.DeleteQuery;
import org.springframework.data.elasticsearch.core.query.IndexQuery; import org.springframework.data.elasticsearch.core.query.IndexQuery;
@ -81,6 +84,7 @@ import org.springframework.util.Assert;
* @author Farid Azaza * @author Farid Azaza
* @author Gyula Attila Csorogi * @author Gyula Attila Csorogi
* @author Roman Puchkovskiy * @author Roman Puchkovskiy
* @author Farid Faoudi
* @deprecated as of 4.0 * @deprecated as of 4.0
*/ */
@Deprecated @Deprecated
@ -251,6 +255,13 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
return new UpdateResponse(result); return new UpdateResponse(result);
} }
@Override
public UpdateByQueryResponse updateByQuery(UpdateQuery query, IndexCoordinates index) {
final UpdateByQueryRequestBuilder updateByQueryRequestBuilder = requestFactory.updateByQueryRequestBuilder(client, query, index);
final BulkByScrollResponse bulkByScrollResponse = updateByQueryRequestBuilder.execute().actionGet();
return UpdateByQueryResponse.of(bulkByScrollResponse);
}
public List<IndexedObjectInformation> doBulkOperation(List<?> queries, BulkOptions bulkOptions, public List<IndexedObjectInformation> doBulkOperation(List<?> queries, BulkOptions bulkOptions,
IndexCoordinates index) { IndexCoordinates index) {
BulkRequestBuilder bulkRequestBuilder = requestFactory.bulkRequestBuilder(client, queries, bulkOptions, index); BulkRequestBuilder bulkRequestBuilder = requestFactory.bulkRequestBuilder(client, queries, bulkOptions, index);

View File

@ -15,6 +15,7 @@
*/ */
package org.springframework.data.elasticsearch.core; package org.springframework.data.elasticsearch.core;
import org.springframework.data.elasticsearch.core.query.UpdateByQueryResponse;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
@ -36,6 +37,7 @@ import org.springframework.util.Assert;
* @author Peter-Josef Meisch * @author Peter-Josef Meisch
* @author Aleksei Arsenev * @author Aleksei Arsenev
* @author Roman Puchkovskiy * @author Roman Puchkovskiy
* @author Farid Faoudi
* @since 4.0 * @since 4.0
*/ */
public interface ReactiveDocumentOperations { public interface ReactiveDocumentOperations {
@ -336,4 +338,13 @@ public interface ReactiveDocumentOperations {
* @since 4.1 * @since 4.1
*/ */
Mono<UpdateResponse> update(UpdateQuery updateQuery, IndexCoordinates index); Mono<UpdateResponse> update(UpdateQuery updateQuery, IndexCoordinates index);
/**
* Update document(s) by query.
* @param updateQuery query defining the update
* @param index the index where to update the records
* @return a {@link Mono} emitting the update response
* @since 4.2
*/
Mono<UpdateByQueryResponse> updateByQuery(UpdateQuery updateQuery, IndexCoordinates index);
} }

View File

@ -15,6 +15,8 @@
*/ */
package org.springframework.data.elasticsearch.core; package org.springframework.data.elasticsearch.core;
import org.elasticsearch.index.reindex.UpdateByQueryRequest;
import org.springframework.data.elasticsearch.core.query.UpdateByQueryResponse;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import reactor.util.function.Tuple2; import reactor.util.function.Tuple2;
@ -97,6 +99,7 @@ import org.springframework.util.Assert;
* @author Roman Puchkovskiy * @author Roman Puchkovskiy
* @author Russell Parry * @author Russell Parry
* @author Thomas Geese * @author Thomas Geese
* @author Farid Faoudi
* @since 3.2 * @since 3.2
*/ */
public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOperations, ApplicationContextAware { public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOperations, ApplicationContextAware {
@ -558,6 +561,18 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
}); });
} }
@Override
public Mono<UpdateByQueryResponse> updateByQuery(UpdateQuery updateQuery, IndexCoordinates index) {
Assert.notNull(updateQuery, "UpdateQuery must not be null");
Assert.notNull(index, "Index must not be null");
return Mono.defer(() -> {
final UpdateByQueryRequest request = requestFactory.updateByQueryRequest(updateQuery, index);
return Mono.from(execute(client -> client.updateBy(request)));
});
}
@Override @Override
public Mono<Long> delete(Query query, Class<?> entityType) { public Mono<Long> delete(Query query, Class<?> entityType) {
return delete(query, entityType, getIndexCoordinatesFor(entityType)); return delete(query, entityType, getIndexCoordinatesFor(entityType));

View File

@ -18,16 +18,7 @@ package org.springframework.data.elasticsearch.core;
import static org.elasticsearch.index.query.QueryBuilders.*; import static org.elasticsearch.index.query.QueryBuilders.*;
import static org.springframework.util.CollectionUtils.*; import static org.springframework.util.CollectionUtils.*;
import java.util.ArrayList; import java.util.*;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.elasticsearch.action.DocWriteRequest; import org.elasticsearch.action.DocWriteRequest;
import org.elasticsearch.action.admin.indices.alias.Alias; import org.elasticsearch.action.admin.indices.alias.Alias;
@ -82,8 +73,10 @@ import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.reindex.DeleteByQueryAction; import org.elasticsearch.index.reindex.DeleteByQueryAction;
import org.elasticsearch.index.reindex.DeleteByQueryRequest; import org.elasticsearch.index.reindex.DeleteByQueryRequest;
import org.elasticsearch.index.reindex.DeleteByQueryRequestBuilder; import org.elasticsearch.index.reindex.DeleteByQueryRequestBuilder;
import org.elasticsearch.index.reindex.UpdateByQueryAction;
import org.elasticsearch.index.reindex.UpdateByQueryRequest;
import org.elasticsearch.index.reindex.UpdateByQueryRequestBuilder;
import org.elasticsearch.script.Script; import org.elasticsearch.script.Script;
import org.elasticsearch.script.ScriptType;
import org.elasticsearch.search.aggregations.AbstractAggregationBuilder; import org.elasticsearch.search.aggregations.AbstractAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder; import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder; import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
@ -123,6 +116,7 @@ import org.springframework.util.StringUtils;
* @author Sascha Woo * @author Sascha Woo
* @author Roman Puchkovskiy * @author Roman Puchkovskiy
* @author Subhobrata Dey * @author Subhobrata Dey
* @author Farid Faoudi
* @since 4.0 * @since 4.0
*/ */
class RequestFactory { class RequestFactory {
@ -1404,7 +1398,7 @@ class RequestFactory {
if (params == null) { if (params == null) {
params = new HashMap<>(); params = new HashMap<>();
} }
Script script = new Script(ScriptType.INLINE, query.getLang(), query.getScript(), params); Script script = new Script(getScriptType(query.getScriptType()), query.getLang(), query.getScript(), params);
updateRequest.script(script); updateRequest.script(script);
} }
@ -1478,7 +1472,7 @@ class RequestFactory {
if (params == null) { if (params == null) {
params = new HashMap<>(); params = new HashMap<>();
} }
Script script = new Script(ScriptType.INLINE, query.getLang(), query.getScript(), params); Script script = new Script(getScriptType(query.getScriptType()), query.getLang(), query.getScript(), params);
updateRequestBuilder.setScript(script); updateRequestBuilder.setScript(script);
} }
@ -1540,6 +1534,147 @@ class RequestFactory {
return updateRequestBuilder; return updateRequestBuilder;
} }
public UpdateByQueryRequest updateByQueryRequest(UpdateQuery query, IndexCoordinates index) {
String indexName = index.getIndexName();
final UpdateByQueryRequest updateByQueryRequest = new UpdateByQueryRequest(indexName);
updateByQueryRequest.setScript(getScript(query));
if (query.getAbortOnVersionConflict() != null) {
updateByQueryRequest.setAbortOnVersionConflict(query.getAbortOnVersionConflict());
}
if (query.getBatchSize() != null) {
updateByQueryRequest.setBatchSize(query.getBatchSize());
}
if (query.getQuery() != null) {
final Query queryQuery = query.getQuery();
updateByQueryRequest.setQuery(getQuery(queryQuery));
if (queryQuery.getIndicesOptions() != null) {
updateByQueryRequest.setIndicesOptions(queryQuery.getIndicesOptions());
}
if(queryQuery.getScrollTime() != null) {
updateByQueryRequest.setScroll(TimeValue.timeValueMillis(queryQuery.getScrollTime().toMillis()));
}
}
if (query.getMaxDocs() != null) {
updateByQueryRequest.setMaxDocs(query.getMaxDocs());
}
if (query.getMaxRetries() != null) {
updateByQueryRequest.setMaxRetries(query.getMaxRetries());
}
if (query.getPipeline() != null) {
updateByQueryRequest.setPipeline(query.getPipeline());
}
if (query.getRefresh() != null) {
updateByQueryRequest.setRefresh(Boolean.getBoolean(query.getRefresh().name().toLowerCase()));
}
if (query.getRequestsPerSecond() != null) {
updateByQueryRequest.setRequestsPerSecond(query.getRequestsPerSecond());
}
if (query.getRouting() != null) {
updateByQueryRequest.setRouting(query.getRouting());
}
if (query.getShouldStoreResult() != null) {
updateByQueryRequest.setShouldStoreResult(query.getShouldStoreResult());
}
if (query.getSlices() != null) {
updateByQueryRequest.setSlices(query.getSlices());
}
if (query.getTimeout() != null) {
updateByQueryRequest.setTimeout(query.getTimeout());
}
if (query.getWaitForActiveShards() != null) {
updateByQueryRequest.setWaitForActiveShards(ActiveShardCount.parseString(query.getWaitForActiveShards()));
}
return updateByQueryRequest;
}
public UpdateByQueryRequestBuilder updateByQueryRequestBuilder(Client client, UpdateQuery query, IndexCoordinates index) {
String indexName = index.getIndexName();
final UpdateByQueryRequestBuilder updateByQueryRequestBuilder = new UpdateByQueryRequestBuilder(client, UpdateByQueryAction.INSTANCE);
updateByQueryRequestBuilder.source(indexName);
updateByQueryRequestBuilder.script(getScript(query));
if (query.getAbortOnVersionConflict() != null) {
updateByQueryRequestBuilder.abortOnVersionConflict(query.getAbortOnVersionConflict());
}
if (query.getBatchSize() != null) {
updateByQueryRequestBuilder.source().setSize(query.getBatchSize());
}
if (query.getQuery() != null) {
final Query queryQuery = query.getQuery();
updateByQueryRequestBuilder.filter(getQuery(queryQuery));
if (queryQuery.getIndicesOptions() != null) {
updateByQueryRequestBuilder.source().setIndicesOptions(queryQuery.getIndicesOptions());
}
if(queryQuery.getScrollTime() != null) {
updateByQueryRequestBuilder.source().setScroll(TimeValue.timeValueMillis(queryQuery.getScrollTime().toMillis()));
}
}
if (query.getMaxDocs() != null) {
updateByQueryRequestBuilder.maxDocs(query.getMaxDocs());
}
if (query.getMaxRetries() != null) {
updateByQueryRequestBuilder.setMaxRetries(query.getMaxRetries());
}
if (query.getPipeline() != null) {
updateByQueryRequestBuilder.setPipeline(query.getPipeline());
}
if (query.getRefresh() != null) {
updateByQueryRequestBuilder.refresh(Boolean.getBoolean(query.getRefresh().name().toLowerCase()));
}
if (query.getRequestsPerSecond() != null) {
updateByQueryRequestBuilder.setRequestsPerSecond(query.getRequestsPerSecond());
}
if (query.getRouting() != null) {
updateByQueryRequestBuilder.source().setRouting(query.getRouting());
}
if (query.getShouldStoreResult() != null) {
updateByQueryRequestBuilder.setShouldStoreResult(query.getShouldStoreResult());
}
if (query.getSlices() != null) {
updateByQueryRequestBuilder.setSlices(query.getSlices());
}
if (query.getTimeout() != null) {
updateByQueryRequestBuilder.source().setTimeout(TimeValue.parseTimeValue(query.getTimeout(), getClass().getSimpleName() + ".timeout"));
}
return updateByQueryRequestBuilder;
}
// endregion // endregion
// region helper functions // region helper functions
@ -1662,6 +1797,31 @@ class RequestFactory {
return entity.hasSeqNoPrimaryTermProperty(); return entity.hasSeqNoPrimaryTermProperty();
} }
private org.elasticsearch.script.ScriptType getScriptType(ScriptType scriptType) {
if (scriptType == null || ScriptType.INLINE.equals(scriptType)) {
return org.elasticsearch.script.ScriptType.INLINE;
} else {
return org.elasticsearch.script.ScriptType.STORED;
}
}
@Nullable
private Script getScript(UpdateQuery query) {
if (ScriptType.STORED.equals(query.getScriptType()) && query.getScriptName() != null) {
final Map<String, Object> params = Optional.ofNullable(query.getParams())
.orElse(new HashMap<>());
return new Script(getScriptType(ScriptType.STORED), null, query.getScriptName(), params);
}
if (ScriptType.INLINE.equals(query.getScriptType()) && query.getScript() != null){
final Map<String, Object> params = Optional.ofNullable(query.getParams())
.orElse(new HashMap<>());
return new Script(getScriptType(ScriptType.INLINE), query.getLang(), query.getScript(), params);
}
return null;
}
// endregion // endregion
} }

View File

@ -0,0 +1,27 @@
/*
* Copyright 2021 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;
/**
* Enum mirroring org.elasticsearch.script.ScriptType to keep Elasticsearch classes out of our API.
*
* @author Farid Faoudi
* @since 4.2
*/
public enum ScriptType {
INLINE, STORED
}

View File

@ -0,0 +1,404 @@
/*
* Copyright 2021 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.elasticsearch.action.bulk.BulkItemResponse;
import org.elasticsearch.index.reindex.BulkByScrollResponse;
import org.springframework.lang.Nullable;
import java.util.List;
import java.util.stream.Collectors;
/**
* Class mirroring org.elasticsearch.index.reindex.BulkByScrollResponse to keep Elasticsearch classes out of our API.
*
* @author Farid Faoudi
* @since 4.2
*/
public class UpdateByQueryResponse {
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;
@Nullable private final String reasonCancelled;
private final List<Failure> failures;
private UpdateByQueryResponse(long took, boolean timedOut, long total, long updated, long deleted, int batches,
long versionConflicts, long noops, long bulkRetries, long searchRetries,
@Nullable String reasonCancelled, 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.reasonCancelled = reasonCancelled;
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 getTimedOut() {
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;
}
/**
* The reason that the request was canceled or null if it hasn't been.
*/
@Nullable
public String getReasonCancelled() {
return reasonCancelled;
}
/**
* 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 UpdateByQueryResponseBuilder} to build {@link UpdateByQueryResponse}
*
* @return a new {@link UpdateByQueryResponseBuilder} to build {@link UpdateByQueryResponse}
*/
public static UpdateByQueryResponseBuilder builder() {
return new UpdateByQueryResponseBuilder();
}
public static UpdateByQueryResponse of(BulkByScrollResponse bulkByScrollResponse) {
final List<Failure> failures = bulkByScrollResponse.getBulkFailures()
.stream()
.map(Failure::of)
.collect(Collectors.toList());
return UpdateByQueryResponse.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())
.withReasonCancelled(bulkByScrollResponse.getReasonCancelled())
.withFailures(failures)
.build();
}
public static class Failure {
private final String index;
private final String type;
private final String id;
private final Exception cause;
private final Integer status;
private final Long seqNo;
private final Long term;
private final Boolean aborted;
private Failure(String index, String type, String id, Exception cause, Integer status, Long seqNo, Long term,
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;
}
public String getIndex() {
return index;
}
public String getType() {
return type;
}
public String getId() {
return id;
}
public Exception getCause() {
return cause;
}
public Integer getStatus() {
return status;
}
public Long getSeqNo() {
return seqNo;
}
public Long getTerm() {
return term;
}
public Boolean getAborted() {
return aborted;
}
/**
* Create a new {@link FailureBuilder} to build {@link Failure}
*
* @return a new {@link FailureBuilder} to build {@link Failure}
*/
public static FailureBuilder builder() {
return new FailureBuilder();
}
/**
* Create a new {@link Failure} from {@link BulkItemResponse.Failure}
* @param failure {@link BulkItemResponse.Failure} to translate
* @return a new {@link Failure}
*/
public static Failure of(BulkItemResponse.Failure failure) {
return 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();
}
/**
* Builder for {@link Failure}
*/
public static final class FailureBuilder {
private String index;
private String type;
private String id;
private Exception cause;
private Integer status;
private Long seqNo;
private Long term;
private Boolean aborted;
private FailureBuilder() {
}
public FailureBuilder withIndex(String index) {
this.index = index;
return this;
}
public FailureBuilder withType(String type) {
this.type = type;
return this;
}
public FailureBuilder withId(String id) {
this.id = id;
return this;
}
public FailureBuilder withCause(Exception cause) {
this.cause = cause;
return this;
}
public FailureBuilder withStatus(Integer status) {
this.status = status;
return this;
}
public FailureBuilder withSeqNo(Long seqNo) {
this.seqNo = seqNo;
return this;
}
public FailureBuilder withTerm(Long term) {
this.term = term;
return this;
}
public 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 UpdateByQueryResponseBuilder {
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;
@Nullable private String reasonCancelled;
private List<Failure> failures;
private UpdateByQueryResponseBuilder() {
}
public UpdateByQueryResponseBuilder withTook(long took) {
this.took = took;
return this;
}
public UpdateByQueryResponseBuilder withTimedOut(boolean timedOut) {
this.timedOut = timedOut;
return this;
}
public UpdateByQueryResponseBuilder withTotal(long total) {
this.total = total;
return this;
}
public UpdateByQueryResponseBuilder withUpdated(long updated) {
this.updated = updated;
return this;
}
public UpdateByQueryResponseBuilder withDeleted(long deleted) {
this.deleted = deleted;
return this;
}
public UpdateByQueryResponseBuilder withBatches(int batches) {
this.batches = batches;
return this;
}
public UpdateByQueryResponseBuilder withVersionConflicts(long versionConflicts) {
this.versionConflicts = versionConflicts;
return this;
}
public UpdateByQueryResponseBuilder withNoops(long noops) {
this.noops = noops;
return this;
}
public UpdateByQueryResponseBuilder withBulkRetries(long bulkRetries) {
this.bulkRetries = bulkRetries;
return this;
}
public UpdateByQueryResponseBuilder withSearchRetries(long searchRetries) {
this.searchRetries = searchRetries;
return this;
}
public UpdateByQueryResponseBuilder withReasonCancelled(String reasonCancelled) {
this.reasonCancelled = reasonCancelled;
return this;
}
public UpdateByQueryResponseBuilder withFailures(List<Failure> failures) {
this.failures = failures;
return this;
}
public UpdateByQueryResponse build() {
return new UpdateByQueryResponse(took, timedOut, total, updated, deleted, batches, versionConflicts, noops, bulkRetries, searchRetries, reasonCancelled, failures);
}
}
}

View File

@ -18,6 +18,7 @@ package org.springframework.data.elasticsearch.core.query;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.springframework.data.elasticsearch.core.ScriptType;
import org.springframework.data.elasticsearch.core.document.Document; import org.springframework.data.elasticsearch.core.document.Document;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
@ -27,6 +28,7 @@ import org.springframework.lang.Nullable;
* @author Rizwan Idrees * @author Rizwan Idrees
* @author Mohsin Husen * @author Mohsin Husen
* @author Peter-Josef Meisch * @author Peter-Josef Meisch
* @author Farid Faoudi
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-update.html>docs</a> * @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-update.html>docs</a>
*/ */
public class UpdateQuery { public class UpdateQuery {
@ -47,19 +49,37 @@ public class UpdateQuery {
@Nullable private Integer ifPrimaryTerm; @Nullable private Integer ifPrimaryTerm;
@Nullable private Refresh refresh; @Nullable private Refresh refresh;
@Nullable private Integer retryOnConflict; @Nullable private Integer retryOnConflict;
@Nullable String timeout; @Nullable private String timeout;
@Nullable String waitForActiveShards; @Nullable private String waitForActiveShards;
@Nullable private Query query;
@Nullable private Boolean abortOnVersionConflict;
@Nullable private Integer batchSize;
@Nullable private Integer maxDocs;
@Nullable private Integer maxRetries;
@Nullable private String pipeline;
@Nullable private Float requestsPerSecond;
@Nullable private Boolean shouldStoreResult;
@Nullable private Integer slices;
@Nullable private ScriptType scriptType;
@Nullable private String scriptName;
public static Builder builder(String id) { public static Builder builder(String id) {
return new Builder(id); return new Builder(id);
} }
public static Builder builder(Query query) {
return new Builder(query);
}
private UpdateQuery(String id, @Nullable String script, @Nullable Map<String, Object> params, private UpdateQuery(String id, @Nullable String script, @Nullable Map<String, Object> params,
@Nullable Document document, @Nullable Document upsert, @Nullable String lang, @Nullable String routing, @Nullable Document document, @Nullable Document upsert, @Nullable String lang, @Nullable String routing,
@Nullable Boolean scriptedUpsert, @Nullable Boolean docAsUpsert, @Nullable Boolean fetchSource, @Nullable Boolean scriptedUpsert, @Nullable Boolean docAsUpsert, @Nullable Boolean fetchSource,
@Nullable List<String> fetchSourceIncludes, @Nullable List<String> fetchSourceExcludes, @Nullable Integer ifSeqNo, @Nullable List<String> fetchSourceIncludes, @Nullable List<String> fetchSourceExcludes, @Nullable Integer ifSeqNo,
@Nullable Integer ifPrimaryTerm, @Nullable Refresh refresh, @Nullable Integer retryOnConflict, @Nullable Integer ifPrimaryTerm, @Nullable Refresh refresh, @Nullable Integer retryOnConflict,
@Nullable String timeout, @Nullable String waitForActiveShards) { @Nullable String timeout, @Nullable String waitForActiveShards, @Nullable Query query,
@Nullable Boolean abortOnVersionConflict, @Nullable Integer batchSize, @Nullable Integer maxDocs,
@Nullable Integer maxRetries, @Nullable String pipeline, @Nullable Float requestsPerSecond,
@Nullable Boolean shouldStoreResult, @Nullable Integer slices, @Nullable ScriptType scriptType, @Nullable String scriptName) {
this.id = id; this.id = id;
this.script = script; this.script = script;
@ -79,6 +99,17 @@ public class UpdateQuery {
this.retryOnConflict = retryOnConflict; this.retryOnConflict = retryOnConflict;
this.timeout = timeout; this.timeout = timeout;
this.waitForActiveShards = waitForActiveShards; this.waitForActiveShards = waitForActiveShards;
this.query = query;
this.abortOnVersionConflict = abortOnVersionConflict;
this.batchSize = batchSize;
this.maxDocs = maxDocs;
this.maxRetries = maxRetries;
this.pipeline = pipeline;
this.requestsPerSecond = requestsPerSecond;
this.shouldStoreResult = shouldStoreResult;
this.slices = slices;
this.scriptType = scriptType;
this.scriptName = scriptName;
} }
public String getId() { public String getId() {
@ -170,6 +201,61 @@ public class UpdateQuery {
return waitForActiveShards; return waitForActiveShards;
} }
@Nullable
public Query getQuery() {
return query;
}
@Nullable
public Boolean getAbortOnVersionConflict() {
return abortOnVersionConflict;
}
@Nullable
public Integer getBatchSize() {
return batchSize;
}
@Nullable
public Integer getMaxDocs() {
return maxDocs;
}
@Nullable
public Integer getMaxRetries() {
return maxRetries;
}
@Nullable
public String getPipeline() {
return pipeline;
}
@Nullable
public Float getRequestsPerSecond() {
return requestsPerSecond;
}
@Nullable
public Boolean getShouldStoreResult() {
return shouldStoreResult;
}
@Nullable
public Integer getSlices() {
return slices;
}
@Nullable
public ScriptType getScriptType() {
return scriptType;
}
@Nullable
public String getScriptName() {
return scriptName;
}
public static final class Builder { public static final class Builder {
private String id; private String id;
@Nullable private String script = null; @Nullable private String script = null;
@ -189,11 +275,26 @@ public class UpdateQuery {
@Nullable String waitForActiveShards; @Nullable String waitForActiveShards;
@Nullable private List<String> fetchSourceIncludes; @Nullable private List<String> fetchSourceIncludes;
@Nullable private List<String> fetchSourceExcludes; @Nullable private List<String> fetchSourceExcludes;
@Nullable private Query query;
@Nullable private Boolean abortOnVersionConflict;
@Nullable private Integer batchSize;
@Nullable private Integer maxDocs;
@Nullable private Integer maxRetries;
@Nullable private String pipeline;
@Nullable private Float requestsPerSecond;
@Nullable private Boolean shouldStoreResult;
@Nullable private Integer slices;
@Nullable private ScriptType scriptType;
@Nullable private String scriptName;
private Builder(String id) { private Builder(String id) {
this.id = id; this.id = id;
} }
private Builder(Query query) {
this.query = query;
}
public Builder withScript(String script) { public Builder withScript(String script) {
this.script = script; this.script = script;
return this; return this;
@ -279,14 +380,66 @@ public class UpdateQuery {
return this; return this;
} }
public Builder withAbortOnVersionConflict(Boolean abortOnVersionConflict) {
this.abortOnVersionConflict = abortOnVersionConflict;
return this;
}
public Builder withBatchSize(Integer batchSize) {
this.batchSize = batchSize;
return this;
}
public Builder withMaxDocs(Integer maxDocs) {
this.maxDocs = maxDocs;
return this;
}
public Builder withMaxRetries(Integer maxRetries) {
this.maxRetries = maxRetries;
return this;
}
public Builder withPipeline(String pipeline) {
this.pipeline = pipeline;
return this;
}
public Builder withRequestsPerSecond(Float requestsPerSecond) {
this.requestsPerSecond = requestsPerSecond;
return this;
}
public Builder withShouldStoreResult(Boolean shouldStoreResult) {
this.shouldStoreResult = shouldStoreResult;
return this;
}
public Builder withSlices(Integer slices) {
this.slices = slices;
return this;
}
public Builder withScriptType(ScriptType scriptType) {
this.scriptType = scriptType;
return this;
}
public Builder withScriptName(String scriptName) {
this.scriptName = scriptName;
return this;
}
public UpdateQuery build() { public UpdateQuery build() {
if (script == null && document == null) { if (script == null && document == null && query == null) {
throw new IllegalArgumentException("either script or document must be set"); throw new IllegalArgumentException("either script, document or query must be set");
} }
return new UpdateQuery(id, script, params, document, upsert, lang, routing, scriptedUpsert, docAsUpsert, return new UpdateQuery(id, script, params, document, upsert, lang, routing, scriptedUpsert, docAsUpsert,
fetchSource, fetchSourceIncludes, fetchSourceExcludes, ifSeqNo, ifPrimaryTerm, refresh, retryOnConflict, fetchSource, fetchSourceIncludes, fetchSourceExcludes, ifSeqNo, ifPrimaryTerm, refresh, retryOnConflict,
timeout, waitForActiveShards); timeout, waitForActiveShards, query, abortOnVersionConflict, batchSize, maxDocs, maxRetries, pipeline,
requestsPerSecond, shouldStoreResult, slices, scriptType, scriptName);
} }
} }

View File

@ -19,6 +19,10 @@ import static org.assertj.core.api.Assertions.*;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest; import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest;
import org.elasticsearch.index.reindex.UpdateByQueryRequest;
import org.elasticsearch.script.Script;
import org.elasticsearch.script.ScriptType;
import org.springframework.data.elasticsearch.core.query.UpdateByQueryResponse;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import reactor.test.StepVerifier; import reactor.test.StepVerifier;
@ -27,6 +31,7 @@ import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import java.util.stream.IntStream; import java.util.stream.IntStream;
@ -102,8 +107,10 @@ public class ReactiveElasticsearchClientIntegrationTests {
// (Object...) // (Object...)
static final Map<String, Object> DOC_SOURCE; static final Map<String, Object> DOC_SOURCE;
@Autowired ReactiveElasticsearchClient client; @Autowired
@Autowired ReactiveElasticsearchOperations operations; ReactiveElasticsearchClient client;
@Autowired
ReactiveElasticsearchOperations operations;
static { static {
@ -451,6 +458,76 @@ public class ReactiveElasticsearchClientIntegrationTests {
.verifyComplete(); .verifyComplete();
} }
@Test // #1446
void updateByEmitResultWhenNothingUpdated() {
addSourceDocument().to(INDEX_I);
addSourceDocument().to(INDEX_I);
Map<String, String> source = new LinkedHashMap<>();
source.put("firstname", "crow");
source.put("lastname", "cat");
final Map<String, String> documentToNotUpdate = Collections.unmodifiableMap(source);
add(documentToNotUpdate).to(INDEX_I);
final String script = "ctx._source['firstname'] = params['newFirstname']";
final Map<String, Object> params = Collections.singletonMap("newFirstname", "arrow");
final UpdateByQueryRequest request = new UpdateByQueryRequest(INDEX_I)
.setQuery(QueryBuilders.boolQuery().must(QueryBuilders.termQuery("lastname", "fallstar")))
.setAbortOnVersionConflict(true)
.setRefresh(true)
.setScript(new Script(ScriptType.INLINE, "painless", script, params));
client.updateBy(request)
.map(UpdateByQueryResponse::getUpdated)
.as(StepVerifier::create)
.expectNext(2L)
.verifyComplete();
final SearchRequest searchUpdatedRequest = new SearchRequest(INDEX_I) //
.source(new SearchSourceBuilder()
.query(QueryBuilders.boolQuery().must(QueryBuilders.termQuery("firstname", "arrow"))));
client.search(searchUpdatedRequest)
.collectList()
.map(List::size)
.as(StepVerifier::create)
.expectNext(2)
.verifyComplete();
}
@Test // #1446
void updateByShouldUpdateExistingDocument() {
addSourceDocument().to(INDEX_I);
final String script = "ctx._source['firstname'] = params['newFirstname']";
final Map<String, Object> params = Collections.singletonMap("newFirstname", "arrow");
final UpdateByQueryRequest request = new UpdateByQueryRequest(INDEX_I)
.setQuery(QueryBuilders.boolQuery().must(QueryBuilders.termQuery("lastname", "non_existing_lastname")))
.setAbortOnVersionConflict(true)
.setRefresh(true)
.setScript(new Script(ScriptType.INLINE, "painless", script, params));
client.updateBy(request)
.map(UpdateByQueryResponse::getUpdated)
.as(StepVerifier::create)
.expectNext(0L)
.verifyComplete();
SearchRequest searchUpdatedRequest = new SearchRequest(INDEX_I) //
.source(new SearchSourceBuilder()
.query(QueryBuilders.boolQuery().must(QueryBuilders.termQuery("firstname", "arrow"))));
client.search(searchUpdatedRequest)
.collectList()
.map(List::size)
.as(StepVerifier::create)
.expectNext(0)
.verifyComplete();
}
@Test // DATAES-510 @Test // DATAES-510
public void scrollShouldReadWhileEndNotReached() { public void scrollShouldReadWhileEndNotReached() {

View File

@ -16,6 +16,8 @@
package org.springframework.data.elasticsearch.core; package org.springframework.data.elasticsearch.core;
import static org.assertj.core.api.Assertions.*; import static org.assertj.core.api.Assertions.*;
import static org.elasticsearch.index.query.QueryBuilders.*;
import static org.skyscreamer.jsonassert.JSONAssert.*;
import static org.springframework.data.elasticsearch.annotations.FieldType.*; import static org.springframework.data.elasticsearch.annotations.FieldType.*;
import static org.springframework.data.elasticsearch.utils.IdGenerator.*; import static org.springframework.data.elasticsearch.utils.IdGenerator.*;
@ -24,21 +26,28 @@ import lombok.Data;
import lombok.val; import lombok.val;
import java.lang.Object; import java.lang.Object;
import java.time.Duration;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import org.elasticsearch.action.support.ActiveShardCount; import org.elasticsearch.action.support.ActiveShardCount;
import org.elasticsearch.action.support.IndicesOptions;
import org.elasticsearch.action.support.WriteRequest; import org.elasticsearch.action.support.WriteRequest;
import org.elasticsearch.action.update.UpdateRequest; import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.index.reindex.UpdateByQueryRequest;
import org.json.JSONException;
import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.skyscreamer.jsonassert.JSONAssert;
import org.springframework.data.annotation.Id; import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.UncategorizedElasticsearchException; import org.springframework.data.elasticsearch.UncategorizedElasticsearchException;
import org.springframework.data.elasticsearch.annotations.Document; import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field; import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.UpdateQuery; import org.springframework.data.elasticsearch.core.query.UpdateQuery;
import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration; import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration;
import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest;
@ -57,6 +66,7 @@ import org.springframework.test.context.ContextConfiguration;
* @author Sascha Woo * @author Sascha Woo
* @author Don Wellington * @author Don Wellington
* @author Peter-Josef Meisch * @author Peter-Josef Meisch
* @author Farid Faoudi
*/ */
@ContextConfiguration(classes = { ElasticsearchRestTemplateConfiguration.class }) @ContextConfiguration(classes = { ElasticsearchRestTemplateConfiguration.class })
@DisplayName("ElasticsearchRestTemplate") @DisplayName("ElasticsearchRestTemplate")
@ -119,4 +129,55 @@ public class ElasticsearchRestTemplateTests extends ElasticsearchTemplateTests {
assertThat(fetchSourceContext.includes()).containsExactlyInAnyOrder("incl"); assertThat(fetchSourceContext.includes()).containsExactlyInAnyOrder("incl");
assertThat(fetchSourceContext.excludes()).containsExactlyInAnyOrder("excl"); assertThat(fetchSourceContext.excludes()).containsExactlyInAnyOrder("excl");
} }
@Test // #1446
void shouldUseAllOptionsFromUpdateByQuery() throws JSONException {
// given
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery())
.withIndicesOptions(IndicesOptions.lenientExpandOpen())
.build();
searchQuery.setScrollTime(Duration.ofMillis(1000));
final UpdateQuery updateQuery = UpdateQuery.builder(searchQuery)
.withAbortOnVersionConflict(true)
.withBatchSize(10)
.withMaxDocs(12)
.withMaxRetries(3)
.withPipeline("pipeline")
.withRequestsPerSecond(5F)
.withShouldStoreResult(false)
.withSlices(4)
.withScriptType(ScriptType.INLINE)
.withScript("script")
.withLang("painless")
.build();
final String expectedSearchRequest = '{' + //
" \"size\": 10," + //
" \"query\": {" + //
" \"match_all\": {" + //
" \"boost\": 1.0" + //
" }" +
" }" +
'}';
// when
final UpdateByQueryRequest request = getRequestFactory().updateByQueryRequest(updateQuery, IndexCoordinates.of("index"));
// then
assertThat(request).isNotNull();
assertThat(request.getSearchRequest().indicesOptions()).usingRecursiveComparison().isEqualTo(IndicesOptions.lenientExpandOpen());
assertThat(request.getScrollTime().getMillis()).isEqualTo(1000);
assertEquals(request.getSearchRequest().source().toString(), expectedSearchRequest, false);
assertThat(request.isAbortOnVersionConflict()).isTrue();
assertThat(request.getBatchSize()).isEqualTo(10);
assertThat(request.getMaxDocs()).isEqualTo(12);
assertThat(request.getPipeline()).isEqualTo("pipeline");
assertThat(request.getRequestsPerSecond()).isEqualTo(5F);
assertThat(request.getShouldStoreResult()).isFalse();
assertThat(request.getSlices()).isEqualTo(4);
assertThat(request.getScript().getIdOrCode()).isEqualTo("script");
assertThat(request.getScript().getType()).isEqualTo(org.elasticsearch.script.ScriptType.INLINE);
assertThat(request.getScript().getLang()).isEqualTo("painless");
}
} }

View File

@ -33,15 +33,7 @@ import java.lang.Double;
import java.lang.Integer; import java.lang.Integer;
import java.lang.Long; import java.lang.Long;
import java.lang.Object; import java.lang.Object;
import java.util.ArrayList; import java.util.*;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.IntStream; import java.util.stream.IntStream;
@ -119,6 +111,7 @@ import org.springframework.lang.Nullable;
* @author Gyula Attila Csorogi * @author Gyula Attila Csorogi
* @author Roman Puchkovskiy * @author Roman Puchkovskiy
* @author Subhobrata Dey * @author Subhobrata Dey
* @author Farid Faoudi
*/ */
@SpringIntegrationTest @SpringIntegrationTest
public abstract class ElasticsearchTemplateTests { public abstract class ElasticsearchTemplateTests {
@ -1535,6 +1528,39 @@ public abstract class ElasticsearchTemplateTests {
assertThat(indexedEntity.getMessage()).isEqualTo(messageAfterUpdate); assertThat(indexedEntity.getMessage()).isEqualTo(messageAfterUpdate);
} }
@Test
void shouldDoUpdateByQueryForExistingDocument() {
// given
final String documentId = nextIdAsString();
final String messageBeforeUpdate = "some test message";
final String messageAfterUpdate = "test message";
final SampleEntity sampleEntity = SampleEntity.builder().id(documentId).message(messageBeforeUpdate)
.version(System.currentTimeMillis()).build();
final IndexQuery indexQuery = getIndexQuery(sampleEntity);
operations.index(indexQuery, index);
indexOperations.refresh();
final NativeSearchQuery query = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).build();
final UpdateQuery updateQuery = UpdateQuery.builder(query)
.withScriptType(org.springframework.data.elasticsearch.core.ScriptType.INLINE)
.withScript("ctx._source['message'] = params['newMessage']")
.withLang("painless")
.withParams(Collections.singletonMap("newMessage", messageAfterUpdate))
.withAbortOnVersionConflict(true)
.build();
// when
operations.updateByQuery(updateQuery, index);
// then
SampleEntity indexedEntity = operations.get(documentId, SampleEntity.class, index);
assertThat(indexedEntity.getMessage()).isEqualTo(messageAfterUpdate);
}
@Test // DATAES-227 @Test // DATAES-227
public void shouldUseUpsertOnUpdate() { public void shouldUseUpsertOnUpdate() {

View File

@ -16,6 +16,8 @@
package org.springframework.data.elasticsearch.core; package org.springframework.data.elasticsearch.core;
import static org.assertj.core.api.Assertions.*; import static org.assertj.core.api.Assertions.*;
import static org.elasticsearch.index.query.QueryBuilders.*;
import static org.skyscreamer.jsonassert.JSONAssert.*;
import static org.springframework.data.elasticsearch.annotations.FieldType.*; import static org.springframework.data.elasticsearch.annotations.FieldType.*;
import static org.springframework.data.elasticsearch.utils.IdGenerator.*; import static org.springframework.data.elasticsearch.utils.IdGenerator.*;
@ -23,17 +25,21 @@ import lombok.Data;
import lombok.val; import lombok.val;
import java.lang.Object; import java.lang.Object;
import java.time.Duration;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import org.elasticsearch.action.search.SearchRequestBuilder; import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.action.support.ActiveShardCount; import org.elasticsearch.action.support.ActiveShardCount;
import org.elasticsearch.action.support.IndicesOptions;
import org.elasticsearch.action.support.WriteRequest; import org.elasticsearch.action.support.WriteRequest;
import org.elasticsearch.action.update.UpdateRequestBuilder; import org.elasticsearch.action.update.UpdateRequestBuilder;
import org.elasticsearch.client.Client; import org.elasticsearch.client.Client;
import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.index.engine.DocumentMissingException; import org.elasticsearch.index.engine.DocumentMissingException;
import org.elasticsearch.index.reindex.UpdateByQueryRequestBuilder;
import org.json.JSONException;
import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -53,6 +59,7 @@ import org.springframework.test.context.ContextConfiguration;
/** /**
* @author Peter-Josef Meisch * @author Peter-Josef Meisch
* @author Sascha Woo * @author Sascha Woo
* @author Farid Faoudi
*/ */
@ContextConfiguration(classes = { ElasticsearchTemplateConfiguration.class }) @ContextConfiguration(classes = { ElasticsearchTemplateConfiguration.class })
@DisplayName("ElasticsearchTransportTemplate") @DisplayName("ElasticsearchTransportTemplate")
@ -138,6 +145,55 @@ public class ElasticsearchTransportTemplateTests extends ElasticsearchTemplateTe
assertThat(client).isNotNull(); assertThat(client).isNotNull();
} }
@Test // #1446
void shouldUseAllOptionsFromUpdateByQuery() throws JSONException {
// given
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery())
.withIndicesOptions(IndicesOptions.lenientExpandOpen())
.build();
searchQuery.setScrollTime(Duration.ofMillis(1000));
final UpdateQuery updateQuery = UpdateQuery.builder(searchQuery)
.withAbortOnVersionConflict(true)
.withBatchSize(10)
.withMaxDocs(12)
.withMaxRetries(3)
.withPipeline("pipeline")
.withRequestsPerSecond(5F)
.withShouldStoreResult(false)
.withSlices(4)
.withScriptType(ScriptType.STORED)
.withScriptName("script_name")
.build();
final String expectedSearchRequest = '{' + //
" \"size\": 10," + //
" \"query\": {" + //
" \"match_all\": {" + //
" \"boost\": 1.0" + //
" }" +
" }" +
'}';
// when
final UpdateByQueryRequestBuilder request = getRequestFactory().updateByQueryRequestBuilder(client, updateQuery, IndexCoordinates.of("index"));
// then
assertThat(request).isNotNull();
assertThat(request.request().getSearchRequest().indicesOptions()).usingRecursiveComparison().isEqualTo(IndicesOptions.lenientExpandOpen());
assertThat(request.request().getScrollTime().getMillis()).isEqualTo(1000);
assertEquals(request.request().getSearchRequest().source().toString(), expectedSearchRequest, false);
assertThat(request.request().isAbortOnVersionConflict()).isTrue();
assertThat(request.request().getBatchSize()).isEqualTo(10);
assertThat(request.request().getMaxDocs()).isEqualTo(12);
assertThat(request.request().getPipeline()).isEqualTo("pipeline");
assertThat(request.request().getRequestsPerSecond()).isEqualTo(5F);
assertThat(request.request().getShouldStoreResult()).isFalse();
assertThat(request.request().getSlices()).isEqualTo(4);
assertThat(request.request().getScript().getIdOrCode()).isEqualTo("script_name");
assertThat(request.request().getScript().getType()).isEqualTo(org.elasticsearch.script.ScriptType.STORED);
}
@Data @Data
@Document(indexName = "test-index-sample-core-transport-template", replicas = 0, refreshInterval = "-1") @Document(indexName = "test-index-sample-core-transport-template", replicas = 0, refreshInterval = "-1")
static class SampleEntity { static class SampleEntity {