From 71cb836db60d526de8292f8205f4e94d0cd5fb9b Mon Sep 17 00:00:00 2001 From: Youssef Awichawi Date: Fri, 15 Mar 2024 15:28:09 +0100 Subject: [PATCH 1/7] Support Delete by query with es parameters. Signed-off-by: Youssef Aouichaoui --- .../client/elc/ElasticsearchTemplate.java | 18 + .../elc/ReactiveElasticsearchTemplate.java | 10 + .../client/elc/RequestConverter.java | 54 ++ ...AbstractReactiveElasticsearchTemplate.java | 6 + .../core/DocumentOperations.java | 26 + .../core/ReactiveDocumentOperations.java | 24 + .../elasticsearch/core/query/DeleteQuery.java | 478 ++++++++++++++++++ 7 files changed, 616 insertions(+) create mode 100644 src/main/java/org/springframework/data/elasticsearch/core/query/DeleteQuery.java diff --git a/src/main/java/org/springframework/data/elasticsearch/client/elc/ElasticsearchTemplate.java b/src/main/java/org/springframework/data/elasticsearch/client/elc/ElasticsearchTemplate.java index e535f857f9..20ddf2a0db 100644 --- a/src/main/java/org/springframework/data/elasticsearch/client/elc/ElasticsearchTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/client/elc/ElasticsearchTemplate.java @@ -54,6 +54,7 @@ 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 void bulkUpdate(List queries, BulkOptions bulkOptions, Index 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 ByQueryResponse delete(Query query, Class clazz, IndexCoordinates inde 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) { diff --git a/src/main/java/org/springframework/data/elasticsearch/client/elc/ReactiveElasticsearchTemplate.java b/src/main/java/org/springframework/data/elasticsearch/client/elc/ReactiveElasticsearchTemplate.java index e9e7fb9812..20b6c038ed 100644 --- a/src/main/java/org/springframework/data/elasticsearch/client/elc/ReactiveElasticsearchTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/client/elc/ReactiveElasticsearchTemplate.java @@ -25,6 +25,7 @@ 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 Mono delete(Query query, Class entityType, IndexCoord return Mono.from(execute(client -> client.deleteByQuery(request))).map(responseConverter::byQueryResponse); } + @Override + public Mono 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 Mono get(String id, Class entityType, IndexCoordinates index) { diff --git a/src/main/java/org/springframework/data/elasticsearch/client/elc/RequestConverter.java b/src/main/java/org/springframework/data/elasticsearch/client/elc/RequestConverter.java index be7ff96ff6..d4bf397211 100644 --- a/src/main/java/org/springframework/data/elasticsearch/client/elc/RequestConverter.java +++ b/src/main/java/org/springframework/data/elasticsearch/client/elc/RequestConverter.java @@ -968,6 +968,60 @@ public DeleteByQueryRequest documentDeleteByQueryRequest(Query query, @Nullable }); } + 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"); + + // Old documentDeleteByQueryRequest needs to be refactored. + return DeleteByQueryRequest.of(dqb -> { + dqb.index(Arrays.asList(index.getIndexNames())) // + .query(getQuery(query, clazz))// + .refresh(deleteByQueryRefresh(refreshPolicy)); + + if (query.isLimiting()) { + // noinspection ConstantConditions + dqb.maxDocs(Long.valueOf(query.getMaxResults())); + } + + dqb.scroll(time(query.getScrollTime())) + .scrollSize(query.getScrollSize()); + + if (query.getRoute() != null) { + dqb.routing(query.getRoute()); + } else if (StringUtils.hasText(routing)) { + dqb.routing(routing); + } + + if (query.getQ() != null) { + dqb.q(query.getQ()) + .analyzer(query.getAnalyzer()) + .analyzeWildcard(query.getAnalyzeWildcard()) + .defaultOperator(query.getDefaultOperator()) + .df(query.getDf()) + .lenient(query.getLenient()); + } + + if (query.getExpandWildcards() != null && !query.getExpandWildcards().isEmpty()) { + dqb.expandWildcards(expandWildcards(query.getExpandWildcards())); + } + dqb.allowNoIndices(query.getAllowNoIndices()) + .conflicts(query.getConflicts()) + .ignoreUnavailable(query.getIgnoreUnavailable()) + .preference(query.getPreference()) + .requestCache(query.getRequestCache()) + .searchType(searchType(query.getSearchType())) + .searchTimeout(time(query.getSearchTimeout())) + .slices(query.getSlices()) + .stats(query.getStats()) + .terminateAfter(query.getTerminateAfter()) + .timeout(time(query.getTimeout())) + .version(query.getVersion()); + + return dqb; + }); + } + public UpdateRequest documentUpdateRequest(UpdateQuery query, IndexCoordinates index, @Nullable RefreshPolicy refreshPolicy, @Nullable String routing) { diff --git a/src/main/java/org/springframework/data/elasticsearch/core/AbstractReactiveElasticsearchTemplate.java b/src/main/java/org/springframework/data/elasticsearch/core/AbstractReactiveElasticsearchTemplate.java index 98aa9a176c..ccd0c440bb 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/AbstractReactiveElasticsearchTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/AbstractReactiveElasticsearchTemplate.java @@ -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 @@ public Mono delete(String id, IndexCoordinates index) { public Mono delete(Query query, Class entityType) { return delete(query, entityType, getIndexCoordinatesFor(entityType)); } + + @Override + public Mono delete(DeleteQuery query, Class entityType) { + return delete(query, entityType, getIndexCoordinatesFor(entityType)); + } // endregion // region SearchDocument diff --git a/src/main/java/org/springframework/data/elasticsearch/core/DocumentOperations.java b/src/main/java/org/springframework/data/elasticsearch/core/DocumentOperations.java index 04e147007b..a00750d526 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/DocumentOperations.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/DocumentOperations.java @@ -21,6 +21,7 @@ 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 @@ default void bulkUpdate(List queries, IndexCoordinates index) { * {@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 @@ default void bulkUpdate(List queries, IndexCoordinates index) { * {@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. * diff --git a/src/main/java/org/springframework/data/elasticsearch/core/ReactiveDocumentOperations.java b/src/main/java/org/springframework/data/elasticsearch/core/ReactiveDocumentOperations.java index 5aef1bc2f5..f228be0b39 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/ReactiveDocumentOperations.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ReactiveDocumentOperations.java @@ -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 @@ default Mono bulkUpdate(List queries, IndexCoordinates index) * @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 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 delete(DeleteQuery query, Class entityType); + /** * Delete the documents matching the given {@link Query} extracting index from entity metadata. * @@ -341,9 +353,21 @@ default Mono bulkUpdate(List queries, IndexCoordinates index) * @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 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 delete(DeleteQuery query, Class entityType, IndexCoordinates index); + /** * Partial update of the document. * diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/DeleteQuery.java b/src/main/java/org/springframework/data/elasticsearch/core/query/DeleteQuery.java new file mode 100644 index 0000000000..fa1983d8d9 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/core/query/DeleteQuery.java @@ -0,0 +1,478 @@ +/* + * Copyright 2013-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 co.elastic.clients.elasticsearch._types.Conflicts; +import co.elastic.clients.elasticsearch._types.Slices; +import co.elastic.clients.elasticsearch._types.query_dsl.Operator; +import org.springframework.lang.Nullable; + +import java.time.Duration; +import java.util.List; + +/** + * Defines a delete request. + * + * @author Aouichaoui Youssef + * @see docs + */ +public class DeleteQuery extends BaseQuery { + // 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 Operator 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. + *

+ * 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 + /** + * Define the types of conflicts that occur when a query encounters version conflicts: abort or proceed. + * Defaults to abort. + */ + @Nullable + private final Conflicts conflicts; + + /** + * 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; + + /** + * 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; + + /** + * Size of the scroll request that powers the operation. + * By default, this is set to {@code 1000}. + */ + @Nullable + private final Long scrollSize; + + /** + * 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 isn’t sliced into subtasks. + */ + @Nullable + private Slices slices; + + /** + * Specific {@code tag} of the request for logging and statistical purposes. + */ + @Nullable + private final List 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; + + /** + * Returns the document version as part of a hit. + */ + @Nullable + private final Boolean version; + + // Body + /** + * Query that specifies the documents to delete. + */ + @Nullable + private final Query query; + + public static Builder builder() { + return new Builder(); + } + + public DeleteQuery(Builder builder) { + super(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.conflicts = builder.conflicts; + this.ignoreUnavailable = builder.ignoreUnavailable; + this.requestsPerSecond = builder.requestsPerSecond; + this.scrollSize = builder.scrollSize; + this.searchTimeout = builder.searchTimeout; + if (builder.slices != null) { + this.slices = Slices.of(sb -> sb.value(builder.slices)); + } + this.stats = builder.stats; + this.terminateAfter = builder.terminateAfter; + 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 Operator getDefaultOperator() { + return defaultOperator; + } + + @Nullable + public String getDf() { + return df; + } + + @Nullable + public Boolean getLenient() { + return lenient; + } + + @Nullable + public Conflicts getConflicts() { + return conflicts; + } + + @Nullable + public Boolean getIgnoreUnavailable() { + return ignoreUnavailable; + } + + @Nullable + public Float getRequestsPerSecond() { + return requestsPerSecond; + } + + @Nullable + public Long getScrollSize() { + return scrollSize; + } + + @Nullable + public Duration getSearchTimeout() { + return searchTimeout; + } + + @Nullable + public Slices getSlices() { + return slices; + } + + @Nullable + public List getStats() { + return stats; + } + + @Nullable + public Long getTerminateAfter() { + return terminateAfter; + } + + @Nullable + public Boolean getVersion() { + return version; + } + + @Nullable + public Query getQuery() { + return query; + } + + public static final class Builder extends BaseQueryBuilder { + // For Lucene query + @Nullable + private String luceneQuery; + @Nullable + private Boolean analyzeWildcard; + @Nullable + private String analyzer; + @Nullable + private Operator defaultOperator; + @Nullable + private String defaultField; + @Nullable + private Boolean lenient; + + // For ES query + @Nullable + private Conflicts conflicts; + @Nullable + private Boolean ignoreUnavailable; + @Nullable + private Float requestsPerSecond; + @Nullable + private Long scrollSize; + @Nullable + private Duration searchTimeout; + @Nullable + private Integer slices; + @Nullable + private List stats; + @Nullable + private Long terminateAfter; + @Nullable + private Boolean version; + + // Body + @Nullable + private Query query; + + private Builder() { + } + + /** + * 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 Operator 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. + *

+ * 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; + } + + /** + * Define the types of conflicts that occur when a query encounters version conflicts: abort or proceed. + * Defaults to abort. + */ + public Builder withConflicts(@Nullable Conflicts conflicts) { + this.conflicts = conflicts; + + 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; + } + + /** + * 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; + } + + /** + * 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; + } + + /** + * 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 isn’t sliced into subtasks. + */ + public Builder withSlices(@Nullable Integer slices) { + this.slices = slices; + + return this; + } + + /** + * Specific {@code tag} of the request for logging and statistical purposes. + */ + public Builder withStats(@Nullable List 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; + } + + /** + * Returns the document version as part of a hit. + */ + public Builder withVersion(@Nullable Boolean version) { + this.version = version; + + return this; + } + + /** + * Query that specifies the documents to delete. + */ + public Builder withQuery(Query query) { + this.query = query; + + return this; + } + + @Override + 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); + } + } +} From 190f055921106c950b70d1df9f214b32e314885e Mon Sep 17 00:00:00 2001 From: Youssef Aouichaoui Date: Sat, 16 Mar 2024 11:43:52 +0100 Subject: [PATCH 2/7] Interpret the given query. Signed-off-by: Youssef Aouichaoui --- .../data/elasticsearch/client/elc/RequestConverter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/springframework/data/elasticsearch/client/elc/RequestConverter.java b/src/main/java/org/springframework/data/elasticsearch/client/elc/RequestConverter.java index d4bf397211..3450133f51 100644 --- a/src/main/java/org/springframework/data/elasticsearch/client/elc/RequestConverter.java +++ b/src/main/java/org/springframework/data/elasticsearch/client/elc/RequestConverter.java @@ -976,7 +976,7 @@ public DeleteByQueryRequest documentDeleteByQueryRequest(DeleteQuery query, @Nul // Old documentDeleteByQueryRequest needs to be refactored. return DeleteByQueryRequest.of(dqb -> { dqb.index(Arrays.asList(index.getIndexNames())) // - .query(getQuery(query, clazz))// + .query(getQuery(query.getQuery(), clazz))// .refresh(deleteByQueryRefresh(refreshPolicy)); if (query.isLimiting()) { From 4abb1dcb66c1d069ea2cab63e391f480d5dcf674 Mon Sep 17 00:00:00 2001 From: Youssef Aouichaoui Date: Sat, 16 Mar 2024 11:55:36 +0100 Subject: [PATCH 3/7] Handle null errors. Signed-off-by: Youssef Aouichaoui --- .../client/elc/RequestConverter.java | 4 +++- .../elasticsearch/core/query/DeleteQuery.java | 22 +++++-------------- 2 files changed, 9 insertions(+), 17 deletions(-) diff --git a/src/main/java/org/springframework/data/elasticsearch/client/elc/RequestConverter.java b/src/main/java/org/springframework/data/elasticsearch/client/elc/RequestConverter.java index 3450133f51..66f60be44b 100644 --- a/src/main/java/org/springframework/data/elasticsearch/client/elc/RequestConverter.java +++ b/src/main/java/org/springframework/data/elasticsearch/client/elc/RequestConverter.java @@ -1005,6 +1005,9 @@ public DeleteByQueryRequest documentDeleteByQueryRequest(DeleteQuery query, @Nul if (query.getExpandWildcards() != null && !query.getExpandWildcards().isEmpty()) { dqb.expandWildcards(expandWildcards(query.getExpandWildcards())); } + if (query.getStats() != null && !query.getStats().isEmpty()) { + dqb.stats(query.getStats()); + } dqb.allowNoIndices(query.getAllowNoIndices()) .conflicts(query.getConflicts()) .ignoreUnavailable(query.getIgnoreUnavailable()) @@ -1013,7 +1016,6 @@ public DeleteByQueryRequest documentDeleteByQueryRequest(DeleteQuery query, @Nul .searchType(searchType(query.getSearchType())) .searchTimeout(time(query.getSearchTimeout())) .slices(query.getSlices()) - .stats(query.getStats()) .terminateAfter(query.getTerminateAfter()) .timeout(time(query.getTimeout())) .version(query.getVersion()); diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/DeleteQuery.java b/src/main/java/org/springframework/data/elasticsearch/core/query/DeleteQuery.java index fa1983d8d9..d1fbceb9e8 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/query/DeleteQuery.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/query/DeleteQuery.java @@ -140,11 +140,10 @@ public class DeleteQuery extends BaseQuery { /** * Query that specifies the documents to delete. */ - @Nullable private final Query query; - public static Builder builder() { - return new Builder(); + public static Builder builder(Query query) { + return new Builder(query); } public DeleteQuery(Builder builder) { @@ -252,7 +251,7 @@ public Query getQuery() { return query; } - public static final class Builder extends BaseQueryBuilder { + public static final class Builder extends BaseQueryBuilder { // For Lucene query @Nullable private String luceneQuery; @@ -288,10 +287,10 @@ public static final class Builder extends BaseQueryBuilder Date: Sat, 16 Mar 2024 12:21:35 +0100 Subject: [PATCH 4/7] Add integration tests. Signed-off-by: Youssef Aouichaoui --- .../core/ElasticsearchIntegrationTests.java | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchIntegrationTests.java index 6a807e4153..5fcb77fdcd 100755 --- a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchIntegrationTests.java @@ -3776,6 +3776,44 @@ void shouldThrowVersionConflictExceptionWhenSavingInvalidVersion() { }).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 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") From d5204fa356468abe42fe1132cf763219d3bc9bde Mon Sep 17 00:00:00 2001 From: Aouichaoui Youssef <21143371+youssef3wi@users.noreply.github.com> Date: Sun, 17 Mar 2024 19:52:18 +0100 Subject: [PATCH 5/7] Update copyright headers Co-authored-by: Peter-Josef Meisch --- .../data/elasticsearch/core/query/DeleteQuery.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/DeleteQuery.java b/src/main/java/org/springframework/data/elasticsearch/core/query/DeleteQuery.java index d1fbceb9e8..2519e651aa 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/query/DeleteQuery.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/query/DeleteQuery.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2024 the original author or authors. + * 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. From a9d7d6600bcb71c4c0c7d5f1a3b31d6506065c6a Mon Sep 17 00:00:00 2001 From: Youssef Aouichaoui Date: Mon, 18 Mar 2024 14:08:51 +0100 Subject: [PATCH 6/7] Use spring-data classes. This commit updates the DeleteQuery to use internal classes instead of Elasticsearch classes. Signed-off-by: Youssef Aouichaoui --- .../client/elc/RequestConverter.java | 36 +- .../elasticsearch/client/elc/TypeUtils.java | 25 ++ .../elasticsearch/core/query/DeleteQuery.java | 307 ++++++++++++++++-- .../core/query/types/ConflictsType.java | 25 ++ .../core/query/types/OperatorType.java | 25 ++ 5 files changed, 384 insertions(+), 34 deletions(-) create mode 100644 src/main/java/org/springframework/data/elasticsearch/core/query/types/ConflictsType.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/core/query/types/OperatorType.java diff --git a/src/main/java/org/springframework/data/elasticsearch/client/elc/RequestConverter.java b/src/main/java/org/springframework/data/elasticsearch/client/elc/RequestConverter.java index 66f60be44b..dff15d1d89 100644 --- a/src/main/java/org/springframework/data/elasticsearch/client/elc/RequestConverter.java +++ b/src/main/java/org/springframework/data/elasticsearch/client/elc/RequestConverter.java @@ -977,18 +977,14 @@ public DeleteByQueryRequest documentDeleteByQueryRequest(DeleteQuery query, @Nul return DeleteByQueryRequest.of(dqb -> { dqb.index(Arrays.asList(index.getIndexNames())) // .query(getQuery(query.getQuery(), clazz))// - .refresh(deleteByQueryRefresh(refreshPolicy)); - - if (query.isLimiting()) { - // noinspection ConstantConditions - dqb.maxDocs(Long.valueOf(query.getMaxResults())); - } - - dqb.scroll(time(query.getScrollTime())) + .refresh(deleteByQueryRefresh(refreshPolicy)) + .requestsPerSecond(query.getRequestsPerSecond()) + .maxDocs(query.getMaxDocs()) + .scroll(time(query.getScroll())) .scrollSize(query.getScrollSize()); - if (query.getRoute() != null) { - dqb.routing(query.getRoute()); + if (query.getRouting() != null) { + dqb.routing(query.getRouting()); } else if (StringUtils.hasText(routing)) { dqb.routing(routing); } @@ -997,7 +993,7 @@ public DeleteByQueryRequest documentDeleteByQueryRequest(DeleteQuery query, @Nul dqb.q(query.getQ()) .analyzer(query.getAnalyzer()) .analyzeWildcard(query.getAnalyzeWildcard()) - .defaultOperator(query.getDefaultOperator()) + .defaultOperator(operator(query.getDefaultOperator())) .df(query.getDf()) .lenient(query.getLenient()); } @@ -1008,14 +1004,28 @@ public DeleteByQueryRequest documentDeleteByQueryRequest(DeleteQuery query, @Nul 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 = getSortOptions(query.getSort(), persistentEntity); + + if (!sortOptions.isEmpty()) { + dqb.sort( + sortOptions.stream() + .map(sortOption -> sortOption.field().field() + ":" + sortOption.field().order().jsonValue()) + .collect(Collectors.toList()) + ); + } + } dqb.allowNoIndices(query.getAllowNoIndices()) - .conflicts(query.getConflicts()) + .conflicts(conflicts(query.getConflicts())) .ignoreUnavailable(query.getIgnoreUnavailable()) .preference(query.getPreference()) .requestCache(query.getRequestCache()) .searchType(searchType(query.getSearchType())) .searchTimeout(time(query.getSearchTimeout())) - .slices(query.getSlices()) .terminateAfter(query.getTerminateAfter()) .timeout(time(query.getTimeout())) .version(query.getVersion()); diff --git a/src/main/java/org/springframework/data/elasticsearch/client/elc/TypeUtils.java b/src/main/java/org/springframework/data/elasticsearch/client/elc/TypeUtils.java index 83bd46dae6..98354b2671 100644 --- a/src/main/java/org/springframework/data/elasticsearch/client/elc/TypeUtils.java +++ b/src/main/java/org/springframework/data/elasticsearch/client/elc/TypeUtils.java @@ -18,6 +18,7 @@ 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.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 @@ static Map paramsMap(Map params) { }); 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; + } } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/DeleteQuery.java b/src/main/java/org/springframework/data/elasticsearch/core/query/DeleteQuery.java index 2519e651aa..de65a07c3e 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/query/DeleteQuery.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/query/DeleteQuery.java @@ -15,12 +15,14 @@ */ package org.springframework.data.elasticsearch.core.query; -import co.elastic.clients.elasticsearch._types.Conflicts; -import co.elastic.clients.elasticsearch._types.Slices; -import co.elastic.clients.elasticsearch._types.query_dsl.Operator; +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 java.time.Duration; +import java.util.EnumSet; import java.util.List; /** @@ -29,7 +31,7 @@ * @author Aouichaoui Youssef * @see docs */ -public class DeleteQuery extends BaseQuery { +public class DeleteQuery { // For Lucene query /** * Query in the Lucene query string syntax. @@ -56,7 +58,7 @@ public class DeleteQuery extends BaseQuery { * This parameter can only be used when the lucene query {@code q} parameter is specified. */ @Nullable - private final Operator defaultOperator; + private final OperatorType defaultOperator; /** * Field to be used as the default when no field prefix is specified in the query string. @@ -75,12 +77,28 @@ public class DeleteQuery extends BaseQuery { 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 Conflicts conflicts; + private final ConflictsType conflicts; + + /** + * Type of index that wildcard patterns can match. + * Defaults to {@literal open}. + */ + @Nullable + private final EnumSet expandWildcards; /** * An error occurs if it is directed at an index that is missing or closed when it is {@code false}. @@ -89,6 +107,33 @@ public class DeleteQuery extends BaseQuery { @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). @@ -96,6 +141,18 @@ public class DeleteQuery extends BaseQuery { @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}. @@ -103,6 +160,12 @@ public class DeleteQuery extends BaseQuery { @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. @@ -115,7 +178,13 @@ public class DeleteQuery extends BaseQuery { * By default, this is set to {@code 1} meaning the task isn’t sliced into subtasks. */ @Nullable - private Slices slices; + 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. @@ -130,6 +199,13 @@ public class DeleteQuery extends BaseQuery { @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. */ @@ -147,8 +223,6 @@ public static Builder builder(Query query) { } public DeleteQuery(Builder builder) { - super(builder); - this.q = builder.luceneQuery; this.analyzeWildcard = builder.analyzeWildcard; this.analyzer = builder.analyzer; @@ -156,16 +230,25 @@ public DeleteQuery(Builder builder) { 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; - if (builder.slices != null) { - this.slices = Slices.of(sb -> sb.value(builder.slices)); - } + 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; @@ -187,7 +270,7 @@ public String getAnalyzer() { } @Nullable - public Operator getDefaultOperator() { + public OperatorType getDefaultOperator() { return defaultOperator; } @@ -202,35 +285,85 @@ public Boolean getLenient() { } @Nullable - public Conflicts getConflicts() { + public Boolean getAllowNoIndices() { + return allowNoIndices; + } + + @Nullable + public ConflictsType getConflicts() { return conflicts; } + @Nullable + public EnumSet 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 Slices getSlices() { + public Integer getSlices() { return slices; } + @Nullable + public Sort getSort() { + return sort; + } + @Nullable public List getStats() { return stats; @@ -241,6 +374,11 @@ public Long getTerminateAfter() { return terminateAfter; } + @Nullable + public Duration getTimeout() { + return timeout; + } + @Nullable public Boolean getVersion() { return version; @@ -251,7 +389,7 @@ public Query getQuery() { return query; } - public static final class Builder extends BaseQueryBuilder { + public static final class Builder { // For Lucene query @Nullable private String luceneQuery; @@ -260,7 +398,7 @@ public static final class Builder extends BaseQueryBuilder @Nullable private String analyzer; @Nullable - private Operator defaultOperator; + private OperatorType defaultOperator; @Nullable private String defaultField; @Nullable @@ -268,22 +406,44 @@ public static final class Builder extends BaseQueryBuilder // For ES query @Nullable - private Conflicts conflicts; + private Boolean allowNoIndices; + @Nullable + private ConflictsType conflicts; + @Nullable + private EnumSet 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 stats; @Nullable private Long terminateAfter; @Nullable + private Duration timeout; + @Nullable private Boolean version; // Body @@ -326,7 +486,7 @@ public Builder withAnalyzer(@Nullable 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. */ - public Builder withDefaultOperator(@Nullable Operator defaultOperator) { + public Builder withDefaultOperator(@Nullable OperatorType defaultOperator) { this.defaultOperator = defaultOperator; return this; @@ -354,16 +514,37 @@ public Builder withLenient(@Nullable Boolean 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 Conflicts conflicts) { + 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 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}. @@ -374,6 +555,45 @@ public Builder withIgnoreUnavailable(@Nullable Boolean 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). @@ -384,6 +604,24 @@ public Builder withRequestsPerSecond(@Nullable Float 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}. @@ -394,6 +632,15 @@ public Builder withScrollSize(@Nullable Long 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. @@ -414,6 +661,15 @@ public Builder withSlices(@Nullable Integer 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. */ @@ -433,6 +689,16 @@ public Builder withTerminateAfter(@Nullable Long 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. */ @@ -442,7 +708,6 @@ public Builder withVersion(@Nullable Boolean version) { return this; } - @Override public DeleteQuery build() { if (luceneQuery == null) { if (defaultField != null) { diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/types/ConflictsType.java b/src/main/java/org/springframework/data/elasticsearch/core/query/types/ConflictsType.java new file mode 100644 index 0000000000..c9a4a6548e --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/core/query/types/ConflictsType.java @@ -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 +} diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/types/OperatorType.java b/src/main/java/org/springframework/data/elasticsearch/core/query/types/OperatorType.java new file mode 100644 index 0000000000..2e79249f8c --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/core/query/types/OperatorType.java @@ -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 +} From d761b163a9dbee3a5cccf295d9144f8c23195ac8 Mon Sep 17 00:00:00 2001 From: Youssef Aouichaoui Date: Sun, 24 Mar 2024 21:24:51 +0100 Subject: [PATCH 7/7] Polishing. Signed-off-by: Youssef Aouichaoui --- .../elasticsearch/client/elc/RequestConverter.java | 11 +++++++++-- .../data/elasticsearch/core/query/DeleteQuery.java | 5 ++++- .../elasticsearch/core/query/types/package-info.java | 3 +++ 3 files changed, 16 insertions(+), 3 deletions(-) create mode 100644 src/main/java/org/springframework/data/elasticsearch/core/query/types/package-info.java diff --git a/src/main/java/org/springframework/data/elasticsearch/client/elc/RequestConverter.java b/src/main/java/org/springframework/data/elasticsearch/client/elc/RequestConverter.java index dff15d1d89..0865b495ce 100644 --- a/src/main/java/org/springframework/data/elasticsearch/client/elc/RequestConverter.java +++ b/src/main/java/org/springframework/data/elasticsearch/client/elc/RequestConverter.java @@ -973,7 +973,6 @@ public DeleteByQueryRequest documentDeleteByQueryRequest(DeleteQuery query, @Nul Assert.notNull(query, "query must not be null"); Assert.notNull(index, "index must not be null"); - // Old documentDeleteByQueryRequest needs to be refactored. return DeleteByQueryRequest.of(dqb -> { dqb.index(Arrays.asList(index.getIndexNames())) // .query(getQuery(query.getQuery(), clazz))// @@ -1014,7 +1013,15 @@ public DeleteByQueryRequest documentDeleteByQueryRequest(DeleteQuery query, @Nul if (!sortOptions.isEmpty()) { dqb.sort( sortOptions.stream() - .map(sortOption -> sortOption.field().field() + ":" + sortOption.field().order().jsonValue()) + .map(sortOption -> { + String order = "asc"; + var sortField = sortOption.field(); + if (sortField.order() != null) { + order = sortField.order().jsonValue(); + } + + return sortField.field() + ":" + order; + }) .collect(Collectors.toList()) ); } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/DeleteQuery.java b/src/main/java/org/springframework/data/elasticsearch/core/query/DeleteQuery.java index de65a07c3e..be430d28b8 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/query/DeleteQuery.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/query/DeleteQuery.java @@ -20,6 +20,7 @@ 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; @@ -222,7 +223,7 @@ public static Builder builder(Query query) { return new Builder(query); } - public DeleteQuery(Builder builder) { + private DeleteQuery(Builder builder) { this.q = builder.luceneQuery; this.analyzeWildcard = builder.analyzeWildcard; this.analyzer = builder.analyzer; @@ -450,6 +451,8 @@ public static final class Builder { private final Query query; private Builder(Query query) { + Assert.notNull(query, "query must not be null"); + this.query = query; } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/types/package-info.java b/src/main/java/org/springframework/data/elasticsearch/core/query/types/package-info.java new file mode 100644 index 0000000000..a759969342 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/core/query/types/package-info.java @@ -0,0 +1,3 @@ +@org.springframework.lang.NonNullApi +@org.springframework.lang.NonNullFields +package org.springframework.data.elasticsearch.core.query.types;