Support Delete by query with es parameters.

Original Pull Request #2875
Closes #2865
This commit is contained in:
Aouichaoui Youssef 2024-03-26 19:18:26 +01:00 committed by GitHub
parent d2b3ba94f6
commit 496b8d62a4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 1009 additions and 0 deletions

View File

@ -54,6 +54,7 @@ import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.BaseQueryBuilder;
import org.springframework.data.elasticsearch.core.query.BulkOptions;
import org.springframework.data.elasticsearch.core.query.ByQueryResponse;
import org.springframework.data.elasticsearch.core.query.DeleteQuery;
import org.springframework.data.elasticsearch.core.query.IndexQuery;
import org.springframework.data.elasticsearch.core.query.MoreLikeThisQuery;
import org.springframework.data.elasticsearch.core.query.Query;
@ -177,6 +178,11 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
doBulkOperation(queries, bulkOptions, index);
}
@Override
public ByQueryResponse delete(DeleteQuery query, Class<?> clazz) {
return delete(query, clazz, getIndexCoordinatesFor(clazz));
}
@Override
public ByQueryResponse delete(Query query, Class<?> clazz, IndexCoordinates index) {
@ -190,6 +196,18 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
return responseConverter.byQueryResponse(response);
}
@Override
public ByQueryResponse delete(DeleteQuery query, Class<?> clazz, IndexCoordinates index) {
Assert.notNull(query, "query must not be null");
DeleteByQueryRequest request = requestConverter.documentDeleteByQueryRequest(query, routingResolver.getRouting(),
clazz, index, getRefreshPolicy());
DeleteByQueryResponse response = execute(client -> client.deleteByQuery(request));
return responseConverter.byQueryResponse(response);
}
@Override
public UpdateResponse update(UpdateQuery updateQuery, IndexCoordinates index) {

View File

@ -25,6 +25,7 @@ import co.elastic.clients.elasticsearch.core.search.ResponseBody;
import co.elastic.clients.json.JsonpMapper;
import co.elastic.clients.transport.Version;
import co.elastic.clients.transport.endpoints.BooleanResponse;
import org.springframework.data.elasticsearch.core.query.DeleteQuery;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.function.Tuple2;
@ -180,6 +181,15 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
return Mono.from(execute(client -> client.deleteByQuery(request))).map(responseConverter::byQueryResponse);
}
@Override
public Mono<ByQueryResponse> delete(DeleteQuery query, Class<?> entityType, IndexCoordinates index) {
Assert.notNull(query, "query must not be null");
DeleteByQueryRequest request = requestConverter.documentDeleteByQueryRequest(query, routingResolver.getRouting(),
entityType, index, getRefreshPolicy());
return Mono.from(execute(client -> client.deleteByQuery(request))).map(responseConverter::byQueryResponse);
}
@Override
public <T> Mono<T> get(String id, Class<T> entityType, IndexCoordinates index) {

View File

@ -968,6 +968,79 @@ class RequestConverter {
});
}
public DeleteByQueryRequest documentDeleteByQueryRequest(DeleteQuery query, @Nullable String routing, Class<?> clazz,
IndexCoordinates index, @Nullable RefreshPolicy refreshPolicy) {
Assert.notNull(query, "query must not be null");
Assert.notNull(index, "index must not be null");
return DeleteByQueryRequest.of(dqb -> {
dqb.index(Arrays.asList(index.getIndexNames())) //
.query(getQuery(query.getQuery(), clazz))//
.refresh(deleteByQueryRefresh(refreshPolicy))
.requestsPerSecond(query.getRequestsPerSecond())
.maxDocs(query.getMaxDocs())
.scroll(time(query.getScroll()))
.scrollSize(query.getScrollSize());
if (query.getRouting() != null) {
dqb.routing(query.getRouting());
} else if (StringUtils.hasText(routing)) {
dqb.routing(routing);
}
if (query.getQ() != null) {
dqb.q(query.getQ())
.analyzer(query.getAnalyzer())
.analyzeWildcard(query.getAnalyzeWildcard())
.defaultOperator(operator(query.getDefaultOperator()))
.df(query.getDf())
.lenient(query.getLenient());
}
if (query.getExpandWildcards() != null && !query.getExpandWildcards().isEmpty()) {
dqb.expandWildcards(expandWildcards(query.getExpandWildcards()));
}
if (query.getStats() != null && !query.getStats().isEmpty()) {
dqb.stats(query.getStats());
}
if (query.getSlices() != null) {
dqb.slices(sb -> sb.value(query.getSlices()));
}
if (query.getSort() != null) {
ElasticsearchPersistentEntity<?> persistentEntity = getPersistentEntity(clazz);
List<SortOptions> sortOptions = getSortOptions(query.getSort(), persistentEntity);
if (!sortOptions.isEmpty()) {
dqb.sort(
sortOptions.stream()
.map(sortOption -> {
String order = "asc";
var sortField = sortOption.field();
if (sortField.order() != null) {
order = sortField.order().jsonValue();
}
return sortField.field() + ":" + order;
})
.collect(Collectors.toList())
);
}
}
dqb.allowNoIndices(query.getAllowNoIndices())
.conflicts(conflicts(query.getConflicts()))
.ignoreUnavailable(query.getIgnoreUnavailable())
.preference(query.getPreference())
.requestCache(query.getRequestCache())
.searchType(searchType(query.getSearchType()))
.searchTimeout(time(query.getSearchTimeout()))
.terminateAfter(query.getTerminateAfter())
.timeout(time(query.getTimeout()))
.version(query.getVersion());
return dqb;
});
}
public UpdateRequest<Document, ?> documentUpdateRequest(UpdateQuery query, IndexCoordinates index,
@Nullable RefreshPolicy refreshPolicy, @Nullable String routing) {

View File

@ -18,6 +18,7 @@ package org.springframework.data.elasticsearch.client.elc;
import co.elastic.clients.elasticsearch._types.*;
import co.elastic.clients.elasticsearch._types.mapping.FieldType;
import co.elastic.clients.elasticsearch._types.mapping.TypeMapping;
import co.elastic.clients.elasticsearch._types.query_dsl.Operator;
import co.elastic.clients.elasticsearch.core.search.BoundaryScanner;
import co.elastic.clients.elasticsearch.core.search.HighlighterEncoder;
import co.elastic.clients.elasticsearch.core.search.HighlighterFragmenter;
@ -46,6 +47,8 @@ import org.springframework.data.elasticsearch.core.query.Order;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.core.query.RescorerQuery;
import org.springframework.data.elasticsearch.core.query.UpdateResponse;
import org.springframework.data.elasticsearch.core.query.types.ConflictsType;
import org.springframework.data.elasticsearch.core.query.types.OperatorType;
import org.springframework.data.elasticsearch.core.reindex.ReindexRequest;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@ -500,4 +503,26 @@ final class TypeUtils {
});
return mappedParams;
}
/**
* Convert a spring-data-elasticsearch operator to an Elasticsearch operator.
*
* @param operator spring-data-elasticsearch operator.
* @return an Elasticsearch Operator.
*/
@Nullable
static Operator operator(@Nullable OperatorType operator) {
return operator != null ? Operator.valueOf(operator.name()) : null;
}
/**
* Convert a spring-data-elasticsearch {@literal conflicts} to an Elasticsearch {@literal conflicts}.
*
* @param conflicts spring-data-elasticsearch {@literal conflicts}.
* @return an Elasticsearch {@literal conflicts}.
*/
@Nullable
static Conflicts conflicts(@Nullable ConflictsType conflicts) {
return conflicts != null ? Conflicts.valueOf(conflicts.name()) : null;
}
}

View File

@ -15,6 +15,7 @@
*/
package org.springframework.data.elasticsearch.core;
import org.springframework.data.elasticsearch.core.query.DeleteQuery;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.publisher.Sinks;
@ -411,6 +412,11 @@ abstract public class AbstractReactiveElasticsearchTemplate
public Mono<ByQueryResponse> delete(Query query, Class<?> entityType) {
return delete(query, entityType, getIndexCoordinatesFor(entityType));
}
@Override
public Mono<ByQueryResponse> delete(DeleteQuery query, Class<?> entityType) {
return delete(query, entityType, getIndexCoordinatesFor(entityType));
}
// endregion
// region SearchDocument

View File

@ -21,6 +21,7 @@ import java.util.List;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.BulkOptions;
import org.springframework.data.elasticsearch.core.query.ByQueryResponse;
import org.springframework.data.elasticsearch.core.query.DeleteQuery;
import org.springframework.data.elasticsearch.core.query.IndexQuery;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.core.query.UpdateQuery;
@ -279,9 +280,21 @@ public interface DocumentOperations {
* {@link org.springframework.data.elasticsearch.annotations.Document}
* @return response with detailed information
* @since 4.1
* @deprecated since 5.3.0, use {@link #delete(DeleteQuery, Class)}
*/
ByQueryResponse delete(Query query, Class<?> clazz);
/**
* Delete all records matching the query.
*
* @param query query defining the objects
* @param clazz The entity class must be annotated with
* {@link org.springframework.data.elasticsearch.annotations.Document}
* @return response with detailed information
* @since 5.3
*/
ByQueryResponse delete(DeleteQuery query, Class<?> clazz);
/**
* Delete all records matching the query.
*
@ -290,9 +303,22 @@ public interface DocumentOperations {
* {@link org.springframework.data.elasticsearch.annotations.Document}
* @param index the index from which to delete
* @return response with detailed information
* @deprecated since 5.3.0, use {@link #delete(DeleteQuery, Class, IndexCoordinates)}
*/
ByQueryResponse delete(Query query, Class<?> clazz, IndexCoordinates index);
/**
* Delete all records matching the query.
*
* @param query query defining the objects
* @param clazz The entity class must be annotated with
* {@link org.springframework.data.elasticsearch.annotations.Document}
* @param index the index from which to delete
* @return response with detailed information
* @since 5.3
*/
ByQueryResponse delete(DeleteQuery query, Class<?> clazz, IndexCoordinates index);
/**
* Partially update a document by the given entity.
*

View File

@ -15,6 +15,7 @@
*/
package org.springframework.data.elasticsearch.core;
import org.springframework.data.elasticsearch.core.query.DeleteQuery;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@ -331,9 +332,20 @@ public interface ReactiveDocumentOperations {
* @param query must not be {@literal null}.
* @param entityType must not be {@literal null}.
* @return a {@link Mono} emitting the number of the removed documents.
* @deprecated since 5.3.0, use {@link #delete(DeleteQuery, Class)}
*/
Mono<ByQueryResponse> delete(Query query, Class<?> entityType);
/**
* Delete the documents matching the given {@link Query} extracting index from entity metadata.
*
* @param query must not be {@literal null}.
* @param entityType must not be {@literal null}.
* @return a {@link Mono} emitting the number of the removed documents.
* @since 5.3
*/
Mono<ByQueryResponse> delete(DeleteQuery query, Class<?> entityType);
/**
* Delete the documents matching the given {@link Query} extracting index from entity metadata.
*
@ -341,9 +353,21 @@ public interface ReactiveDocumentOperations {
* @param entityType must not be {@literal null}.
* @param index the target index, must not be {@literal null}
* @return a {@link Mono} emitting the number of the removed documents.
* @deprecated since 5.3.0, use {@link #delete(DeleteQuery, Class, IndexCoordinates)}
*/
Mono<ByQueryResponse> delete(Query query, Class<?> entityType, IndexCoordinates index);
/**
* Delete the documents matching the given {@link Query} extracting index from entity metadata.
*
* @param query must not be {@literal null}.
* @param entityType must not be {@literal null}.
* @param index the target index, must not be {@literal null}
* @return a {@link Mono} emitting the number of the removed documents.
* @since 5.3
*/
Mono<ByQueryResponse> delete(DeleteQuery query, Class<?> entityType, IndexCoordinates index);
/**
* Partial update of the document.
*

View File

@ -0,0 +1,736 @@
/*
* Copyright 2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.core.query;
import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.core.query.Query.SearchType;
import org.springframework.data.elasticsearch.core.query.types.ConflictsType;
import org.springframework.data.elasticsearch.core.query.types.OperatorType;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import java.time.Duration;
import java.util.EnumSet;
import java.util.List;
/**
* Defines a delete request.
*
* @author Aouichaoui Youssef
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-delete-by-query.html">docs</a>
*/
public class DeleteQuery {
// For Lucene query
/**
* Query in the Lucene query string syntax.
*/
@Nullable
private final String q;
/**
* If true, wildcard and prefix queries are analyzed. Defaults to false.
* This parameter can only be used when the lucene query {@code q} parameter is specified.
*/
@Nullable
private final Boolean analyzeWildcard;
/**
* Analyzer to use for the query string.
* This parameter can only be used when the lucene query {@code q} parameter is specified.
*/
@Nullable
private final String analyzer;
/**
* The default operator for a query string query: {@literal AND} or {@literal OR}. Defaults to {@literal OR}.
* This parameter can only be used when the lucene query {@code q} parameter is specified.
*/
@Nullable
private final OperatorType defaultOperator;
/**
* Field to be used as the default when no field prefix is specified in the query string.
* This parameter can only be used when the lucene query {@code q} parameter is specified.
* <p>
* e.g: {@code {"query":{"prefix":{"user.name":{"value":"es"}}}} }
*/
@Nullable
private final String df;
/**
* If a query contains errors related to the format of the data being entered, they will be disregarded unless specified otherwise.
* By default, this feature is turned off.
*/
@Nullable
private final Boolean lenient;
// For ES query
/**
* An error will occur if the condition is {@code false} and any of the following are true: a wildcard expression,
* an index alias, or the {@literal _all value} only targets missing or closed indices.
* By default, this is set to {@code true}.
*/
@Nullable
private final Boolean allowNoIndices;
/**
* Define the types of conflicts that occur when a query encounters version conflicts: abort or proceed.
* Defaults to abort.
*/
@Nullable
private final ConflictsType conflicts;
/**
* Type of index that wildcard patterns can match.
* Defaults to {@literal open}.
*/
@Nullable
private final EnumSet<IndicesOptions.WildcardStates> expandWildcards;
/**
* An error occurs if it is directed at an index that is missing or closed when it is {@code false}.
* By default, this is set to {@code false}.
*/
@Nullable
private final Boolean ignoreUnavailable;
/**
* Maximum number of documents to process.
* Defaults to all documents.
*/
@Nullable
private final Long maxDocs;
/**
* Specifies the node or shard the operation should be performed on.
*/
@Nullable
private final String preference;
/**
* Use the request cache when it is {@code true}.
* By default, use the index-level setting.
*/
@Nullable
private final Boolean requestCache;
/**
* Refreshes all shards involved in the deleting by query after the request completes when it is {@code true}.
* By default, this is set to {@code false}.
*/
@Nullable
private final Boolean refresh;
/**
* Limited this request to a certain number of sub-requests per second.
* By default, this is set to {@code -1} (no throttle).
*/
@Nullable
private final Float requestsPerSecond;
/**
* Custom value used to route operations to a specific shard.
*/
@Nullable
private final String routing;
/**
* Period to retain the search context for scrolling.
*/
@Nullable
private final Duration scroll;
/**
* Size of the scroll request that powers the operation.
* By default, this is set to {@code 1000}.
*/
@Nullable
private final Long scrollSize;
/**
* The type of the search operation.
*/
@Nullable
private final SearchType searchType;
/**
* Explicit timeout for each search request.
* By default, this is set to no timeout.
*/
@Nullable
private final Duration searchTimeout;
/**
* The number of slices this task should be divided into.
* By default, this is set to {@code 1} meaning the task isnt sliced into subtasks.
*/
@Nullable
private final Integer slices;
/**
* Sort search results in a specific order.
*/
@Nullable
private final Sort sort;
/**
* Specific {@code tag} of the request for logging and statistical purposes.
*/
@Nullable
private final List<String> stats;
/**
* The Maximum number of documents that can be collected for each shard.
* If a query exceeds this limit, Elasticsearch will stop the query.
*/
@Nullable
private final Long terminateAfter;
/**
* Period each deletion request waits for active shards.
* By default, this is set to {@code 1m} (one minute).
*/
@Nullable
private final Duration timeout;
/**
* Returns the document version as part of a hit.
*/
@Nullable
private final Boolean version;
// Body
/**
* Query that specifies the documents to delete.
*/
private final Query query;
public static Builder builder(Query query) {
return new Builder(query);
}
private DeleteQuery(Builder builder) {
this.q = builder.luceneQuery;
this.analyzeWildcard = builder.analyzeWildcard;
this.analyzer = builder.analyzer;
this.defaultOperator = builder.defaultOperator;
this.df = builder.defaultField;
this.lenient = builder.lenient;
this.allowNoIndices = builder.allowNoIndices;
this.conflicts = builder.conflicts;
this.expandWildcards = builder.expandWildcards;
this.ignoreUnavailable = builder.ignoreUnavailable;
this.maxDocs = builder.maxDocs;
this.preference = builder.preference;
this.requestCache = builder.requestCache;
this.refresh = builder.refresh;
this.requestsPerSecond = builder.requestsPerSecond;
this.routing = builder.routing;
this.scroll = builder.scrollTime;
this.scrollSize = builder.scrollSize;
this.searchType = builder.searchType;
this.searchTimeout = builder.searchTimeout;
this.slices = builder.slices;
this.sort = builder.sort;
this.stats = builder.stats;
this.terminateAfter = builder.terminateAfter;
this.timeout = builder.timeout;
this.version = builder.version;
this.query = builder.query;
}
@Nullable
public String getQ() {
return q;
}
@Nullable
public Boolean getAnalyzeWildcard() {
return analyzeWildcard;
}
@Nullable
public String getAnalyzer() {
return analyzer;
}
@Nullable
public OperatorType getDefaultOperator() {
return defaultOperator;
}
@Nullable
public String getDf() {
return df;
}
@Nullable
public Boolean getLenient() {
return lenient;
}
@Nullable
public Boolean getAllowNoIndices() {
return allowNoIndices;
}
@Nullable
public ConflictsType getConflicts() {
return conflicts;
}
@Nullable
public EnumSet<IndicesOptions.WildcardStates> getExpandWildcards() {
return expandWildcards;
}
@Nullable
public Boolean getIgnoreUnavailable() {
return ignoreUnavailable;
}
@Nullable
public Long getMaxDocs() {
return maxDocs;
}
@Nullable
public String getPreference() {
return preference;
}
@Nullable
public Boolean getRequestCache() {
return requestCache;
}
@Nullable
public Boolean getRefresh() {
return refresh;
}
@Nullable
public Float getRequestsPerSecond() {
return requestsPerSecond;
}
@Nullable
public String getRouting() {
return routing;
}
@Nullable
public Duration getScroll() {
return scroll;
}
@Nullable
public Long getScrollSize() {
return scrollSize;
}
@Nullable
public SearchType getSearchType() {
return searchType;
}
@Nullable
public Duration getSearchTimeout() {
return searchTimeout;
}
@Nullable
public Integer getSlices() {
return slices;
}
@Nullable
public Sort getSort() {
return sort;
}
@Nullable
public List<String> getStats() {
return stats;
}
@Nullable
public Long getTerminateAfter() {
return terminateAfter;
}
@Nullable
public Duration getTimeout() {
return timeout;
}
@Nullable
public Boolean getVersion() {
return version;
}
@Nullable
public Query getQuery() {
return query;
}
public static final class Builder {
// For Lucene query
@Nullable
private String luceneQuery;
@Nullable
private Boolean analyzeWildcard;
@Nullable
private String analyzer;
@Nullable
private OperatorType defaultOperator;
@Nullable
private String defaultField;
@Nullable
private Boolean lenient;
// For ES query
@Nullable
private Boolean allowNoIndices;
@Nullable
private ConflictsType conflicts;
@Nullable
private EnumSet<IndicesOptions.WildcardStates> expandWildcards;
@Nullable
private Boolean ignoreUnavailable;
@Nullable
private Long maxDocs;
@Nullable
private String preference;
@Nullable
private Boolean requestCache;
@Nullable
private Boolean refresh;
@Nullable
private Float requestsPerSecond;
@Nullable
private String routing;
@Nullable
private Duration scrollTime;
@Nullable
private Long scrollSize;
@Nullable
private SearchType searchType;
@Nullable
private Duration searchTimeout;
@Nullable
private Integer slices;
@Nullable
private Sort sort;
@Nullable
private List<String> stats;
@Nullable
private Long terminateAfter;
@Nullable
private Duration timeout;
@Nullable
private Boolean version;
// Body
private final Query query;
private Builder(Query query) {
Assert.notNull(query, "query must not be null");
this.query = query;
}
/**
* Query in the Lucene query string syntax.
*/
public Builder withLuceneQuery(@Nullable String luceneQuery) {
this.luceneQuery = luceneQuery;
return this;
}
/**
* If true, wildcard and prefix queries are analyzed. Defaults to false.
* This parameter can only be used when the lucene query {@code q} parameter is specified.
*/
public Builder withAnalyzeWildcard(@Nullable Boolean analyzeWildcard) {
this.analyzeWildcard = analyzeWildcard;
return this;
}
/**
* Analyzer to use for the query string.
* This parameter can only be used when the lucene query {@code q} parameter is specified.
*/
public Builder withAnalyzer(@Nullable String analyzer) {
this.analyzer = analyzer;
return this;
}
/**
* The default operator for a query string query: {@literal AND} or {@literal OR}. Defaults to {@literal OR}.
* This parameter can only be used when the lucene query {@code q} parameter is specified.
*/
public Builder withDefaultOperator(@Nullable OperatorType defaultOperator) {
this.defaultOperator = defaultOperator;
return this;
}
/**
* Field to be used as the default when no field prefix is specified in the query string.
* This parameter can only be used when the lucene query {@code q} parameter is specified.
* <p>
* e.g: {@code {"query":{"prefix":{"user.name":{"value":"es"}}}} }
*/
public Builder withDefaultField(@Nullable String defaultField) {
this.defaultField = defaultField;
return this;
}
/**
* If a query contains errors related to the format of the data being entered, they will be disregarded unless specified otherwise.
* By default, this feature is turned off.
*/
public Builder withLenient(@Nullable Boolean lenient) {
this.lenient = lenient;
return this;
}
/**
* An error will occur if the condition is {@code false} and any of the following are true: a wildcard expression,
* an index alias, or the {@literal _all value} only targets missing or closed indices.
* By default, this is set to {@code true}.
*/
public Builder withAllowNoIndices(@Nullable Boolean allowNoIndices) {
this.allowNoIndices = allowNoIndices;
return this;
}
/**
* Define the types of conflicts that occur when a query encounters version conflicts: abort or proceed.
* Defaults to abort.
*/
public Builder withConflicts(@Nullable ConflictsType conflicts) {
this.conflicts = conflicts;
return this;
}
/**
* Type of index that wildcard patterns can match.
* Defaults to {@literal open}.
*/
public Builder setExpandWildcards(@Nullable EnumSet<IndicesOptions.WildcardStates> expandWildcards) {
this.expandWildcards = expandWildcards;
return this;
}
/**
* An error occurs if it is directed at an index that is missing or closed when it is {@code false}.
* By default, this is set to {@code false}.
*/
public Builder withIgnoreUnavailable(@Nullable Boolean ignoreUnavailable) {
this.ignoreUnavailable = ignoreUnavailable;
return this;
}
/**
* Maximum number of documents to process.
* Defaults to all documents.
*/
public Builder withMaxDocs(@Nullable Long maxDocs) {
this.maxDocs = maxDocs;
return this;
}
/**
* Specifies the node or shard the operation should be performed on.
*/
public Builder withPreference(@Nullable String preference) {
this.preference = preference;
return this;
}
/**
* Use the request cache when it is {@code true}.
* By default, use the index-level setting.
*/
public Builder withRequestCache(@Nullable Boolean requestCache) {
this.requestCache = requestCache;
return this;
}
/**
* Refreshes all shards involved in the deleting by query after the request completes when it is {@code true}.
* By default, this is set to {@code false}.
*/
public Builder withRefresh(@Nullable Boolean refresh) {
this.refresh = refresh;
return this;
}
/**
* Limited this request to a certain number of sub-requests per second.
* By default, this is set to {@code -1} (no throttle).
*/
public Builder withRequestsPerSecond(@Nullable Float requestsPerSecond) {
this.requestsPerSecond = requestsPerSecond;
return this;
}
/**
* Custom value used to route operations to a specific shard.
*/
public Builder withRouting(@Nullable String routing) {
this.routing = routing;
return this;
}
/**
* Period to retain the search context for scrolling.
*/
public Builder withScrollTime(@Nullable Duration scrollTime) {
this.scrollTime = scrollTime;
return this;
}
/**
* Size of the scroll request that powers the operation.
* By default, this is set to {@code 1000}.
*/
public Builder withScrollSize(@Nullable Long scrollSize) {
this.scrollSize = scrollSize;
return this;
}
/**
* The type of the search operation.
*/
public Builder withSearchType(@Nullable SearchType searchType) {
this.searchType = searchType;
return this;
}
/**
* Explicit timeout for each search request.
* By default, this is set to no timeout.
*/
public Builder withSearchTimeout(@Nullable Duration searchTimeout) {
this.searchTimeout = searchTimeout;
return this;
}
/**
* The number of slices this task should be divided into.
* By default, this is set to {@code 1} meaning the task isnt sliced into subtasks.
*/
public Builder withSlices(@Nullable Integer slices) {
this.slices = slices;
return this;
}
/**
* Sort search results in a specific order.
*/
public Builder withSort(@Nullable Sort sort) {
this.sort = sort;
return this;
}
/**
* Specific {@code tag} of the request for logging and statistical purposes.
*/
public Builder withStats(@Nullable List<String> stats) {
this.stats = stats;
return this;
}
/**
* The Maximum number of documents that can be collected for each shard.
* If a query exceeds this limit, Elasticsearch will stop the query.
*/
public Builder withTerminateAfter(@Nullable Long terminateAfter) {
this.terminateAfter = terminateAfter;
return this;
}
/**
* Period each deletion request waits for active shards.
* By default, this is set to {@code 1m} (one minute).
*/
public Builder withTimeout(@Nullable Duration timeout) {
this.timeout = timeout;
return this;
}
/**
* Returns the document version as part of a hit.
*/
public Builder withVersion(@Nullable Boolean version) {
this.version = version;
return this;
}
public DeleteQuery build() {
if (luceneQuery == null) {
if (defaultField != null) {
throw new IllegalArgumentException("When defining the df parameter, you must include the Lucene query.");
}
if (analyzer != null) {
throw new IllegalArgumentException("When defining the analyzer parameter, you must include the Lucene query.");
}
if (analyzeWildcard != null) {
throw new IllegalArgumentException("When defining the analyzeWildcard parameter, you must include the Lucene query.");
}
if (defaultOperator != null) {
throw new IllegalArgumentException("When defining the defaultOperator parameter, you must include the Lucene query.");
}
if (lenient != null) {
throw new IllegalArgumentException("When defining the lenient parameter, you must include the Lucene query.");
}
}
return new DeleteQuery(this);
}
}
}

View File

@ -0,0 +1,25 @@
/*
* Copyright 2024 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.types;
/**
* Define the types of conflicts that occur when a query encounters version conflicts.
*
* @author Aouichaoui Youssef
*/
public enum ConflictsType {
Abort, Proceed
}

View File

@ -0,0 +1,25 @@
/*
* Copyright 2024 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.types;
/**
* Define the default operator for a query string query.
*
* @author Aouichaoui Youssef
*/
public enum OperatorType {
And, Or
}

View File

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

View File

@ -3776,6 +3776,44 @@ public abstract class ElasticsearchIntegrationTests {
}).isInstanceOf(VersionConflictException.class);
}
@Test // GH-2865
public void shouldDeleteDocumentForGivenQueryUsingParameters() {
// Given
String documentId = nextIdAsString();
SampleEntity sampleEntity = SampleEntity.builder().id(documentId).message("some message")
.version(System.currentTimeMillis()).build();
IndexQuery indexQuery = getIndexQuery(sampleEntity);
String indexName = indexNameProvider.indexName();
operations.index(indexQuery, IndexCoordinates.of(indexName));
// When
final Query query = getTermQuery("id", documentId);
final DeleteQuery deleteQuery = DeleteQuery.builder(query).withSlices(2).build();
ByQueryResponse result = operations.delete(deleteQuery, SampleEntity.class, IndexCoordinates.of(indexName));
// Then
assertThat(result.getDeleted()).isEqualTo(1);
SearchHits<SampleEntity> searchHits = operations.search(query, SampleEntity.class,
IndexCoordinates.of(indexName));
assertThat(searchHits.getTotalHits()).isEqualTo(0);
}
@Test
public void shouldDeleteDocumentForGivenQueryAndUnavailableIndex() {
// Given
String indexName = UUID.randomUUID().toString();
// When
final Query query = operations.matchAllQuery();
final DeleteQuery deleteQuery = DeleteQuery.builder(query).withIgnoreUnavailable(true).build();
ByQueryResponse result = operations.delete(deleteQuery, SampleEntity.class, IndexCoordinates.of(indexName));
// Then
assertThat(result.getDeleted()).isEqualTo(0);
}
// region entities
@Document(indexName = "#{@indexNameProvider.indexName()}")
@Setting(shards = 1, replicas = 0, refreshInterval = "-1")