1
0
mirror of synced 2026-05-23 20:53:17 +00:00

Compare commits

...

24 Commits

Author SHA1 Message Date
Mark Paluch 1126c65766 Release version 4.4.1 (2021.2.1).
See #2166
2022-06-20 11:29:30 +02:00
Mark Paluch db21ab06f9 Prepare 4.4.1 (2021.2.1).
See #2166
2022-06-20 11:29:07 +02:00
Mark Paluch 1af298d95a Upgrade to Maven Wrapper 3.8.5.
See #2178
2022-06-03 09:39:36 +02:00
Mark Paluch 980c6b350d Update CI properties.
See #2166
2022-06-03 09:34:36 +02:00
panzhenchao 7efd4b3be7 Fix incorrect argument check asserts.
Original Pull Request #2169
Closes #2170

(cherry picked from commit c826adb152)
2022-05-27 20:29:40 +02:00
Christoph Strobl d2cc58ccad After release cleanups.
See #2140
2022-05-13 10:15:13 +02:00
Christoph Strobl 109dc05d9b Prepare next development iteration.
See #2140
2022-05-13 10:15:11 +02:00
Christoph Strobl edde0214a0 Release version 4.4 GA (2021.2.0).
See #2140
2022-05-13 10:05:08 +02:00
Christoph Strobl c8699d93d0 Prepare 4.4 GA (2021.2.0).
See #2140
2022-05-13 10:04:21 +02:00
Peter-Josef Meisch c0b26a51f1 Add new Elasticsearch client as an alternative to the existing REST client.
Original Pull Request #2160
Closes #1973
2022-05-12 07:32:39 +02:00
Peter-Josef Meisch 3dbb1e73d6 Update to Elasticsearch 7.17.3.
Original Pull Request #2145
Closes #2144

(cherry picked from commit 0950dd6c7a)
2022-04-24 11:58:05 +02:00
Christoph Strobl e5efd31973 After release cleanups.
See #2120
2022-04-19 11:21:18 +02:00
Christoph Strobl 0637927ed4 Prepare next development iteration.
See #2120
2022-04-19 11:21:15 +02:00
Christoph Strobl 16dacbb63c Release version 4.4 RC1 (2021.2.0).
See #2120
2022-04-19 11:10:55 +02:00
Christoph Strobl 12acddb86d Prepare 4.4 RC1 (2021.2.0).
See #2120
2022-04-19 11:10:15 +02:00
Peter-Josef Meisch 8cef50347e Add more implementations using the new client.
Original Pull Request #2136
See #1973
2022-04-13 22:12:02 +02:00
Peter-Josef Meisch ea4d3f9f30 Upgrade to Elasticsearch 7.17.2.
Original Pull Request #2131
Closes #2130

(cherry picked from commit a60f3059e1)
2022-04-02 21:15:17 +02:00
Peter-Josef Meisch b9e2b13f21 Add info to readme about Elasticsearch versions.
Original Pull Request #2128
Closes #2127

(cherry picked from commit 3154c74f94)
2022-04-02 18:22:54 +02:00
Peter-Josef Meisch 5549216db0 Default Refresh policy for ReactiveElasticsearchTemplate.
Original Pull Request #2124
Closes #2110

(cherry picked from commit acd7990fac)
2022-03-24 21:11:25 +01:00
Mark Paluch a2cee9defd Update Jenkinsfile according to Java 8 build pipeline configuration.
See #2120
2022-03-24 09:30:20 +01:00
Greg L. Turnquist ea0ac3f7bc After release cleanups.
See #2092
2022-03-21 10:20:33 -05:00
Greg L. Turnquist 49cb56ed0c Prepare next development iteration.
See #2092
2022-03-21 10:20:31 -05:00
Greg L. Turnquist e8f9f9f1e3 Release version 4.4 M4 (2021.2.0).
See #2092
2022-03-21 10:09:34 -05:00
Greg L. Turnquist f91c9c443b Prepare 4.4 M4 (2021.2.0).
See #2092
2022-03-21 10:07:09 -05:00
69 changed files with 2186 additions and 696 deletions
+2 -2
View File
@@ -1,3 +1,3 @@
#Tue Feb 22 13:59:12 CET 2022
#Fri Jun 03 09:39:36 CEST 2022
wrapperUrl=https\://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar
distributionUrl=https\://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.4/apache-maven-3.8.4-bin.zip
distributionUrl=https\://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.5/apache-maven-3.8.5-bin.zip
Vendored
+7 -4
View File
@@ -9,7 +9,7 @@ pipeline {
triggers {
pollSCM 'H/10 * * * *'
upstream(upstreamProjects: "spring-data-commons/main", threshold: hudson.model.Result.SUCCESS)
upstream(upstreamProjects: "spring-data-commons/2.7.x", threshold: hudson.model.Result.SUCCESS)
}
options {
@@ -20,8 +20,9 @@ pipeline {
stages {
stage("test: baseline (main)") {
when {
beforeAgent(true)
anyOf {
branch 'main'
branch(pattern: "main|(\\d\\.\\d\\.x)", comparator: "REGEXP")
not { triggeredBy 'UpstreamCause' }
}
}
@@ -50,8 +51,9 @@ pipeline {
stage("Test other configurations") {
when {
beforeAgent(true)
allOf {
branch 'main'
branch(pattern: "main|(\\d\\.\\d\\.x)", comparator: "REGEXP")
not { triggeredBy 'UpstreamCause' }
}
}
@@ -108,8 +110,9 @@ pipeline {
stage('Release to artifactory') {
when {
beforeAgent(true)
anyOf {
branch 'main'
branch(pattern: "main|(\\d\\.\\d\\.x)", comparator: "REGEXP")
not { triggeredBy 'UpstreamCause' }
}
}
+24 -1
View File
@@ -1,3 +1,4 @@
image:https://spring.io/badges/spring-data-elasticsearch/ga.svg[Spring Data Elasticsearch,link=https://projects.spring.io/spring-data-elasticsearch#quick-start] image:https://spring.io/badges/spring-data-elasticsearch/snapshot.svg[Spring Data Elasticsearch,link=https://projects.spring.io/spring-data-elasticsearch#quick-start]
= Spring Data for Elasticsearch image:https://jenkins.spring.io/buildStatus/icon?job=spring-data-elasticsearch%2Fmain&subject=Build[link=https://jenkins.spring.io/view/SpringData/job/spring-data-elasticsearch/] https://gitter.im/spring-projects/spring-data[image:https://badges.gitter.im/spring-projects/spring-data.svg[Gitter]]
@@ -12,13 +13,35 @@ This project is lead and maintained by the community.
== Features
* Spring configuration support using Java based `@Configuration` classes or an XML namespace for a ES clients instances.
* `ElasticsearchRestTemplate` helper class that increases productivity performing common ES operations.
* `ElasticsearchOperations` class and implementations that increases productivity performing common ES operations.
Includes integrated object mapping between documents and POJOs.
* Feature Rich Object Mapping integrated with Springs Conversion Service
* Annotation based mapping metadata
* Automatic implementation of `Repository` interfaces including support for custom search methods.
* CDI support for repositories
== About Elasticsearch versions and clients
=== Elasticsearch 7.17 client libraries
At the end of 2021 Elasticsearch with version 7.17 released the new version of their Java client and deprecated the `RestHighLevelCLient` which was the default way to access Elasticsearch up to then.
Spring Data Elasticsearch will in version 4.4 offer the possibility to optionally use the new client as an alternative to the existing setup using the `RestHighLevelCLient`.
The default client that is used still is the `RestHighLevelCLient`, first because the integration of the new client is not yet complete, the new client still has features missing and bugs which will hopefully be resolved soon.
Second, and more important, the new Elasticsearch client forces users to switch from using `javax.json.spi.JsonProvider` to `jakarta.json.spi.JsonProvider`.
Spring Data Elasticsearch cannot enforce this switch; Spring Boot will switch to `jakarta` with version 3 and then it's safe for Spring Data Elasticsearch to switch to the new client.
So for version 4.4 Spring Data Elasticsearch will keep using the `RestHighLevelCLient` in version 7.17.x (as long as this will be available).
=== Elasticsearch 8 client libraries
In Elasticsearch 8, the `RestHighLevelCLient` has been removed.
This means that a switch to this client version can only be done with the next major upgrade which will be Spring Data Elasticsearch 5, based on Spring Data 3, used by Spring Boot 3, based on Spring 6 and Java 17.
=== Elasticsearch 8 cluster
It should be possible to use the Elasticsearch 7 client to access a cluster running version 8 by setting the appropriate aompatibility headers (see the documentation at https://docs.spring.io/spring-data/elasticsearch/docs/current/reference/html/#elasticsearch.clients.configuration). but I encountered and heard of cases where the response from the server is not parseable by the client although the headers are set, so use with care.
== Code of Conduct
This project is governed by the https://github.com/spring-projects/.github/blob/e3cc2ff230d8f1dca06535aa6b5a4a23815861d4/CODE_OF_CONDUCT.md[Spring Code of Conduct].
+3 -3
View File
@@ -1,7 +1,7 @@
# Java versions
java.main.tag=8u322-b06-jdk
java.next.tag=11.0.14.1_1-jdk
java.lts.tag=17.0.2_8-jdk
java.main.tag=8u332-b09-jdk
java.next.tag=11.0.15_10-jdk
java.lts.tag=17.0.3_7-jdk
# Docker container images - standard
docker.java.main.image=harbor-repo.vmware.com/dockerhub-proxy-cache/library/eclipse-temurin:${java.main.tag}
+12 -10
View File
@@ -5,12 +5,12 @@
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-elasticsearch</artifactId>
<version>4.4.0-SNAPSHOT</version>
<version>4.4.1</version>
<parent>
<groupId>org.springframework.data.build</groupId>
<artifactId>spring-data-parent</artifactId>
<version>2.7.0-SNAPSHOT</version>
<version>2.7.1</version>
</parent>
<name>Spring Data Elasticsearch</name>
@@ -18,11 +18,13 @@
<url>https://github.com/spring-projects/spring-data-elasticsearch</url>
<properties>
<elasticsearch>7.17.1</elasticsearch>
<elasticsearch-java>7.17.1</elasticsearch-java>
<!-- version of the RestHighLevelClient -->
<elasticsearch-rhlc>7.17.3</elasticsearch-rhlc>
<!-- version of the new ElasticsearchClient -->
<elasticsearch-java>7.17.3</elasticsearch-java>
<log4j>2.17.1</log4j>
<netty>4.1.65.Final</netty>
<springdata.commons>2.7.0-SNAPSHOT</springdata.commons>
<springdata.commons>2.7.1</springdata.commons>
<testcontainers>1.16.2</testcontainers>
<blockhound-junit>1.0.6.RELEASE</blockhound-junit>
<java-module-name>spring.data.elasticsearch</java-module-name>
@@ -139,7 +141,7 @@
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>${elasticsearch}</version>
<version>${elasticsearch-rhlc}</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
@@ -163,7 +165,7 @@
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-client</artifactId> <!-- is Apache 2-->
<version>${elasticsearch}</version>
<version>${elasticsearch-java}</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
@@ -497,15 +499,15 @@
<repositories>
<repository>
<id>spring-libs-snapshot</id>
<url>https://repo.spring.io/libs-snapshot</url>
<id>spring-libs-release</id>
<url>https://repo.spring.io/libs-release</url>
</repository>
<repository>
<id>local-maven-repo</id>
<url>file:///${project.basedir}/src/test/resources/local-maven-repo</url>
</repository>
</repositories>
<pluginRepositories>
+1 -1
View File
@@ -34,7 +34,7 @@ The following table shows the Elasticsearch versions that are used by Spring Dat
[cols="^,^,^,^,^",options="header"]
|===
| Spring Data Release Train | Spring Data Elasticsearch | Elasticsearch | Spring Framework | Spring Boot
| 2022.0 (Raj) | 4.4.x | 7.17.1 | 5.3.x | 2.7.x
| 2022.0 (Raj) | 4.4.x | 7.17.3 | 5.3.x | 2.7.x
| 2021.1 (Q) | 4.3.x | 7.15.2 | 5.3.x | 2.6.x
| 2021.0 (Pascal) | 4.2.x | 7.12.0 | 5.3.x | 2.5.x
| 2020.0 (Ockham)footnote:oom[Out of maintenance] | 4.1.xfootnote:oom[] | 7.9.3 | 5.3.2 | 2.4.x
@@ -28,6 +28,14 @@ Connections to Elasticsearch must be made using either the imperative `Elasticse
In 4.3 two classes (`ElasticsearchAggregations` and `ElasticsearchAggregation`) had been moved to the `org.springframework.data.elasticsearch.core.clients.elasticsearch7` package in preparation for the integration of the new Elasticsearch client.
The were moved back to the `org.springframework.data.elasticsearch.core` package as we keep the classes use the old Elasticsearch client where they were.
=== Behaviour change
The `ReactiveElasticsearchTemplate`, when created directly or by Spring Boot configuration had a default refresh policy of IMMEDIATE.
This could cause performance issues on heavy indexing and was different than the default behaviour of Elasticsearch.
This has been changed to that now the default refresh policy is NONE.
When the
`ReactiveElasticsearchTemplate` was provided by using the configuration like described in <<elasticsearch.clients.reactive>> the default refresh policy already was set to NONE.
[[elasticsearch-migration-guide-4.3-4.4.new-clients]]
== New Elasticsearch client
@@ -74,7 +82,7 @@ The dependencies for the new Elasticsearch client are still optional in Spring D
<dependency>
<groupId>co.elastic.clients</groupId>
<artifactId>elasticsearch-java</artifactId>
<version>7.17.1</version>
<version>7.17.3</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
@@ -85,7 +93,7 @@ The dependencies for the new Elasticsearch client are still optional in Spring D
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-client</artifactId> <!-- is Apache 2-->
<version>7.17.1</version>
<version>7.17.3</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
@@ -5,7 +5,7 @@
== New in Spring Data Elasticsearch 4.4
* Introduction of new imperative and reactive clients using the classes from the new Elasticsearch Java client
* Upgrade to Elasticsearch 7.17.1.
* Upgrade to Elasticsearch 7.17.3.
[[new-features.4-3-0]]
== New in Spring Data Elasticsearch 4.3
@@ -25,6 +25,14 @@ public class NoSuchIndexException extends NonTransientDataAccessResourceExceptio
private final String index;
/**
* @since 4.4
*/
public NoSuchIndexException(String index) {
super(String.format("Index %s not found.", index));
this.index = index;
}
public NoSuchIndexException(String index, Throwable cause) {
super(String.format("Index %s not found.", index), cause);
this.index = index;
@@ -0,0 +1,44 @@
/*
* Copyright 2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.client.elc;
import co.elastic.clients.elasticsearch._types.aggregations.Aggregate;
/**
* Class to combine an Elasticsearch {@link co.elastic.clients.elasticsearch._types.aggregations.Aggregate} with its
* name. Necessary as the Elasticsearch Aggregate does not know i"s name.
*
* @author Peter-Josef Meisch
* @since 4.4
*/
public class Aggregation {
private final String name;
private final Aggregate aggregate;
public Aggregation(String name, Aggregate aggregate) {
this.name = name;
this.aggregate = aggregate;
}
public String getName() {
return name;
}
public Aggregate getAggregate() {
return aggregate;
}
}
@@ -31,6 +31,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.data.elasticsearch.core.convert.GeoConverters;
@@ -67,9 +68,13 @@ class CriteriaFilterProcessor {
for (Criteria chainedCriteria : criteria.getCriteriaChain()) {
if (chainedCriteria.isOr()) {
// todo #1973
BoolQuery.Builder boolQueryBuilder = QueryBuilders.bool();
queriesForEntries(chainedCriteria).forEach(boolQueryBuilder::should);
filterQueries.add(new Query(boolQueryBuilder.build()));
} else if (chainedCriteria.isNegating()) {
// todo #1973
Collection<? extends Query> negatingFilters = buildNegatingFilter(criteria.getField().getName(),
criteria.getFilterCriteriaEntries());
filterQueries.addAll(negatingFilters);
} else {
filterQueries.addAll(queriesForEntries(chainedCriteria));
}
@@ -85,11 +90,28 @@ class CriteriaFilterProcessor {
BoolQuery.Builder boolQueryBuilder = QueryBuilders.bool();
filterQueries.forEach(boolQueryBuilder::must);
BoolQuery boolQuery = boolQueryBuilder.build();
return Optional.of(boolQuery._toQuery());
return Optional.of(new Query(boolQuery));
}
}
}
private static Collection<? extends Query> buildNegatingFilter(String fieldName,
Set<Criteria.CriteriaEntry> filterCriteriaEntries) {
List<Query> negationFilters = new ArrayList<>();
filterCriteriaEntries.forEach(criteriaEntry -> {
Optional<Query> query = queryFor(criteriaEntry.getKey(), criteriaEntry.getValue(), fieldName);
if (query.isPresent()) {
BoolQuery negatingFilter = QueryBuilders.bool().mustNot(query.get()).build();
negationFilters.add(new Query(negatingFilter));
}
});
return negationFilters;
}
private static Collection<? extends Query> queriesForEntries(Criteria criteria) {
Assert.notNull(criteria.getField(), "criteria must have a field");
@@ -28,6 +28,7 @@ import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.apache.commons.logging.Log;
@@ -77,18 +78,11 @@ final class DocumentAdapters {
NestedMetaData nestedMetaData = from(hit.nested());
// todo #1973 explanation
Explanation explanation = from(hit.explanation());
// todo #1973 matchedQueries
List<String> matchedQueries = null;
// todo #1973 documentFields
Map<String, List<Object>> documentFields = Collections.emptyMap();
List<String> matchedQueries = hit.matchedQueries();
Document document;
Object source = hit.source();
if (source == null) {
// Elasticsearch provides raw JsonData, so we build the fields into a JSON string
Function<Map<String, JsonData>, EntityAsMap> fromFields = fields -> {
StringBuilder sb = new StringBuilder("{");
final boolean[] firstField = { true };
hit.fields().forEach((key, jsonData) -> {
@@ -100,7 +94,25 @@ final class DocumentAdapters {
firstField[0] = false;
});
sb.append('}');
document = Document.parse(sb.toString());
return new EntityAsMap().fromJson(sb.toString());
};
EntityAsMap hitFieldsAsMap = fromFields.apply(hit.fields());
Map<String, List<Object>> documentFields = new LinkedHashMap<>();
hitFieldsAsMap.entrySet().forEach(entry -> {
if (entry.getValue() instanceof List) {
// noinspection unchecked
documentFields.put(entry.getKey(), (List<Object>) entry.getValue());
} else {
documentFields.put(entry.getKey(), Collections.singletonList(entry.getValue()));
}
});
Document document;
Object source = hit.source();
if (source == null) {
document = Document.from(hitFieldsAsMap);
} else {
if (source instanceof EntityAsMap) {
document = Document.from((EntityAsMap) source);
@@ -0,0 +1,37 @@
/*
* Copyright 2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.client.elc;
import org.springframework.data.elasticsearch.core.AggregationContainer;
/**
* {@link AggregationContainer} for a {@link Aggregation} that holds Elasticsearch data.
* @author Peter-Josef Meisch
* @since 4.4
*/
public class ElasticsearchAggregation implements AggregationContainer<Aggregation> {
private final Aggregation aggregation;
public ElasticsearchAggregation(Aggregation aggregation) {
this.aggregation = aggregation;
}
@Override
public Aggregation aggregation() {
return aggregation;
}
}
@@ -17,9 +17,12 @@ package org.springframework.data.elasticsearch.client.elc;
import co.elastic.clients.elasticsearch._types.aggregations.Aggregate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.springframework.data.elasticsearch.core.AggregationsContainer;
import org.springframework.util.Assert;
/**
* AggregationsContainer implementation for the Elasticsearch aggregations.
@@ -27,16 +30,33 @@ import org.springframework.data.elasticsearch.core.AggregationsContainer;
* @author Peter-Josef Meisch
* @since 4.4
*/
public class ElasticsearchAggregations implements AggregationsContainer<Map<String, Aggregate>> {
public class ElasticsearchAggregations implements AggregationsContainer<List<ElasticsearchAggregation>> {
private final Map<String, Aggregate> aggregations;
private final List<ElasticsearchAggregation> aggregations;
public ElasticsearchAggregations(List<ElasticsearchAggregation> aggregations) {
Assert.notNull(aggregations, "aggregations must not be null");
public ElasticsearchAggregations(Map<String, Aggregate> aggregations) {
this.aggregations = aggregations;
}
/**
* convenience constructor taking a map as it is returned from the new Elasticsearch client.
*
* @param aggregationsMap aggregate map
*/
public ElasticsearchAggregations(Map<String, Aggregate> aggregationsMap) {
Assert.notNull(aggregationsMap, "aggregationsMap must not be null");
aggregations = new ArrayList<>(aggregationsMap.size());
aggregationsMap
.forEach((name, aggregate) -> aggregations.add(new ElasticsearchAggregation(new Aggregation(name, aggregate))));
}
@Override
public Map<String, Aggregate> aggregations() {
public List<ElasticsearchAggregation> aggregations() {
return aggregations;
}
}
@@ -20,13 +20,18 @@ import co.elastic.clients.elasticsearch._types.ErrorResponse;
import co.elastic.clients.json.JsonpMapper;
import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.elasticsearch.client.ResponseException;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.data.elasticsearch.RestStatusException;
import org.springframework.data.elasticsearch.NoSuchIndexException;
import org.springframework.data.elasticsearch.UncategorizedElasticsearchException;
import org.springframework.http.HttpStatus;
/**
* Simple {@link PersistenceExceptionTranslator} for Elasticsearch. Convert the given runtime exception to an
@@ -67,14 +72,28 @@ public class ElasticsearchExceptionTranslator implements PersistenceExceptionTra
return new OptimisticLockingFailureException("Cannot index a document due to seq_no+primary_term conflict", ex);
}
// todo #1973 index unavailable?
if (ex instanceof ElasticsearchException) {
ElasticsearchException elasticsearchException = (ElasticsearchException) ex;
ErrorResponse response = elasticsearchException.response();
if (response.status() == HttpStatus.NOT_FOUND.value()
&& "index_not_found_exception".equals(response.error().type())) {
Pattern pattern = Pattern.compile(".*no such index \\[(.*)\\]");
String index = "";
Matcher matcher = pattern.matcher(response.error().reason());
if (matcher.matches()) {
index = matcher.group(1);
}
return new NoSuchIndexException(index);
}
String body = JsonUtils.toJson(response, jsonpMapper);
if (response.error().type().contains("validation_exception")) {
return new DataIntegrityViolationException(response.error().reason());
}
return new UncategorizedElasticsearchException(ex.getMessage(), response.status(), body, ex);
}
@@ -86,20 +105,21 @@ public class ElasticsearchExceptionTranslator implements PersistenceExceptionTra
return null;
}
private boolean isSeqNoConflict(Exception exception) {
// todo #1973 check if this works
private boolean isSeqNoConflict(Throwable exception) {
Integer status = null;
String message = null;
if (exception instanceof RestStatusException) {
RestStatusException statusException = (RestStatusException) exception;
status = statusException.getStatus();
message = statusException.getMessage();
if (exception instanceof ResponseException) {
ResponseException responseException = (ResponseException) exception;
status = responseException.getResponse().getStatusLine().getStatusCode();
message = responseException.getMessage();
} else if (exception.getCause() != null) {
return isSeqNoConflict(exception.getCause());
}
if (status != null && message != null) {
return status == 409 && message.contains("type=version_conflict_engine_exception")
return status == 409 && message.contains("type\":\"version_conflict_engine_exception")
&& message.contains("version conflict, required seqNo");
}
@@ -15,10 +15,14 @@
*/
package org.springframework.data.elasticsearch.client.elc;
import static org.springframework.data.elasticsearch.client.elc.TypeUtils.*;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.Time;
import co.elastic.clients.elasticsearch.core.*;
import co.elastic.clients.elasticsearch.core.bulk.BulkResponseItem;
import co.elastic.clients.elasticsearch.core.msearch.MultiSearchResponseItem;
import co.elastic.clients.elasticsearch.core.search.ResponseBody;
import co.elastic.clients.json.JsonpMapper;
import co.elastic.clients.transport.Version;
@@ -30,6 +34,8 @@ import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.data.elasticsearch.BulkFailureException;
import org.springframework.data.elasticsearch.client.UnsupportedBackendOperation;
import org.springframework.data.elasticsearch.core.AbstractElasticsearchTemplate;
@@ -40,6 +46,7 @@ import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.SearchScrollHits;
import org.springframework.data.elasticsearch.core.cluster.ClusterOperations;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
import org.springframework.data.elasticsearch.core.document.Document;
import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.BulkOptions;
@@ -63,6 +70,8 @@ import org.springframework.util.Assert;
*/
public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
private static final Log LOGGER = LogFactory.getLog(ElasticsearchTemplate.class);
private final ElasticsearchClient client;
private final RequestConverter requestConverter;
private final ResponseConverter responseConverter;
@@ -137,7 +146,12 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
@Override
public void bulkUpdate(List<UpdateQuery> queries, BulkOptions bulkOptions, IndexCoordinates index) {
throw new UnsupportedOperationException("not implemented");
Assert.notNull(queries, "queries must not be null");
Assert.notNull(bulkOptions, "bulkOptions must not be null");
Assert.notNull(index, "index must not be null");
doBulkOperation(queries, bulkOptions, index);
}
@Override
@@ -155,12 +169,25 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
@Override
public UpdateResponse update(UpdateQuery updateQuery, IndexCoordinates index) {
throw new UnsupportedOperationException("not implemented");
UpdateRequest<Document, ?> request = requestConverter.documentUpdateRequest(updateQuery, index, getRefreshPolicy(),
routingResolver.getRouting());
co.elastic.clients.elasticsearch.core.UpdateResponse<Document> response = execute(
client -> client.update(request, Document.class));
return UpdateResponse.of(result(response.result()));
}
@Override
public ByQueryResponse updateByQuery(UpdateQuery updateQuery, IndexCoordinates index) {
throw new UnsupportedOperationException("not implemented");
Assert.notNull(updateQuery, "updateQuery must not be null");
Assert.notNull(index, "index must not be null");
UpdateByQueryRequest request = requestConverter.documentUpdateByQueryRequest(updateQuery, index,
getRefreshPolicy());
UpdateByQueryResponse byQueryResponse = execute(client -> client.updateByQuery(request));
return responseConverter.byQueryResponse(byQueryResponse);
}
@Override
@@ -226,7 +253,6 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
client -> client.reindex(reindexRequestES));
if (reindexResponse.task() == null) {
// todo #1973 check behaviour and create issue in ES if necessary
throw new UnsupportedBackendOperation("ElasticsearchClient did not return a task id on submit request");
}
@@ -331,8 +357,8 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
return getSearchScrollHits(clazz, index, response);
}
private <T, R extends SearchResponse<EntityAsMap>> SearchScrollHits<T> getSearchScrollHits(Class<T> clazz,
IndexCoordinates index, R response) {
private <T> SearchScrollHits<T> getSearchScrollHits(Class<T> clazz, IndexCoordinates index,
ResponseBody<EntityAsMap> response) {
ReadDocumentCallback<T> documentCallback = new ReadDocumentCallback<>(elasticsearchConverter, clazz, index);
SearchDocumentResponseCallback<SearchScrollHits<T>> callback = new ReadSearchScrollDocumentResponseCallback<>(clazz,
index);
@@ -404,14 +430,53 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
return doMultiSearch(multiSearchQueryParameters);
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private List<SearchHits<?>> doMultiSearch(List<MultiSearchQueryParameter> multiSearchQueryParameters) {
throw new UnsupportedOperationException("not implemented");
MsearchRequest request = requestConverter.searchMsearchRequest(multiSearchQueryParameters);
MsearchResponse<EntityAsMap> msearchResponse = execute(client -> client.msearch(request, EntityAsMap.class));
List<MultiSearchResponseItem<EntityAsMap>> responseItems = msearchResponse.responses();
Assert.isTrue(multiSearchQueryParameters.size() == responseItems.size(),
"number of response items does not match number of requests");
List<SearchHits<?>> searchHitsList = new ArrayList<>(multiSearchQueryParameters.size());
Iterator<MultiSearchQueryParameter> queryIterator = multiSearchQueryParameters.iterator();
Iterator<MultiSearchResponseItem<EntityAsMap>> responseIterator = responseItems.iterator();
while (queryIterator.hasNext()) {
MultiSearchQueryParameter queryParameter = queryIterator.next();
MultiSearchResponseItem<EntityAsMap> responseItem = responseIterator.next();
if (responseItem.isResult()) {
Class clazz = queryParameter.clazz;
ReadDocumentCallback<?> documentCallback = new ReadDocumentCallback<>(elasticsearchConverter, clazz,
queryParameter.index);
SearchDocumentResponseCallback<SearchHits<?>> callback = new ReadSearchDocumentResponseCallback<>(clazz,
queryParameter.index);
SearchHits<?> searchHits = callback.doWith(
SearchDocumentResponseBuilder.from(responseItem.result(), getEntityCreator(documentCallback), jsonpMapper));
searchHitsList.add(searchHits);
} else {
if (LOGGER.isWarnEnabled()) {
LOGGER
.warn(String.format("multisearch responsecontains failure: {}", responseItem.failure().error().reason()));
}
}
}
return searchHitsList;
}
/**
* value class combining the information needed for a single query in a multisearch request.
*/
private static class MultiSearchQueryParameter {
static class MultiSearchQueryParameter {
final Query query;
final Class<?> clazz;
final IndexCoordinates index;
@@ -87,10 +87,6 @@ class HighlightQueryBuilder {
builder.boundaryScannerLocale(parameters.getBoundaryScannerLocale());
}
if (parameters.getForceSource()) { // default is false
// todo #1973 parameter missing in new client
}
if (StringUtils.hasLength(parameters.getFragmenter())) {
builder.fragmenter(highlighterFragmenter(parameters.getFragmenter()));
}
@@ -111,10 +107,6 @@ class HighlightQueryBuilder {
builder.order(highlighterOrder(parameters.getOrder()));
}
if (parameters.getPhraseLimit() > -1) {
// todo #1973 parameter missing in new client
}
if (parameters.getPreTags().length > 0) {
builder.preTags(Arrays.asList(parameters.getPreTags()));
}
@@ -17,14 +17,18 @@ package org.springframework.data.elasticsearch.client.elc;
import co.elastic.clients.elasticsearch._types.aggregations.Aggregation;
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
import co.elastic.clients.elasticsearch.core.search.FieldCollapse;
import co.elastic.clients.elasticsearch.core.search.Suggester;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.springframework.data.elasticsearch.core.query.BaseQuery;
import org.springframework.data.elasticsearch.core.query.RescorerQuery;
import org.springframework.data.elasticsearch.core.query.ScriptedField;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* A {@link org.springframework.data.elasticsearch.core.query.Query} implementation using query builders from the new
@@ -36,13 +40,23 @@ import org.springframework.util.Assert;
public class NativeQuery extends BaseQuery {
@Nullable private final Query query;
@Nullable private Query filter;
// note: the new client does not have pipeline aggs, these are just set up as normal aggs
private final Map<String, Aggregation> aggregations = new LinkedHashMap<>();
@Nullable private Suggester suggester;
@Nullable private FieldCollapse fieldCollapse;
private List<ScriptedField> scriptedFields = Collections.emptyList();
private List<RescorerQuery> rescorerQueries = Collections.emptyList();
public NativeQuery(NativeQueryBuilder builder) {
super(builder);
this.query = builder.getQuery();
this.filter = builder.getFilter();
this.aggregations.putAll(builder.getAggregations());
this.suggester = builder.getSuggester();
this.fieldCollapse = builder.getFieldCollapse();
this.scriptedFields = builder.getScriptedFields();
this.rescorerQueries = builder.getRescorerQueries();
}
public NativeQuery(@Nullable Query query) {
@@ -58,20 +72,9 @@ public class NativeQuery extends BaseQuery {
return query;
}
public void addAggregation(String name, Aggregation aggregation) {
Assert.notNull(name, "name must not be null");
Assert.notNull(aggregation, "aggregation must not be null");
aggregations.put(name, aggregation);
}
public void setAggregations(Map<String, Aggregation> aggregations) {
Assert.notNull(aggregations, "aggregations must not be null");
this.aggregations.clear();
this.aggregations.putAll(aggregations);
@Nullable
public Query getFilter() {
return filter;
}
public Map<String, Aggregation> getAggregations() {
@@ -83,8 +86,17 @@ public class NativeQuery extends BaseQuery {
return suggester;
}
public void setSuggester(@Nullable Suggester suggester) {
this.suggester = suggester;
@Nullable
public FieldCollapse getFieldCollapse() {
return fieldCollapse;
}
public List<ScriptedField> getScriptedFields() {
return scriptedFields;
}
@Override
public List<RescorerQuery> getRescorerQueries() {
return rescorerQueries;
}
}
@@ -17,14 +17,19 @@ package org.springframework.data.elasticsearch.client.elc;
import co.elastic.clients.elasticsearch._types.aggregations.Aggregation;
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
import co.elastic.clients.elasticsearch.core.search.FieldCollapse;
import co.elastic.clients.elasticsearch.core.search.Suggester;
import co.elastic.clients.util.ObjectBuilder;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import org.springframework.data.elasticsearch.core.query.BaseQueryBuilder;
import org.springframework.data.elasticsearch.core.query.RescorerQuery;
import org.springframework.data.elasticsearch.core.query.ScriptedField;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -35,17 +40,47 @@ import org.springframework.util.Assert;
public class NativeQueryBuilder extends BaseQueryBuilder<NativeQuery, NativeQueryBuilder> {
@Nullable private Query query;
@Nullable private Query filter;
private final Map<String, Aggregation> aggregations = new LinkedHashMap<>();
@Nullable private Suggester suggester;
@Nullable private FieldCollapse fieldCollapse;
private final List<ScriptedField> scriptedFields = new ArrayList<>();
private List<RescorerQuery> rescorerQueries = new ArrayList<>();
public NativeQueryBuilder() {
}
public NativeQueryBuilder() {}
@Nullable
public Query getQuery() {
return query;
}
@Nullable
public Query getFilter() {
return this.filter;
}
public Map<String, Aggregation> getAggregations() {
return aggregations;
}
@Nullable
public Suggester getSuggester() {
return suggester;
}
@Nullable
public FieldCollapse getFieldCollapse() {
return fieldCollapse;
}
public List<ScriptedField> getScriptedFields() {
return scriptedFields;
}
public List<RescorerQuery> getRescorerQueries() {
return rescorerQueries;
}
public NativeQueryBuilder withQuery(Query query) {
Assert.notNull(query, "query must not be null");
@@ -54,6 +89,11 @@ public class NativeQueryBuilder extends BaseQueryBuilder<NativeQuery, NativeQuer
return this;
}
public NativeQueryBuilder withFilter(@Nullable Query filter) {
this.filter = filter;
return this;
}
public NativeQueryBuilder withQuery(Function<Query.Builder, ObjectBuilder<Query>> fn) {
Assert.notNull(fn, "fn must not be null");
@@ -75,11 +115,28 @@ public class NativeQueryBuilder extends BaseQueryBuilder<NativeQuery, NativeQuer
return this;
}
public NativeQuery build() {
NativeQuery nativeQuery = new NativeQuery(this);
nativeQuery.setAggregations(aggregations);
nativeQuery.setSuggester(suggester);
public NativeQueryBuilder withFieldCollapse(@Nullable FieldCollapse fieldCollapse) {
this.fieldCollapse = fieldCollapse;
return this;
}
return nativeQuery;
public NativeQueryBuilder withScriptedField(ScriptedField scriptedField) {
Assert.notNull(scriptedField, "scriptedField must not be null");
this.scriptedFields.add(scriptedField);
return this;
}
public NativeQueryBuilder withResorerQuery(RescorerQuery resorerQuery) {
Assert.notNull(resorerQuery, "resorerQuery must not be null");
this.rescorerQueries.add(resorerQuery);
return this;
}
public NativeQuery build() {
return new NativeQuery(this);
}
}
@@ -17,6 +17,7 @@ package org.springframework.data.elasticsearch.client.elc;
import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch._types.LatLonGeoLocation;
import co.elastic.clients.elasticsearch._types.query_dsl.IdsQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.MatchAllQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.MatchQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.Operator;
@@ -29,6 +30,7 @@ import co.elastic.clients.util.ObjectBuilder;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.List;
import java.util.function.Function;
import org.springframework.data.elasticsearch.core.geo.GeoPoint;
@@ -45,6 +47,21 @@ public final class QueryBuilders {
private QueryBuilders() {}
public static IdsQuery idsQuery(List<String> ids) {
Assert.notNull(ids, "ids must not be null");
return IdsQuery.of(i -> i.values(ids));
}
public static Query idsQueryAsQuery(List<String> ids) {
Assert.notNull(ids, "ids must not be null");
Function<Query.Builder, ObjectBuilder<Query>> builder = b -> b.ids(idsQuery(ids));
return builder.apply(new Query.Builder()).build();
}
public static MatchQuery matchQuery(String fieldName, String query, @Nullable Operator operator,
@Nullable Float boost) {
@@ -18,6 +18,7 @@ package org.springframework.data.elasticsearch.client.elc;
import co.elastic.clients.ApiClient;
import co.elastic.clients.elasticsearch._types.ErrorResponse;
import co.elastic.clients.elasticsearch.core.*;
import co.elastic.clients.elasticsearch.core.search.ResponseBody;
import co.elastic.clients.transport.ElasticsearchTransport;
import co.elastic.clients.transport.JsonEndpoint;
import co.elastic.clients.transport.TransportOptions;
@@ -127,12 +128,51 @@ public class ReactiveElasticsearchClient extends ApiClient<ElasticsearchTranspor
return Mono.fromFuture(transport.performRequestAsync(request, endpoint, transportOptions));
}
public <T, P> Mono<UpdateResponse<T>> update(UpdateRequest<T, P> request, Class<T> clazz) {
Assert.notNull(request, "request must not be null");
// noinspection unchecked
JsonEndpoint<UpdateRequest<?, ?>, UpdateResponse<T>, ErrorResponse> endpoint = new EndpointWithResponseMapperAttr(
UpdateRequest._ENDPOINT, "co.elastic.clients:Deserializer:_global.update.TDocument",
this.getDeserializer(clazz));
return Mono.fromFuture(transport.performRequestAsync(request, endpoint, this.transportOptions));
}
public <T, P> Mono<UpdateResponse<T>> update(
Function<UpdateRequest.Builder<T, P>, ObjectBuilder<UpdateRequest<T, P>>> fn, Class<T> clazz) {
Assert.notNull(fn, "fn must not be null");
return update(fn.apply(new UpdateRequest.Builder<>()).build(), clazz);
}
public <T> Mono<GetResponse<T>> get(Function<GetRequest.Builder, ObjectBuilder<GetRequest>> fn, Class<T> tClass) {
Assert.notNull(fn, "fn must not be null");
return get(fn.apply(new GetRequest.Builder()).build(), tClass);
}
public <T> Mono<MgetResponse<T>> mget(MgetRequest request, Class<T> clazz) {
Assert.notNull(request, "request must not be null");
Assert.notNull(clazz, "clazz must not be null");
// noinspection unchecked
JsonEndpoint<MgetRequest, MgetResponse<T>, ErrorResponse> endpoint = (JsonEndpoint<MgetRequest, MgetResponse<T>, ErrorResponse>) MgetRequest._ENDPOINT;
endpoint = new EndpointWithResponseMapperAttr<>(endpoint, "co.elastic.clients:Deserializer:_global.mget.TDocument",
this.getDeserializer(clazz));
return Mono.fromFuture(transport.performRequestAsync(request, endpoint, transportOptions));
}
public <T> Mono<MgetResponse<T>> mget(Function<MgetRequest.Builder, ObjectBuilder<MgetRequest>> fn, Class<T> clazz) {
Assert.notNull(fn, "fn must not be null");
return mget(fn.apply(new MgetRequest.Builder()).build(), clazz);
}
public Mono<ReindexResponse> reindex(ReindexRequest request) {
Assert.notNull(request, "request must not be null");
@@ -161,10 +201,25 @@ public class ReactiveElasticsearchClient extends ApiClient<ElasticsearchTranspor
return delete(fn.apply(new DeleteRequest.Builder()).build());
}
public Mono<DeleteByQueryResponse> deleteByQuery(DeleteByQueryRequest request) {
Assert.notNull(request, "request must not be null");
return Mono.fromFuture(transport.performRequestAsync(request, DeleteByQueryRequest._ENDPOINT, transportOptions));
}
public Mono<DeleteByQueryResponse> deleteByQuery(
Function<DeleteByQueryRequest.Builder, ObjectBuilder<DeleteByQueryRequest>> fn) {
Assert.notNull(fn, "fn must not be null");
return deleteByQuery(fn.apply(new DeleteByQueryRequest.Builder()).build());
}
// endregion
// region search
public <T> Mono<SearchResponse<T>> search(SearchRequest request, Class<T> tDocumentClass) {
public <T> Mono<ResponseBody<T>> search(SearchRequest request, Class<T> tDocumentClass) {
Assert.notNull(request, "request must not be null");
Assert.notNull(tDocumentClass, "tDocumentClass must not be null");
@@ -173,7 +228,7 @@ public class ReactiveElasticsearchClient extends ApiClient<ElasticsearchTranspor
SearchRequest.createSearchEndpoint(this.getDeserializer(tDocumentClass)), transportOptions));
}
public <T> Mono<SearchResponse<T>> search(Function<SearchRequest.Builder, ObjectBuilder<SearchRequest>> fn,
public <T> Mono<ResponseBody<T>> search(Function<SearchRequest.Builder, ObjectBuilder<SearchRequest>> fn,
Class<T> tDocumentClass) {
Assert.notNull(fn, "fn must not be null");
@@ -212,14 +212,6 @@ public class ReactiveElasticsearchIndicesClient
return existsTemplate(fn.apply(new ExistsTemplateRequest.Builder()).build());
}
public Mono<BooleanResponse> existsType(ExistsTypeRequest request) {
return Mono.fromFuture(transport.performRequestAsync(request, ExistsTypeRequest._ENDPOINT, transportOptions));
}
public Mono<BooleanResponse> existsType(Function<ExistsTypeRequest.Builder, ObjectBuilder<ExistsTypeRequest>> fn) {
return existsType(fn.apply(new ExistsTypeRequest.Builder()).build());
}
public Mono<FlushResponse> flush(FlushRequest request) {
return Mono.fromFuture(transport.performRequestAsync(request, FlushRequest._ENDPOINT, transportOptions));
}
@@ -232,19 +224,6 @@ public class ReactiveElasticsearchIndicesClient
return flush(builder -> builder);
}
public Mono<FlushSyncedResponse> flushSynced(FlushSyncedRequest request) {
return Mono.fromFuture(transport.performRequestAsync(request, FlushSyncedRequest._ENDPOINT, transportOptions));
}
public Mono<FlushSyncedResponse> flushSynced(
Function<FlushSyncedRequest.Builder, ObjectBuilder<FlushSyncedRequest>> fn) {
return flushSynced(fn.apply(new FlushSyncedRequest.Builder()).build());
}
public Mono<FlushSyncedResponse> flushSynced() {
return flushSynced(builder -> builder);
}
@SuppressWarnings("SpellCheckingInspection")
public Mono<ForcemergeResponse> forcemerge(ForcemergeRequest request) {
return Mono.fromFuture(transport.performRequestAsync(request, ForcemergeRequest._ENDPOINT, transportOptions));
@@ -260,14 +239,6 @@ public class ReactiveElasticsearchIndicesClient
return forcemerge(builder -> builder);
}
public Mono<FreezeResponse> freeze(FreezeRequest request) {
return Mono.fromFuture(transport.performRequestAsync(request, FreezeRequest._ENDPOINT, transportOptions));
}
public Mono<FreezeResponse> freeze(Function<FreezeRequest.Builder, ObjectBuilder<FreezeRequest>> fn) {
return freeze(fn.apply(new FreezeRequest.Builder()).build());
}
public Mono<GetIndexResponse> get(GetIndexRequest request) {
return Mono.fromFuture(transport.performRequestAsync(request, GetIndexRequest._ENDPOINT, transportOptions));
}
@@ -363,18 +334,6 @@ public class ReactiveElasticsearchIndicesClient
return getTemplate(builder -> builder);
}
public Mono<GetUpgradeResponse> getUpgrade(GetUpgradeRequest request) {
return Mono.fromFuture(transport.performRequestAsync(request, GetUpgradeRequest._ENDPOINT, transportOptions));
}
public Mono<GetUpgradeResponse> getUpgrade(Function<GetUpgradeRequest.Builder, ObjectBuilder<GetUpgradeRequest>> fn) {
return getUpgrade(fn.apply(new GetUpgradeRequest.Builder()).build());
}
public Mono<GetUpgradeResponse> getUpgrade() {
return getUpgrade(builder -> builder);
}
public Mono<MigrateToDataStreamResponse> migrateToDataStream(MigrateToDataStreamRequest request) {
return Mono
.fromFuture(transport.performRequestAsync(request, MigrateToDataStreamRequest._ENDPOINT, transportOptions));
@@ -601,18 +560,6 @@ public class ReactiveElasticsearchIndicesClient
return updateAliases(builder -> builder);
}
public Mono<UpgradeResponse> upgrade(UpgradeRequest request) {
return Mono.fromFuture(transport.performRequestAsync(request, UpgradeRequest._ENDPOINT, transportOptions));
}
public Mono<UpgradeResponse> upgrade(Function<UpgradeRequest.Builder, ObjectBuilder<UpgradeRequest>> fn) {
return upgrade(fn.apply(new UpgradeRequest.Builder()).build());
}
public Mono<UpgradeResponse> upgrade() {
return upgrade(builder -> builder);
}
public Mono<ValidateQueryResponse> validateQuery(ValidateQueryRequest request) {
return Mono.fromFuture(transport.performRequestAsync(request, ValidateQueryRequest._ENDPOINT, transportOptions));
}
@@ -15,10 +15,14 @@
*/
package org.springframework.data.elasticsearch.client.elc;
import static org.springframework.data.elasticsearch.client.elc.TypeUtils.*;
import co.elastic.clients.elasticsearch._types.Result;
import co.elastic.clients.elasticsearch._types.Time;
import co.elastic.clients.elasticsearch.core.*;
import co.elastic.clients.elasticsearch.core.bulk.BulkResponseItem;
import co.elastic.clients.elasticsearch.core.get.GetResult;
import co.elastic.clients.elasticsearch.core.search.ResponseBody;
import co.elastic.clients.json.JsonpMapper;
import co.elastic.clients.transport.Version;
import reactor.core.publisher.Flux;
@@ -46,6 +50,7 @@ import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperatio
import org.springframework.data.elasticsearch.core.ReactiveIndexOperations;
import org.springframework.data.elasticsearch.core.cluster.ReactiveClusterOperations;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
import org.springframework.data.elasticsearch.core.document.Document;
import org.springframework.data.elasticsearch.core.document.SearchDocument;
import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
@@ -103,6 +108,34 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
)));
}
@Override
public <T> Flux<T> saveAll(Mono<? extends Collection<? extends T>> entitiesPublisher, IndexCoordinates index) {
Assert.notNull(entitiesPublisher, "entitiesPublisher must not be null!");
return entitiesPublisher //
.flatMapMany(entities -> Flux.fromIterable(entities) //
.concatMap(entity -> maybeCallBeforeConvert(entity, index)) //
).collectList() //
.map(Entities::new) //
.flatMapMany(entities -> {
if (entities.isEmpty()) {
return Flux.empty();
}
return doBulkOperation(entities.indexQueries(), BulkOptions.defaultOptions(), index)//
.index() //
.flatMap(indexAndResponse -> {
T savedEntity = entities.entityAt(indexAndResponse.getT1());
BulkResponseItem response = indexAndResponse.getT2();
updateIndexedObject(savedEntity, IndexedObjectInformation.of(response.id(), response.seqNo(),
response.primaryTerm(), response.version()));
return maybeCallAfterSave(savedEntity, index);
});
});
}
@Override
public <T> Mono<T> get(String id, Class<T> entityType, IndexCoordinates index) {
@@ -144,9 +177,8 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
return Mono.from(execute( //
(ClientCallback<Publisher<co.elastic.clients.elasticsearch.core.ReindexResponse>>) client -> client
.reindex(reindexRequestES)))
.flatMap(response -> (response.task() == null)
? Mono.error( // todo #1973 check behaviour and create issue in ES if necessary
new UnsupportedBackendOperation("ElasticsearchClient did not return a task id on submit request"))
.flatMap(response -> (response.task() == null) ? Mono.error(
new UnsupportedBackendOperation("ElasticsearchClient did not return a task id on submit request"))
: Mono.just(response.task()));
}
@@ -170,7 +202,7 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
}
private <R> Mono<BulkResponse> checkForBulkOperationFailure(BulkResponse bulkResponse) {
private Mono<BulkResponse> checkForBulkOperationFailure(BulkResponse bulkResponse) {
if (bulkResponse.errors()) {
Map<String, String> failedDocuments = new HashMap<>();
@@ -214,6 +246,31 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
}).onErrorResume(NoSuchIndexException.class, it -> Mono.empty());
}
@Override
public <T> Flux<MultiGetItem<T>> multiGet(Query query, Class<T> clazz, IndexCoordinates index) {
Assert.notNull(query, "query must not be null");
Assert.notNull(clazz, "clazz must not be null");
MgetRequest request = requestConverter.documentMgetRequest(query, clazz, index);
ReadDocumentCallback<T> callback = new ReadDocumentCallback<>(converter, clazz, index);
Publisher<MgetResponse<EntityAsMap>> response = execute(
(ClientCallback<Publisher<MgetResponse<EntityAsMap>>>) client -> client.mget(request, EntityAsMap.class));
return Mono.from(response)//
.flatMapMany(it -> Flux.fromIterable(DocumentAdapters.from(it))) //
.flatMap(multiGetItem -> {
if (multiGetItem.isFailed()) {
return Mono.just(MultiGetItem.of(null, multiGetItem.getFailure()));
} else {
return callback.toEntity(multiGetItem.getItem()) //
.map(t -> MultiGetItem.of(t, multiGetItem.getFailure()));
}
});
}
// endregion
@Override
@@ -223,8 +280,30 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
@Override
protected Mono<Boolean> doExists(String id, IndexCoordinates index) {
throw new UnsupportedOperationException("not implemented");
Assert.notNull(id, "id must not be null");
Assert.notNull(index, "index must not be null");
GetRequest getRequest = requestConverter.documentGetRequest(id, routingResolver.getRouting(), index, true);
return Mono.from(execute(
((ClientCallback<Publisher<GetResponse<EntityAsMap>>>) client -> client.get(getRequest, EntityAsMap.class))))
.map(GetResult::found) //
.onErrorReturn(NoSuchIndexException.class, false);
}
@Override
public Mono<ByQueryResponse> delete(Query query, Class<?> entityType, IndexCoordinates index) {
Assert.notNull(query, "query must not be null");
DeleteByQueryRequest request = requestConverter.documentDeleteByQueryRequest(query, entityType, index,
getRefreshPolicy());
return Mono
.from(execute((ClientCallback<Publisher<DeleteByQueryResponse>>) client -> client.deleteByQuery(request)))
.map(responseConverter::byQueryResponse);
}
// region search operations
@Override
@@ -247,22 +326,27 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
Time scrollTimeout = searchRequest.scroll() != null ? searchRequest.scroll() : Time.of(t -> t.time("1m"));
Flux<SearchResponse<EntityAsMap>> searchResponses = Flux.usingWhen(Mono.fromSupplier(ScrollState::new), //
state -> Mono
.from(execute((ClientCallback<Publisher<SearchResponse<EntityAsMap>>>) client -> client
.search(searchRequest, EntityAsMap.class))) //
.expand(entityAsMapSearchResponse -> {
Flux<ResponseBody<EntityAsMap>> searchResponses = Flux.usingWhen(Mono.fromSupplier(ScrollState::new), //
state -> {
return Mono
.from(execute((ClientCallback<Publisher<ResponseBody<EntityAsMap>>>) client1 -> client1
.search(searchRequest, EntityAsMap.class))) //
.expand(entityAsMapSearchResponse -> {
state.updateScrollId(entityAsMapSearchResponse.scrollId());
state.updateScrollId(entityAsMapSearchResponse.scrollId());
if (entityAsMapSearchResponse.hits() == null
|| CollectionUtils.isEmpty(entityAsMapSearchResponse.hits().hits())) {
return Mono.empty();
}
if (entityAsMapSearchResponse.hits() == null
|| CollectionUtils.isEmpty(entityAsMapSearchResponse.hits().hits())) {
return Mono.empty();
}
return Mono.from(execute((ClientCallback<Publisher<ScrollResponse<EntityAsMap>>>) client -> client.scroll(
ScrollRequest.of(sr -> sr.scrollId(state.getScrollId()).scroll(scrollTimeout)), EntityAsMap.class)));
}),
return Mono.from(execute((ClientCallback<Publisher<ScrollResponse<EntityAsMap>>>) client1 -> {
ScrollRequest scrollRequest = ScrollRequest
.of(sr -> sr.scrollId(state.getScrollId()).scroll(scrollTimeout));
return client1.scroll(scrollRequest, EntityAsMap.class);
}));
});
},
this::cleanupScroll, (state, ex) -> cleanupScroll(state), this::cleanupScroll);
return searchResponses.flatMapIterable(entityAsMapSearchResponse -> entityAsMapSearchResponse.hits().hits())
@@ -284,7 +368,7 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
SearchRequest searchRequest = requestConverter.searchRequest(query, entityType, index, true, false);
return Mono
.from(execute((ClientCallback<Publisher<SearchResponse<EntityAsMap>>>) client -> client.search(searchRequest,
.from(execute((ClientCallback<Publisher<ResponseBody<EntityAsMap>>>) client -> client.search(searchRequest,
EntityAsMap.class)))
.map(searchResponse -> searchResponse.hits().total() != null ? searchResponse.hits().total().value() : 0L);
}
@@ -292,7 +376,7 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
private Flux<SearchDocument> doFind(SearchRequest searchRequest) {
return Mono
.from(execute((ClientCallback<Publisher<SearchResponse<EntityAsMap>>>) client -> client.search(searchRequest,
.from(execute((ClientCallback<Publisher<ResponseBody<EntityAsMap>>>) client -> client.search(searchRequest,
EntityAsMap.class))) //
.flatMapIterable(entityAsMapSearchResponse -> entityAsMapSearchResponse.hits().hits()) //
.map(entityAsMapHit -> DocumentAdapters.from(entityAsMapHit, jsonpMapper));
@@ -307,16 +391,25 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
SearchRequest searchRequest = requestConverter.searchRequest(query, clazz, index, false, false);
// noinspection unchecked
SearchDocumentCallback<T> callback = new ReadSearchDocumentCallback<T>((Class<T>) clazz, index);
SearchDocumentCallback<T> callback = new ReadSearchDocumentCallback<>((Class<T>) clazz, index);
SearchDocumentResponse.EntityCreator<T> entityCreator = searchDocument -> callback.toEntity(searchDocument)
.toFuture();
return Mono
.from(execute((ClientCallback<Publisher<SearchResponse<EntityAsMap>>>) client -> client.search(searchRequest,
.from(execute((ClientCallback<Publisher<ResponseBody<EntityAsMap>>>) client -> client.search(searchRequest,
EntityAsMap.class)))
.map(searchResponse -> SearchDocumentResponseBuilder.from(searchResponse, entityCreator, jsonpMapper));
}
@Override
public Flux<? extends AggregationContainer<?>> aggregate(Query query, Class<?> entityType, IndexCoordinates index) {
return doFindForResponse(query, entityType, index).flatMapMany(searchDocumentResponse -> {
ElasticsearchAggregations aggregations = (ElasticsearchAggregations) searchDocumentResponse.getAggregations();
return aggregations == null ? Flux.empty() : Flux.fromIterable(aggregations.aggregations());
});
}
// endregion
@Override
@@ -326,7 +419,7 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
@Override
protected Mono<String> getRuntimeLibraryVersion() {
return Mono.just(Version.VERSION.toString());
return Mono.just(Version.VERSION != null ? Version.VERSION.toString() : "null");
}
@Override
@@ -334,47 +427,22 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
return Mono.from(execute(ReactiveElasticsearchClient::info)).map(infoResponse -> infoResponse.version().number());
}
@Override
public <T> Flux<T> saveAll(Mono<? extends Collection<? extends T>> entitiesPublisher, IndexCoordinates index) {
Assert.notNull(entitiesPublisher, "entitiesPublisher must not be null!");
return entitiesPublisher //
.flatMapMany(entities -> Flux.fromIterable(entities) //
.concatMap(entity -> maybeCallBeforeConvert(entity, index)) //
).collectList() //
.map(Entities::new) //
.flatMapMany(entities -> {
if (entities.isEmpty()) {
return Flux.empty();
}
return doBulkOperation(entities.indexQueries(), BulkOptions.defaultOptions(), index)//
.index() //
.flatMap(indexAndResponse -> {
T savedEntity = entities.entityAt(indexAndResponse.getT1());
BulkResponseItem response = indexAndResponse.getT2();
updateIndexedObject(savedEntity, IndexedObjectInformation.of(response.id(), response.seqNo(),
response.primaryTerm(), response.version()));
return maybeCallAfterSave(savedEntity, index);
});
});
}
@Override
public <T> Flux<MultiGetItem<T>> multiGet(Query query, Class<T> clazz, IndexCoordinates index) {
throw new UnsupportedOperationException("not implemented");
}
@Override
public Mono<ByQueryResponse> delete(Query query, Class<?> entityType, IndexCoordinates index) {
throw new UnsupportedOperationException("not implemented");
}
@Override
public Mono<UpdateResponse> update(UpdateQuery updateQuery, IndexCoordinates index) {
throw new UnsupportedOperationException("not implemented");
Assert.notNull(updateQuery, "UpdateQuery must not be null");
Assert.notNull(index, "Index must not be null");
UpdateRequest<Document, ?> request = requestConverter.documentUpdateRequest(updateQuery, index, getRefreshPolicy(),
routingResolver.getRouting());
return Mono.from(execute(
(ClientCallback<Publisher<co.elastic.clients.elasticsearch.core.UpdateResponse<Document>>>) client -> client
.update(request, Document.class)))
.flatMap(response -> {
UpdateResponse.Result result = result(response.result());
return result == null ? Mono.empty() : Mono.just(UpdateResponse.of(result));
});
}
@Override
@@ -413,11 +481,6 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
return new ReactiveClusterTemplate(client.cluster(), converter);
}
@Override
public Flux<AggregationContainer<?>> aggregate(Query query, Class<?> entityType, IndexCoordinates index) {
throw new UnsupportedOperationException("not implemented");
}
@Override
public Flux<Suggest> suggest(SuggestBuilder suggestion, Class<?> entityType) {
throw new UnsupportedOperationException("not implemented");
@@ -435,7 +498,7 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
@Override
public Query idsQuery(List<String> ids) {
throw new UnsupportedOperationException("not implemented");
return NativeQuery.builder().withQuery(QueryBuilders.idsQueryAsQuery(ids)).build();
}
/**
@@ -297,7 +297,7 @@ public class ReactiveIndicesTemplate extends ReactiveChildTemplate<ReactiveElast
co.elastic.clients.elasticsearch.indices.ExistsTemplateRequest existsTemplateRequestES = requestConverter
.indicesExistsTemplateRequest(existsTemplateRequest);
return Mono.from(execute(client1 -> client1.existsTemplate(existsTemplateRequestES))).map(BooleanResponse::value);
return Mono.from(execute(client -> client.existsTemplate(existsTemplateRequestES))).map(BooleanResponse::value);
}
@Override
@@ -307,13 +307,18 @@ public class ReactiveIndicesTemplate extends ReactiveChildTemplate<ReactiveElast
co.elastic.clients.elasticsearch.indices.DeleteTemplateRequest deleteTemplateRequestES = requestConverter
.indicesDeleteTemplateRequest(deleteTemplateRequest);
return Mono.from(execute(client1 -> client1.deleteTemplate(deleteTemplateRequestES)))
return Mono.from(execute(client -> client.deleteTemplate(deleteTemplateRequestES)))
.map(DeleteTemplateResponse::acknowledged);
}
@Override
public Flux<IndexInformation> getInformation(IndexCoordinates index) {
throw new UnsupportedOperationException("not implemented");
GetIndexRequest request = requestConverter.indicesGetIndexRequest(index);
return Mono.from(execute(client -> client.get(request))) //
.map(responseConverter::indicesGetIndexInformations) //
.flatMapMany(Flux::fromIterable);
}
@Override
@@ -16,14 +16,15 @@
package org.springframework.data.elasticsearch.client.elc;
import static org.springframework.data.elasticsearch.client.elc.TypeUtils.*;
import static org.springframework.util.ObjectUtils.*;
import static org.springframework.util.CollectionUtils.*;
import co.elastic.clients.elasticsearch._types.Conflicts;
import co.elastic.clients.elasticsearch._types.InlineScript;
import co.elastic.clients.elasticsearch._types.OpType;
import co.elastic.clients.elasticsearch._types.SortOptions;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.Time;
import co.elastic.clients.elasticsearch._types.VersionType;
import co.elastic.clients.elasticsearch._types.WaitForActiveShardOptions;
import co.elastic.clients.elasticsearch._types.mapping.FieldType;
import co.elastic.clients.elasticsearch._types.mapping.Property;
import co.elastic.clients.elasticsearch._types.mapping.RuntimeField;
@@ -31,20 +32,17 @@ import co.elastic.clients.elasticsearch._types.mapping.RuntimeFieldType;
import co.elastic.clients.elasticsearch._types.mapping.TypeMapping;
import co.elastic.clients.elasticsearch._types.query_dsl.Like;
import co.elastic.clients.elasticsearch.cluster.HealthRequest;
import co.elastic.clients.elasticsearch.core.BulkRequest;
import co.elastic.clients.elasticsearch.core.DeleteByQueryRequest;
import co.elastic.clients.elasticsearch.core.DeleteRequest;
import co.elastic.clients.elasticsearch.core.GetRequest;
import co.elastic.clients.elasticsearch.core.IndexRequest;
import co.elastic.clients.elasticsearch.core.MgetRequest;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.*;
import co.elastic.clients.elasticsearch.core.bulk.BulkOperation;
import co.elastic.clients.elasticsearch.core.bulk.CreateOperation;
import co.elastic.clients.elasticsearch.core.bulk.IndexOperation;
import co.elastic.clients.elasticsearch.core.bulk.UpdateOperation;
import co.elastic.clients.elasticsearch.core.mget.MultiGetOperation;
import co.elastic.clients.elasticsearch.core.search.Highlight;
import co.elastic.clients.elasticsearch.core.search.Rescore;
import co.elastic.clients.elasticsearch.core.search.SourceConfig;
import co.elastic.clients.elasticsearch.indices.*;
import co.elastic.clients.elasticsearch.indices.ExistsRequest;
import co.elastic.clients.elasticsearch.indices.update_aliases.Action;
import co.elastic.clients.json.JsonData;
import co.elastic.clients.json.JsonpDeserializer;
@@ -58,7 +56,9 @@ import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
@@ -67,6 +67,7 @@ import java.util.stream.Collectors;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.core.RefreshPolicy;
import org.springframework.data.elasticsearch.core.ScriptType;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
import org.springframework.data.elasticsearch.core.document.Document;
import org.springframework.data.elasticsearch.core.index.AliasAction;
@@ -192,7 +193,7 @@ class RequestConverter {
if (filterQuery != null) {
elasticsearchConverter.updateQuery(filterQuery, parameters.getFilterQueryClass());
co.elastic.clients.elasticsearch._types.query_dsl.Query esQuery = getFilter(filterQuery);
co.elastic.clients.elasticsearch._types.query_dsl.Query esQuery = getQuery(filterQuery, null);
if (esQuery != null) {
addActionBuilder.filter(esQuery);
@@ -239,7 +240,8 @@ class RequestConverter {
PutMappingRequest.Builder builder = new PutMappingRequest.Builder();
builder.index(Arrays.asList(indexCoordinates.getIndexNames()));
addPropertiesToMapping(builder, mapping);
// TODO #1973 what else to add
// TODO #2155 what else to add
return builder.build();
}
@@ -374,7 +376,7 @@ class RequestConverter {
Query filterQuery = parameters.getFilterQuery();
if (filterQuery != null) {
co.elastic.clients.elasticsearch._types.query_dsl.Query esQuery = getFilter(filterQuery);
co.elastic.clients.elasticsearch._types.query_dsl.Query esQuery = getQuery(filterQuery, null);
if (esQuery != null) {
aliasBuilder.filter(esQuery);
@@ -475,7 +477,7 @@ class RequestConverter {
}
}
builder.refresh(refresh(refreshPolicy));
builder.refresh(TypeUtils.refresh(refreshPolicy));
return builder.build();
}
@@ -562,6 +564,79 @@ class RequestConverter {
return builder.build();
}
private UpdateOperation<?, ?> bulkUpdateOperation(UpdateQuery query, IndexCoordinates index,
@Nullable RefreshPolicy refreshPolicy) {
UpdateOperation.Builder<Object, Object> uob = new UpdateOperation.Builder<>();
String indexName = query.getIndexName() != null ? query.getIndexName() : index.getIndexName();
uob.index(indexName).id(query.getId());
uob.action(a -> {
a //
.script(getScript(query.getScriptData())) //
.doc(query.getDocument()) //
.upsert(query.getUpsert()) //
.scriptedUpsert(query.getScriptedUpsert()) //
.docAsUpsert(query.getDocAsUpsert()) //
;
if (query.getFetchSource() != null) {
a.source(sc -> sc.fetch(query.getFetchSource()));
}
if (query.getFetchSourceIncludes() != null || query.getFetchSourceExcludes() != null) {
List<String> includes = query.getFetchSourceIncludes() != null ? query.getFetchSourceIncludes()
: Collections.emptyList();
List<String> excludes = query.getFetchSourceExcludes() != null ? query.getFetchSourceExcludes()
: Collections.emptyList();
a.source(sc -> sc.filter(sf -> sf.includes(includes).excludes(excludes)));
}
return a;
});
uob //
.routing(query.getRouting()) //
.ifSeqNo(query.getIfSeqNo() != null ? Long.valueOf(query.getIfSeqNo()) : null) //
.ifPrimaryTerm(query.getIfPrimaryTerm() != null ? Long.valueOf(query.getIfPrimaryTerm()) : null) //
.retryOnConflict(query.getRetryOnConflict()) //
;
// no refresh, timeout, waitForActiveShards on UpdateOperation or UpdateAction
return uob.build();
}
@Nullable
private co.elastic.clients.elasticsearch._types.Script getScript(@Nullable ScriptData scriptData) {
if (scriptData == null) {
return null;
}
Map<String, JsonData> params = new HashMap<>();
if (scriptData.getParams() != null) {
scriptData.getParams().forEach((key, value) -> {
params.put(key, JsonData.of(value, jsonpMapper));
});
}
return co.elastic.clients.elasticsearch._types.Script.of(sb -> {
if (scriptData.getType() == ScriptType.INLINE) {
sb.inline(is -> is //
.lang(scriptData.getLanguage()) //
.source(scriptData.getScript()) //
.params(params)); //
} else if (scriptData.getType() == ScriptType.STORED) {
sb.stored(ss -> ss //
.id(scriptData.getScript()) //
.params(params) //
);
}
return sb;
});
}
public BulkRequest documentBulkRequest(List<?> queries, BulkOptions bulkOptions, IndexCoordinates indexCoordinates,
@Nullable RefreshPolicy refreshPolicy) {
@@ -571,9 +646,9 @@ class RequestConverter {
builder.timeout(tb -> tb.time(Long.valueOf(bulkOptions.getTimeout().toMillis()).toString() + "ms"));
}
builder.refresh(refresh(refreshPolicy));
builder.refresh(TypeUtils.refresh(refreshPolicy));
if (bulkOptions.getRefreshPolicy() != null) {
builder.refresh(refresh(bulkOptions.getRefreshPolicy()));
builder.refresh(TypeUtils.refresh(bulkOptions.getRefreshPolicy()));
}
if (bulkOptions.getWaitForActiveShards() != null) {
@@ -599,7 +674,8 @@ class RequestConverter {
ob.index(bulkIndexOperation(indexQuery, indexCoordinates, refreshPolicy));
}
} else if (query instanceof UpdateQuery) {
// todo #1973
UpdateQuery updateQuery = (UpdateQuery) query;
ob.update(bulkUpdateOperation(updateQuery, indexCoordinates, refreshPolicy));
}
return ob.build();
}).collect(Collectors.toList());
@@ -674,7 +750,7 @@ class RequestConverter {
}
if (source.getQuery() != null) {
s.query(getQuery(source.getQuery()));
s.query(getQuery(source.getQuery(), null));
}
if (source.getRemote() != null) {
@@ -717,13 +793,13 @@ class RequestConverter {
ReindexRequest.Dest dest = reindexRequest.getDest();
return d //
.index(dest.getIndex().getIndexName()) //
.versionType(versionType(dest.getVersionType())) //
.opType(opType(dest.getOpType()));
.versionType(TypeUtils.versionType(dest.getVersionType())) //
.opType(TypeUtils.opType(dest.getOpType()));
} //
);
if (reindexRequest.getConflicts() != null) {
builder.conflicts(conflicts(reindexRequest.getConflicts()));
builder.conflicts(TypeUtils.conflicts(reindexRequest.getConflicts()));
}
ReindexRequest.Script script = reindexRequest.getScript();
@@ -731,17 +807,12 @@ class RequestConverter {
builder.script(s -> s.inline(InlineScript.of(i -> i.lang(script.getLang()).source(script.getSource()))));
}
if (reindexRequest.getTimeout() != null) {
builder.timeout(tv -> tv.time(reindexRequest.getTimeout().toMillis() + "ms"));
}
if (reindexRequest.getScroll() != null) {
builder.scroll(tv -> tv.time(reindexRequest.getScroll().toMillis() + "ms"));
}
builder.timeout(time(reindexRequest.getTimeout())) //
.scroll(time(reindexRequest.getScroll()));
if (reindexRequest.getWaitForActiveShards() != null) {
builder.waitForActiveShards(wfas -> wfas //
.count(waitForActiveShardsCount(reindexRequest.getWaitForActiveShards())));
.count(TypeUtils.waitForActiveShardsCount(reindexRequest.getWaitForActiveShards())));
}
builder //
@@ -766,7 +837,7 @@ class RequestConverter {
if (routing != null) {
r.routing(routing);
}
r.refresh(refresh(refreshPolicy));
r.refresh(TypeUtils.refresh(refreshPolicy));
return r;
});
}
@@ -779,7 +850,7 @@ class RequestConverter {
return DeleteByQueryRequest.of(b -> {
b.index(Arrays.asList(index.getIndexNames())) //
.query(getQuery(query))//
.query(getQuery(query, clazz))//
.refresh(deleteByQueryRefresh(refreshPolicy));
if (query.isLimiting()) {
@@ -787,10 +858,7 @@ class RequestConverter {
b.maxDocs(Long.valueOf(query.getMaxResults()));
}
if (query.hasScrollTime()) {
// noinspection ConstantConditions
b.scroll(Time.of(t -> t.time(query.getScrollTime().toMillis() + "ms")));
}
b.scroll(time(query.getScrollTime()));
if (query.getRoute() != null) {
b.routing(query.getRoute());
@@ -800,6 +868,140 @@ class RequestConverter {
});
}
public UpdateRequest<Document, ?> documentUpdateRequest(UpdateQuery query, IndexCoordinates index,
@Nullable RefreshPolicy refreshPolicy, @Nullable String routing) {
String indexName = query.getIndexName() != null ? query.getIndexName() : index.getIndexName();
return UpdateRequest.of(uqb -> {
uqb.index(indexName).id(query.getId());
if (query.getScript() != null) {
Map<String, JsonData> params = new HashMap<>();
if (query.getParams() != null) {
query.getParams().forEach((key, value) -> {
params.put(key, JsonData.of(value, jsonpMapper));
});
}
uqb.script(sb -> {
if (query.getScriptType() == ScriptType.INLINE) {
sb.inline(is -> is //
.lang(query.getLang()) //
.source(query.getScript()) //
.params(params)); //
} else if (query.getScriptType() == ScriptType.STORED) {
sb.stored(ss -> ss //
.id(query.getScript()) //
.params(params) //
);
}
return sb;
}
);
}
uqb //
.doc(query.getDocument()) //
.upsert(query.getUpsert()) //
.routing(query.getRouting() != null ? query.getRouting() : routing) //
.scriptedUpsert(query.getScriptedUpsert()) //
.docAsUpsert(query.getDocAsUpsert()) //
.ifSeqNo(query.getIfSeqNo() != null ? Long.valueOf(query.getIfSeqNo()) : null) //
.ifPrimaryTerm(query.getIfPrimaryTerm() != null ? Long.valueOf(query.getIfPrimaryTerm()) : null) //
.refresh(TypeUtils.refresh(refreshPolicy)) //
.retryOnConflict(query.getRetryOnConflict()) //
;
if (query.getFetchSource() != null) {
uqb.source(sc -> sc.fetch(query.getFetchSource()));
}
if (query.getFetchSourceIncludes() != null || query.getFetchSourceExcludes() != null) {
List<String> includes = query.getFetchSourceIncludes() != null ? query.getFetchSourceIncludes()
: Collections.emptyList();
List<String> excludes = query.getFetchSourceExcludes() != null ? query.getFetchSourceExcludes()
: Collections.emptyList();
uqb.source(sc -> sc.filter(sf -> sf.includes(includes).excludes(excludes)));
}
if (query.getTimeout() != null) {
uqb.timeout(tv -> tv.time(query.getTimeout()));
}
String waitForActiveShards = query.getWaitForActiveShards();
if (waitForActiveShards != null) {
if ("all".equalsIgnoreCase(waitForActiveShards)) {
uqb.waitForActiveShards(wfa -> wfa.option(WaitForActiveShardOptions.All));
} else {
int val;
try {
val = Integer.parseInt(waitForActiveShards);
} catch (NumberFormatException var3) {
throw new IllegalArgumentException("cannot parse ActiveShardCount[" + waitForActiveShards + "]", var3);
}
uqb.waitForActiveShards(wfa -> wfa.count(val));
}
}
return uqb;
} //
);
}
public UpdateByQueryRequest documentUpdateByQueryRequest(UpdateQuery updateQuery, IndexCoordinates index,
@Nullable RefreshPolicy refreshPolicy) {
return UpdateByQueryRequest.of(ub -> {
ub //
.index(Arrays.asList(index.getIndexNames())) //
.refresh(refreshPolicy == RefreshPolicy.IMMEDIATE) //
.routing(updateQuery.getRouting()) //
.script(getScript(updateQuery.getScriptData())) //
.maxDocs(updateQuery.getMaxDocs() != null ? Long.valueOf(updateQuery.getMaxDocs()) : null) //
.pipeline(updateQuery.getPipeline()) //
.requestsPerSecond(
updateQuery.getRequestsPerSecond() != null ? updateQuery.getRequestsPerSecond().longValue() : null) //
.slices(updateQuery.getSlices() != null ? Long.valueOf(updateQuery.getSlices()) : null) //
;
if (updateQuery.getAbortOnVersionConflict() != null) {
ub.conflicts(updateQuery.getAbortOnVersionConflict() ? Conflicts.Abort : Conflicts.Proceed);
}
if (updateQuery.getBatchSize() != null) {
ub.size(Long.valueOf(updateQuery.getBatchSize()));
}
if (updateQuery.getQuery() != null) {
Query queryQuery = updateQuery.getQuery();
ub.query(getQuery(queryQuery, null));
// no indicesOptions available like in old client
ub.scroll(time(queryQuery.getScrollTime()));
}
// no maxRetries available like in old client
// no shouldStoreResult
if (updateQuery.getRefreshPolicy() != null) {
ub.refresh(updateQuery.getRefreshPolicy() == RefreshPolicy.IMMEDIATE);
}
if (updateQuery.getTimeout() != null) {
ub.timeout(tb -> tb.time(updateQuery.getTimeout()));
}
if (updateQuery.getWaitForActiveShards() != null) {
ub.waitForActiveShards(w -> w.count(TypeUtils.waitForActiveShardsCount(updateQuery.getWaitForActiveShards())));
}
return ub;
});
}
// endregion
// region search
@@ -831,13 +1033,36 @@ class RequestConverter {
builder.scroll(t -> t.time(scrollTimeInMillis + "ms"));
}
builder.query(getQuery(query));
builder.query(getQuery(query, clazz));
addFilter(query, builder);
return builder.build();
}
public MsearchRequest searchMsearchRequest(
List<ElasticsearchTemplate.MultiSearchQueryParameter> multiSearchQueryParameters) {
return MsearchRequest.of(mrb -> {
multiSearchQueryParameters.forEach(param -> {
ElasticsearchPersistentEntity<?> persistentEntity = getPersistentEntity(param.clazz);
mrb.searches(sb -> sb //
.header(h -> h //
.index(param.index.getIndexName()) //
// todo #2156 add remaining flags for header
) //
.body(bb -> bb //
.query(getQuery(param.query, param.clazz))//
// todo #2138 seq_no_primary_term and version not available in client ES issue 161
// todo #2156 add remaining flags for body
) //
);
});
return mrb;
});
}
private <T> void prepareSearchRequest(Query query, @Nullable Class<T> clazz, IndexCoordinates indexCoordinates,
SearchRequest.Builder builder, boolean forCount, boolean useScroll) {
@@ -878,7 +1103,7 @@ class RequestConverter {
}
if (query.getIndicesOptions() != null) {
// todo #1973 indices options
// new Elasticsearch client does not support the old Indices options, need to be adapted
}
if (query.isLimiting()) {
@@ -893,7 +1118,7 @@ class RequestConverter {
builder.preference(query.getPreference());
}
// todo #1973 searchType
builder.searchType(searchType(query.getSearchType()));
if (query.getSort() != null) {
List<SortOptions> sortOptions = getSortOptions(query.getSort(), persistentEntity);
@@ -921,7 +1146,7 @@ class RequestConverter {
builder.routing(query.getRoute());
}
// todo #1973 timeout
builder.timeout(timeStringMs(query.getTimeout()));
if (query.getExplain()) {
builder.explain(true);
@@ -930,8 +1155,12 @@ class RequestConverter {
if (!isEmpty(query.getSearchAfter())) {
builder.searchAfter(query.getSearchAfter().stream().map(Object::toString).collect(Collectors.toList()));
}
// todo #1973 rescorer queries
// todo #1973 request cache
query.getRescorerQueries().forEach(rescorerQuery -> {
builder.rescore(getRescore(rescorerQuery));
});
builder.requestCache(query.getRequestCache());
if (!query.getRuntimeFields().isEmpty()) {
@@ -952,10 +1181,34 @@ class RequestConverter {
// request_cache is not allowed on scroll requests.
builder.requestCache(null);
Duration scrollTimeout = query.getScrollTime() != null ? query.getScrollTime() : Duration.ofMinutes(1);
builder.scroll(tv -> tv.time(scrollTimeout.toMillis() + "ms"));
builder.scroll(time(scrollTimeout));
// limit the number of documents in a batch
builder.size(500);
}
if (!isEmpty(query.getIndicesBoost())) {
Map<String, Double> boosts = new LinkedHashMap<>();
query.getIndicesBoost().forEach(indexBoost -> {
boosts.put(indexBoost.getIndexName(), (double) indexBoost.getBoost());
});
// noinspection unchecked
builder.indicesBoost(boosts);
}
}
private Rescore getRescore(RescorerQuery rescorerQuery) {
return Rescore.of(r -> r //
.query(rq -> rq //
.query(getQuery(rescorerQuery.getQuery(), null)) //
.scoreMode(TypeUtils.scoreMode(rescorerQuery.getScoreMode())) //
.queryWeight(rescorerQuery.getQueryWeight() != null ? Double.valueOf(rescorerQuery.getQueryWeight()) : 1.0) //
.rescoreQueryWeight(
rescorerQuery.getRescoreQueryWeight() != null ? Double.valueOf(rescorerQuery.getRescoreQueryWeight())
: 1.0) //
) //
.windowSize(rescorerQuery.getWindowSize()));
}
private void addHighlight(Query query, SearchRequest.Builder builder) {
@@ -1000,8 +1253,9 @@ class RequestConverter {
.geoDistance(gd -> gd //
.field(fieldName) //
.location(loc -> loc.latlon(QueryBuilders.latLon(geoDistanceOrder.getGeoPoint())))//
.distanceType(geoDistanceType(geoDistanceOrder.getDistanceType())).mode(sortMode(finalMode)) //
.unit(distanceUnit(geoDistanceOrder.getUnit())) //
.distanceType(TypeUtils.geoDistanceType(geoDistanceOrder.getDistanceType()))
.mode(TypeUtils.sortMode(finalMode)) //
.unit(TypeUtils.distanceUnit(geoDistanceOrder.getUnit())) //
.ignoreUnmapped(geoDistanceOrder.getIgnoreUnmapped())));
} else {
String missing = (order.getNullHandling() == Sort.NullHandling.NULLS_FIRST) ? "_first"
@@ -1011,10 +1265,10 @@ class RequestConverter {
.field(f -> {
f.field(fieldName) //
.order(sortOrder) //
.mode(sortMode(finalMode));
.mode(TypeUtils.sortMode(finalMode));
if (finalUnmappedType != null) {
FieldType fieldType = fieldType(finalUnmappedType);
FieldType fieldType = TypeUtils.fieldType(finalUnmappedType);
if (fieldType != null) {
f.unmappedType(fieldType);
@@ -1032,23 +1286,33 @@ class RequestConverter {
}
private void prepareNativeSearch(NativeQuery query, SearchRequest.Builder builder) {
// todo #1973 script fields
// todo #1973 collapse builder
// todo #1973 indices boost
query.getScriptedFields().forEach(scriptedField -> {
builder.scriptFields(scriptedField.getFieldName(), sf -> sf.script(getScript(scriptedField.getScriptData())));
});
builder //
.suggest(query.getSuggester()) //
.collapse(query.getFieldCollapse()) //
;
if (!isEmpty(query.getAggregations())) {
builder.aggregations(query.getAggregations());
}
builder.suggest(query.getSuggester());
// todo #1973 searchExt
// todo #2150 searchExt, currently not supported by the new client
}
@Nullable
private co.elastic.clients.elasticsearch._types.query_dsl.Query getQuery(Query query) {
private co.elastic.clients.elasticsearch._types.query_dsl.Query getQuery(@Nullable Query query,
@Nullable Class<?> clazz) {
if (query == null) {
return null;
}
elasticsearchConverter.updateQuery(query, clazz);
// todo #1973 some native stuff
co.elastic.clients.elasticsearch._types.query_dsl.Query esQuery = null;
if (query instanceof CriteriaQuery) {
@@ -1074,7 +1338,7 @@ class RequestConverter {
} else if (query instanceof StringQuery) {
// no filter for StringQuery
} else if (query instanceof NativeQuery) {
// todo #1973 NativeQuery filter
builder.postFilter(((NativeQuery) query).getFilter());
} else {
throw new IllegalArgumentException("unhandled Query implementation " + query.getClass().getName());
}
@@ -1128,6 +1392,7 @@ class RequestConverter {
return moreLikeThisQuery;
}
// endregion
// region helper functions
@@ -1208,12 +1473,6 @@ class RequestConverter {
return versionType != null ? versionType : VersionType.External;
}
private co.elastic.clients.elasticsearch._types.query_dsl.Query getFilter(Query filterQuery) {
// TODO #1973 add filter query
throw new UnsupportedOperationException("not implemented");
}
@Nullable
private SourceConfig getSourceConfig(Query query) {
@@ -23,6 +23,7 @@ import co.elastic.clients.elasticsearch._types.Time;
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
import co.elastic.clients.elasticsearch.cluster.HealthResponse;
import co.elastic.clients.elasticsearch.core.DeleteByQueryResponse;
import co.elastic.clients.elasticsearch.core.UpdateByQueryResponse;
import co.elastic.clients.elasticsearch.core.mget.MultiGetError;
import co.elastic.clients.elasticsearch.core.mget.MultiGetResponseItem;
import co.elastic.clients.elasticsearch.indices.*;
@@ -314,6 +315,56 @@ class ResponseConverter {
}
public ByQueryResponse byQueryResponse(DeleteByQueryResponse response) {
// the code for the methods taking a DeleteByQueryResponse or a UpdateByQueryResponse is duplicated because the
// Elasticsearch responses do not share a common class
// noinspection DuplicatedCode
List<ByQueryResponse.Failure> failures = response.failures().stream().map(this::byQueryResponseFailureOf)
.collect(Collectors.toList());
ByQueryResponse.ByQueryResponseBuilder builder = ByQueryResponse.builder();
if (response.took() != null) {
builder.withTook(response.took());
}
if (response.timedOut() != null) {
builder.withTimedOut(response.timedOut());
}
if (response.total() != null) {
builder.withTotal(response.total());
}
if (response.deleted() != null) {
builder.withDeleted(response.deleted());
}
if (response.batches() != null) {
builder.withBatches(Math.toIntExact(response.batches()));
}
if (response.versionConflicts() != null) {
builder.withVersionConflicts(response.versionConflicts());
}
if (response.noops() != null) {
builder.withNoops(response.noops());
}
if (response.retries() != null) {
builder.withBulkRetries(response.retries().bulk());
builder.withSearchRetries(response.retries().search());
}
builder.withFailures(failures);
return builder.build();
}
public ByQueryResponse byQueryResponse(UpdateByQueryResponse response) {
// the code for the methods taking a DeleteByQueryResponse or a UpdateByQueryResponse is duplicated because the
// Elasticsearch responses do not share a common class
// noinspection DuplicatedCode
List<ByQueryResponse.Failure> failures = response.failures().stream().map(this::byQueryResponseFailureOf)
.collect(Collectors.toList());
@@ -358,8 +409,8 @@ class ResponseConverter {
}
// endregion
// region helper functions
private long timeToLong(Time time) {
if (time.isTime()) {
@@ -19,6 +19,7 @@ import co.elastic.clients.elasticsearch._types.aggregations.Aggregate;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.elasticsearch.core.search.HitsMetadata;
import co.elastic.clients.elasticsearch.core.search.ResponseBody;
import co.elastic.clients.elasticsearch.core.search.Suggestion;
import co.elastic.clients.elasticsearch.core.search.TotalHits;
import co.elastic.clients.json.JsonpMapper;
@@ -45,21 +46,22 @@ class SearchDocumentResponseBuilder {
/**
* creates a SearchDocumentResponse from the {@link SearchResponse}
*
* @param searchResponse the Elasticsearch search response
* @param responseBody the Elasticsearch response body
* @param entityCreator function to create an entity from a {@link SearchDocument}
* @param jsonpMapper to map JsonData objects
* @return the SearchDocumentResponse
*/
public static <T> SearchDocumentResponse from(SearchResponse<EntityAsMap> searchResponse,
@SuppressWarnings("DuplicatedCode")
public static <T> SearchDocumentResponse from(ResponseBody<EntityAsMap> responseBody,
SearchDocumentResponse.EntityCreator<T> entityCreator, JsonpMapper jsonpMapper) {
Assert.notNull(searchResponse, "searchResponse must not be null");
Assert.notNull(responseBody, "responseBody must not be null");
Assert.notNull(entityCreator, "entityCreator must not be null");
HitsMetadata<EntityAsMap> hitsMetadata = searchResponse.hits();
String scrollId = searchResponse.scrollId();
Map<String, Aggregate> aggregations = searchResponse.aggregations();
Map<String, List<Suggestion<EntityAsMap>>> suggest = searchResponse.suggest();
HitsMetadata<EntityAsMap> hitsMetadata = responseBody.hits();
String scrollId = responseBody.scrollId();
Map<String, Aggregate> aggregations = responseBody.aggregations();
Map<String, List<Suggestion<EntityAsMap>>> suggest = responseBody.suggest();
return from(hitsMetadata, scrollId, aggregations, suggest, entityCreator, jsonpMapper);
}
@@ -114,7 +116,7 @@ class SearchDocumentResponseBuilder {
ElasticsearchAggregations aggregationsContainer = aggregations != null ? new ElasticsearchAggregations(aggregations)
: null;
// todo #1973
// todo #2154
Suggest suggest = null;
return new SearchDocumentResponse(totalHits, totalHitsRelation, maxScore, scrollId, searchDocuments,
@@ -15,13 +15,7 @@
*/
package org.springframework.data.elasticsearch.client.elc;
import co.elastic.clients.elasticsearch._types.Conflicts;
import co.elastic.clients.elasticsearch._types.DistanceUnit;
import co.elastic.clients.elasticsearch._types.GeoDistanceType;
import co.elastic.clients.elasticsearch._types.OpType;
import co.elastic.clients.elasticsearch._types.Refresh;
import co.elastic.clients.elasticsearch._types.SortMode;
import co.elastic.clients.elasticsearch._types.VersionType;
import co.elastic.clients.elasticsearch._types.*;
import co.elastic.clients.elasticsearch._types.mapping.FieldType;
import co.elastic.clients.elasticsearch.core.search.BoundaryScanner;
import co.elastic.clients.elasticsearch.core.search.BuiltinHighlighterType;
@@ -30,11 +24,17 @@ import co.elastic.clients.elasticsearch.core.search.HighlighterFragmenter;
import co.elastic.clients.elasticsearch.core.search.HighlighterOrder;
import co.elastic.clients.elasticsearch.core.search.HighlighterTagsSchema;
import co.elastic.clients.elasticsearch.core.search.HighlighterType;
import co.elastic.clients.elasticsearch.core.search.ScoreMode;
import java.time.Duration;
import org.springframework.data.elasticsearch.core.RefreshPolicy;
import org.springframework.data.elasticsearch.core.query.GeoDistanceOrder;
import org.springframework.data.elasticsearch.core.query.IndexQuery;
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.reindex.ReindexRequest;
import org.springframework.lang.Nullable;
@@ -243,6 +243,71 @@ final class TypeUtils {
}
}
@Nullable
static UpdateResponse.Result result(@Nullable Result result) {
if (result == null) {
return null;
}
switch (result) {
case Created:
return UpdateResponse.Result.CREATED;
case Updated:
return UpdateResponse.Result.UPDATED;
case Deleted:
return UpdateResponse.Result.DELETED;
case NotFound:
return UpdateResponse.Result.NOT_FOUND;
case NoOp:
return UpdateResponse.Result.NOOP;
}
return null;
}
@Nullable
static ScoreMode scoreMode(@Nullable RescorerQuery.ScoreMode scoreMode) {
if (scoreMode == null) {
return null;
}
switch (scoreMode) {
case Default:
return null;
case Avg:
return ScoreMode.Avg;
case Max:
return ScoreMode.Max;
case Min:
return ScoreMode.Min;
case Total:
return ScoreMode.Total;
case Multiply:
return ScoreMode.Multiply;
}
return null;
}
@Nullable
static SearchType searchType(@Nullable Query.SearchType searchType) {
if (searchType == null) {
return null;
}
switch (searchType) {
case QUERY_THEN_FETCH:
return SearchType.QueryThenFetch;
case DFS_QUERY_THEN_FETCH:
return SearchType.DfsQueryThenFetch;
}
return null;
}
@Nullable
static SortMode sortMode(Order.Mode mode) {
@@ -260,6 +325,26 @@ final class TypeUtils {
return null;
}
@Nullable
static Time time(@Nullable Duration duration) {
if (duration == null) {
return null;
}
return Time.of(t -> t.time(duration.toMillis() + "ms"));
}
@Nullable
static String timeStringMs(@Nullable Duration duration) {
if (duration == null) {
return null;
}
return duration.toMillis() + "ms";
}
@Nullable
static VersionType versionType(
@Nullable org.springframework.data.elasticsearch.annotations.Document.VersionType versionType) {
@@ -73,7 +73,7 @@ abstract public class AbstractReactiveElasticsearchTemplate
protected final SimpleElasticsearchMappingContext mappingContext;
protected final EntityOperations entityOperations;
protected @Nullable RefreshPolicy refreshPolicy = RefreshPolicy.IMMEDIATE;
protected @Nullable RefreshPolicy refreshPolicy = RefreshPolicy.NONE;
protected RoutingResolver routingResolver;
protected @Nullable ReactiveEntityCallbacks entityCallbacks;
@@ -443,7 +443,7 @@ abstract public class AbstractReactiveElasticsearchTemplate
IndexCoordinates index);
@Override
public Flux<AggregationContainer<?>> aggregate(Query query, Class<?> entityType) {
public Flux<? extends AggregationContainer<?>> aggregate(Query query, Class<?> entityType) {
return aggregate(query, entityType, getIndexCoordinatesFor(entityType));
}
@@ -277,8 +277,7 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
*/
protected Mono<GetResult> doGet(GetRequest request) {
return Mono.from(execute(client -> client.get(request))) //
.onErrorResume(NoSuchIndexException.class, it -> Mono.empty());
return Mono.from(execute(client -> client.get(request)));
}
protected Mono<String> doDeleteById(String id, @Nullable String routing, IndexCoordinates index) {
@@ -633,8 +632,7 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
QUERY_LOGGER.debug(String.format("Executing doCount: %s", request));
}
return Mono.from(execute(client -> client.count(request))) //
.onErrorResume(NoSuchIndexException.class, it -> Mono.just(0L));
return Mono.from(execute(client -> client.count(request)));
}
/**
@@ -22,8 +22,8 @@ import java.util.List;
import org.elasticsearch.search.suggest.SuggestBuilder;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.core.suggest.response.Suggest;
@@ -237,7 +237,7 @@ public interface ReactiveSearchOperations {
* @return a {@link Flux} emitting matching aggregations one by one.
* @since 4.0
*/
Flux<AggregationContainer<?>> aggregate(Query query, Class<?> entityType);
Flux<? extends AggregationContainer<?>> aggregate(Query query, Class<?> entityType);
/**
* Perform an aggregation specified by the given {@link Query query}. <br />
@@ -248,7 +248,7 @@ public interface ReactiveSearchOperations {
* @return a {@link Flux} emitting matching aggregations one by one.
* @since 4.0
*/
Flux<AggregationContainer<?>> aggregate(Query query, Class<?> entityType, IndexCoordinates index);
Flux<? extends AggregationContainer<?>> aggregate(Query query, Class<?> entityType, IndexCoordinates index);
/**
* Does a suggest query
@@ -66,7 +66,6 @@ final public class ElasticsearchDateConverter {
*/
public static ElasticsearchDateConverter of(String pattern) {
Assert.notNull(pattern, "pattern must not be null");
Assert.hasText(pattern, "pattern must not be empty");
String[] subPatterns = pattern.split("\\|\\|");
@@ -86,7 +85,7 @@ final public class ElasticsearchDateConverter {
*/
public String format(TemporalAccessor accessor) {
Assert.notNull("accessor", "accessor must not be null");
Assert.notNull(accessor, "accessor must not be null");
if (accessor instanceof Instant) {
Instant instant = (Instant) accessor;
@@ -22,7 +22,8 @@ import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* class that holds explanations returned from an Elasticsearch search.
* class that holds explanations returned from an Elasticsearch search. Note: the new Elasticsearch client does not
* return the match property in search hits anymore, probably because a returned hit always is a match.
*
* @author Peter-Josef Meisch
*/
@@ -62,15 +62,16 @@ public class BaseQuery implements Query {
@Nullable protected Integer maxResults;
@Nullable protected HighlightQuery highlightQuery;
@Nullable private Boolean trackTotalHits;
@Nullable private Integer trackTotalHitsUpTo;
@Nullable private Duration scrollTime;
@Nullable private Duration timeout;
@Nullable protected Integer trackTotalHitsUpTo;
@Nullable protected Duration scrollTime;
@Nullable protected Duration timeout;
private boolean explain = false;
@Nullable private List<Object> searchAfter;
@Nullable protected List<Object> searchAfter;
protected List<RescorerQuery> rescorerQueries = new ArrayList<>();
@Nullable protected Boolean requestCache;
private List<IdWithRouting> idsWithRouting = Collections.emptyList();
private final List<RuntimeField> runtimeFields = new ArrayList<>();
protected List<IdWithRouting> idsWithRouting = Collections.emptyList();
protected final List<RuntimeField> runtimeFields = new ArrayList<>();
@Nullable protected List<IndexBoost> indicesBoost;
public BaseQuery() {}
@@ -86,7 +87,9 @@ public class BaseQuery implements Query {
this.preference = builder.getPreference();
this.sourceFilter = builder.getSourceFilter();
this.fields = builder.getFields();
// #1973 add the other fields to the builder
this.highlightQuery = builder.highlightQuery;
this.route = builder.getRoute();
this.indicesBoost = builder.getIndicesBoost();
}
@Override
@@ -431,4 +434,10 @@ public class BaseQuery implements Query {
public List<RuntimeField> getRuntimeFields() {
return runtimeFields;
}
@Override
@Nullable
public List<IndexBoost> getIndicesBoost() {
return indicesBoost;
}
}
@@ -44,6 +44,9 @@ public abstract class BaseQueryBuilder<Q extends BaseQuery, SELF extends BaseQue
@Nullable private String preference;
@Nullable private SourceFilter sourceFilter;
private List<String> fields = new ArrayList<>();
@Nullable protected HighlightQuery highlightQuery;
@Nullable private String route;
@Nullable private List<IndexBoost> indicesBoost;
@Nullable
public Pageable getPageable() {
@@ -92,6 +95,21 @@ public abstract class BaseQueryBuilder<Q extends BaseQuery, SELF extends BaseQue
return fields;
}
@Nullable
public HighlightQuery getHighlightQuery() {
return highlightQuery;
}
@Nullable
public String getRoute() {
return route;
}
@Nullable
public List<IndexBoost> getIndicesBoost() {
return indicesBoost;
}
public SELF withPageable(Pageable pageable) {
this.pageable = pageable;
return self();
@@ -156,6 +174,26 @@ public abstract class BaseQueryBuilder<Q extends BaseQuery, SELF extends BaseQue
return self();
}
public SELF withHighlightQuery(HighlightQuery highlightQuery) {
this.highlightQuery = highlightQuery;
return self();
}
public SELF withRoute(String route) {
this.route = route;
return self();
}
public SELF withIndicesBoost(List<IndexBoost> indicesBoost) {
this.indicesBoost = indicesBoost;
return self();
}
public SELF withIndicesBoost(IndexBoost... indicesBoost) {
this.indicesBoost = Arrays.asList(indicesBoost);
return self();
}
public abstract Q build();
private SELF self() {
@@ -55,7 +55,6 @@ public class NativeSearchQuery extends BaseQuery {
@Nullable private List<PipelineAggregationBuilder> pipelineAggregations;
@Nullable private HighlightBuilder highlightBuilder;
@Nullable private HighlightBuilder.Field[] highlightFields;
@Nullable private List<IndexBoost> indicesBoost;
@Nullable private SearchTemplateRequestBuilder searchTemplate;
@Nullable private SuggestBuilder suggestBuilder;
@Nullable private List<SearchExtBuilder> searchExtBuilders;
@@ -182,11 +181,6 @@ public class NativeSearchQuery extends BaseQuery {
this.pipelineAggregations = pipelineAggregationBuilders;
}
@Nullable
public List<IndexBoost> getIndicesBoost() {
return indicesBoost;
}
public void setIndicesBoost(List<IndexBoost> indicesBoost) {
this.indicesBoost = indicesBoost;
}
@@ -63,9 +63,7 @@ public class NativeSearchQueryBuilder extends BaseQueryBuilder<NativeSearchQuery
@Nullable private List<HighlightBuilder.Field> highlightFields = new ArrayList<>();
@Nullable protected List<String> storedFields;
@Nullable private CollapseBuilder collapseBuilder;
@Nullable private List<IndexBoost> indicesBoost = new ArrayList<>();
@Nullable private SearchTemplateRequestBuilder searchTemplateBuilder;
@Nullable private String route;
@Nullable private SearchType searchType;
@Nullable private Boolean trackTotalHits;
@Nullable private Duration timeout;
@@ -195,19 +193,6 @@ public class NativeSearchQueryBuilder extends BaseQueryBuilder<NativeSearchQuery
return this;
}
public NativeSearchQueryBuilder withIndicesBoost(Collection<IndexBoost> indicesBoost) {
this.indicesBoost.addAll(indicesBoost);
return this;
}
/**
* @since 4.3
*/
public NativeSearchQueryBuilder withIndicesBoost(IndexBoost... indicesBoost) {
Collections.addAll(this.indicesBoost, indicesBoost);
return this;
}
public NativeSearchQueryBuilder withSearchTemplate(SearchTemplateRequestBuilder searchTemplateBuilder) {
this.searchTemplateBuilder = searchTemplateBuilder;
return this;
@@ -232,11 +217,6 @@ public class NativeSearchQueryBuilder extends BaseQueryBuilder<NativeSearchQuery
return this;
}
public NativeSearchQueryBuilder withRoute(String route) {
this.route = route;
return this;
}
public NativeSearchQueryBuilder withSearchType(SearchType searchType) {
this.searchType = searchType;
return this;
@@ -296,10 +276,6 @@ public class NativeSearchQueryBuilder extends BaseQueryBuilder<NativeSearchQuery
nativeSearchQuery.setStoredFields(storedFields);
}
if (indicesBoost != null) {
nativeSearchQuery.setIndicesBoost(indicesBoost);
}
if (searchTemplateBuilder != null) {
nativeSearchQuery.setSearchTemplate(searchTemplateBuilder);
}
@@ -320,10 +296,6 @@ public class NativeSearchQueryBuilder extends BaseQueryBuilder<NativeSearchQuery
nativeSearchQuery.setPipelineAggregations(pipelineAggregationBuilders);
}
if (route != null) {
nativeSearchQuery.setRoute(route);
}
if (searchType != null) {
nativeSearchQuery.setSearchType(Query.SearchType.valueOf(searchType.name()));
}
@@ -433,6 +433,12 @@ public interface Query {
*/
List<RuntimeField> getRuntimeFields();
/**
* @since 4.4
*/
@Nullable
List<IndexBoost> getIndicesBoost();
/**
* @since 4.3
*/
@@ -0,0 +1,70 @@
/*
* Copyright 2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.core.query;
import java.util.Map;
import org.springframework.data.elasticsearch.core.ScriptType;
import org.springframework.lang.Nullable;
/**
* value class combining script information.
*
* @author Peter-Josef Meisch
* @since 4.4
*/
public final class ScriptData {
@Nullable private final ScriptType type;
@Nullable private final String language;
@Nullable private final String script;
@Nullable private final String scriptName;
@Nullable private final Map<String, Object> params;
public ScriptData(@Nullable ScriptType type, @Nullable String language, @Nullable String script,
@Nullable String scriptName, @Nullable Map<String, Object> params) {
this.type = type;
this.language = language;
this.script = script;
this.scriptName = scriptName;
this.params = params;
}
@Nullable
public ScriptType getType() {
return type;
}
@Nullable
public String getLanguage() {
return language;
}
@Nullable
public String getScript() {
return script;
}
@Nullable
public String getScriptName() {
return scriptName;
}
@Nullable
public Map<String, Object> getParams() {
return params;
}
}
@@ -0,0 +1,45 @@
/*
* Copyright 2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.core.query;
import org.springframework.util.Assert;
/**
* @author Peter-Josef Meisch
* @since 4.4
*/
public class ScriptedField {
private final String fieldName;
private final ScriptData scriptData;
public ScriptedField(String fieldName, ScriptData scriptData) {
Assert.notNull(fieldName, "fieldName must not be null");
Assert.notNull(scriptData, "scriptData must not be null");
this.fieldName = fieldName;
this.scriptData = scriptData;
}
public String getFieldName() {
return fieldName;
}
public ScriptData getScriptData() {
return scriptData;
}
}
@@ -35,11 +35,8 @@ import org.springframework.lang.Nullable;
public class UpdateQuery {
private final String id;
@Nullable private final String script;
@Nullable private final Map<String, Object> params;
@Nullable private final Document document;
@Nullable private final Document upsert;
@Nullable private final String lang;
@Nullable private final String routing;
@Nullable private final Boolean scriptedUpsert;
@Nullable private final Boolean docAsUpsert;
@@ -61,9 +58,8 @@ public class UpdateQuery {
@Nullable private final Float requestsPerSecond;
@Nullable private final Boolean shouldStoreResult;
@Nullable private final Integer slices;
@Nullable private final ScriptType scriptType;
@Nullable private final String scriptName;
@Nullable private final String indexName;
@Nullable private final ScriptData scriptData;
public static Builder builder(String id) {
return new Builder(id);
@@ -85,11 +81,8 @@ public class UpdateQuery {
@Nullable String scriptName, @Nullable String indexName) {
this.id = id;
this.script = script;
this.params = params;
this.document = document;
this.upsert = upsert;
this.lang = lang;
this.routing = routing;
this.scriptedUpsert = scriptedUpsert;
this.docAsUpsert = docAsUpsert;
@@ -111,9 +104,13 @@ public class UpdateQuery {
this.requestsPerSecond = requestsPerSecond;
this.shouldStoreResult = shouldStoreResult;
this.slices = slices;
this.scriptType = scriptType;
this.scriptName = scriptName;
this.indexName = indexName;
if (scriptType != null || lang != null || script != null || scriptName != null || params != null) {
this.scriptData = new ScriptData(scriptType, lang, script, scriptName, params);
} else {
this.scriptData = null;
}
}
public String getId() {
@@ -122,12 +119,12 @@ public class UpdateQuery {
@Nullable
public String getScript() {
return script;
return scriptData != null ? scriptData.getScript() : null;
}
@Nullable
public Map<String, Object> getParams() {
return params;
return scriptData != null ? scriptData.getParams() : null;
}
@Nullable
@@ -142,7 +139,7 @@ public class UpdateQuery {
@Nullable
public String getLang() {
return lang;
return scriptData != null ? scriptData.getLanguage() : null;
}
@Nullable
@@ -252,12 +249,12 @@ public class UpdateQuery {
@Nullable
public ScriptType getScriptType() {
return scriptType;
return scriptData != null ? scriptData.getType() : null;
}
@Nullable
public String getScriptName() {
return scriptName;
return scriptData != null ? scriptData.getScriptName() : null;
}
/**
@@ -268,13 +265,21 @@ public class UpdateQuery {
return indexName;
}
/**
* @since 4.4
*/
@Nullable
public ScriptData getScriptData() {
return scriptData;
}
public static final class Builder {
private String id;
@Nullable private String script = null;
@Nullable private Map<String, Object> params;
@Nullable private Document document = null;
@Nullable private Document upsert = null;
@Nullable private String lang = "painless";
@Nullable private String lang = null;
@Nullable private String routing = null;
@Nullable private Boolean scriptedUpsert;
@Nullable private Boolean docAsUpsert;
@@ -35,6 +35,13 @@ public class UpdateResponse {
this.result = result;
}
/**
* @since 4.4
*/
public static UpdateResponse of(Result result) {
return new UpdateResponse(result);
}
public Result getResult() {
return result;
}
@@ -30,6 +30,17 @@ public class Highlight {
private final HighlightParameters parameters;
private final List<HighlightField> fields;
/**
* @since 4.4
*/
public Highlight(List<HighlightField> fields) {
Assert.notNull(fields, "fields must not be null");
this.parameters = HighlightParameters.builder().build();
this.fields = fields;
}
public Highlight(HighlightParameters parameters, List<HighlightField> fields) {
Assert.notNull(parameters, "parameters must not be null");
@@ -25,6 +25,17 @@ public class HighlightField {
private final String name;
private final HighlightFieldParameters parameters;
/**
* @since 4.4
*/
public HighlightField(String name) {
Assert.notNull(name, "name must not be null");
this.name = name;
this.parameters = HighlightFieldParameters.builder().build();
}
public HighlightField(String name, HighlightFieldParameters parameters) {
Assert.notNull(name, "name must not be null");
+5 -1
View File
@@ -1,4 +1,4 @@
Spring Data Elasticsearch 4.4 M3 (2021.2.0)
Spring Data Elasticsearch 4.4.1 (2021.2.1)
Copyright (c) [2013-2021] Pivotal Software, Inc.
This product is licensed to you under the Apache License, Version 2.0 (the "License").
@@ -30,6 +30,10 @@ conditions of the subcomponent's license, as noted in the LICENSE file.
+1 -1
View File
@@ -1,2 +1,2 @@
version.spring-data-elasticsearch=${project.version}
version.elasticsearch-client=${elasticsearch}
version.elasticsearch-client=${elasticsearch-rhlc}
@@ -0,0 +1,57 @@
/*
// * Copyright 2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch;
import static org.springframework.data.elasticsearch.client.elc.QueryBuilders.*;
import co.elastic.clients.elasticsearch._types.aggregations.Aggregation;
import org.springframework.data.elasticsearch.client.elc.NativeQuery;
import org.springframework.data.elasticsearch.client.elc.QueryBuilders;
import org.springframework.data.elasticsearch.core.query.BaseQueryBuilder;
import org.springframework.data.elasticsearch.core.query.Query;
/**
* Class providing some queries for the new Elasticsearch client needed in different tests.
*
* @author Peter-Josef Meisch
* @since 4.4
*/
public final class ELCQueries {
private ELCQueries() {}
public static Query getTermsAggsQuery(String aggsName, String aggsField){
return NativeQuery.builder() //
.withQuery(QueryBuilders.matchAllQueryAsQuery()) //
.withAggregation(aggsName, Aggregation.of(a -> a //
.terms(ta -> ta.field(aggsField)))) //
.withMaxResults(0) //
.build();
}
public static Query queryWithIds(String... ids) {
return NativeQuery.builder().withIds(ids).build();
}
public static BaseQueryBuilder<?, ?> getBuilderWithMatchAllQuery() {
return NativeQuery.builder().withQuery(matchAllQueryAsQuery());
}
public static BaseQueryBuilder<?, ?> getBuilderWithTermQuery(String field, String value) {
return NativeQuery.builder().withQuery(termQueryAsQuery(field, value));
}
}
@@ -16,7 +16,7 @@
package org.springframework.data.elasticsearch;
/**
* TODO #1973 remove when the new Elasticsearch client is fully working
* TODO remove when the new Elasticsearch client is fully working
*
* @author Peter-Josef Meisch
*/
@@ -25,7 +25,7 @@ import co.elastic.clients.elasticsearch.cluster.HealthResponse;
import co.elastic.clients.elasticsearch.core.IndexRequest;
import co.elastic.clients.elasticsearch.core.IndexResponse;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.ResponseBody;
import co.elastic.clients.elasticsearch.indices.ElasticsearchIndicesClient;
import co.elastic.clients.elasticsearch.indices.IndexSettings;
import co.elastic.clients.transport.ElasticsearchTransport;
@@ -300,7 +300,7 @@ public class DevTests {
.query(query -> query.match(matchQuery -> matchQuery.field("content").query(FieldValue.of("content1"))))
.build();
SearchResponse<EntityAsMap> searchResponse = null;
ResponseBody<EntityAsMap> searchResponse = null;
try {
searchResponse = searchImperative(searchRequest);
assertThat(searchResponse).isNotNull();
@@ -316,11 +316,11 @@ public class DevTests {
}
}
private SearchResponse<EntityAsMap> searchImperative(SearchRequest searchRequest) throws IOException {
private ResponseBody<EntityAsMap> searchImperative(SearchRequest searchRequest) throws IOException {
return imperativeElasticsearchClient.search(searchRequest, EntityAsMap.class);
}
private SearchResponse<EntityAsMap> searchReactive(SearchRequest searchRequest) {
private ResponseBody<EntityAsMap> searchReactive(SearchRequest searchRequest) {
return Objects.requireNonNull(reactiveElasticsearchClient.search(searchRequest, EntityAsMap.class).block());
}
// endregion
@@ -20,12 +20,15 @@ import co.elastic.clients.json.JsonData;
import co.elastic.clients.json.JsonpMapper;
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.assertj.core.api.SoftAssertions;
import org.assertj.core.data.Offset;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.data.elasticsearch.core.document.Explanation;
import org.springframework.data.elasticsearch.core.document.SearchDocument;
/**
@@ -106,4 +109,53 @@ class DocumentAdaptersUnitTests {
softly.assertAll();
}
@Test // #725 #1973
@DisplayName("should adapt returned explanations")
void shouldAdaptReturnedExplanations() {
Hit<EntityAsMap> searchHit = new Hit.Builder<EntityAsMap>() //
.index("index") //
.id("42") //
.explanation(eb -> eb //
.value(3.14f) //
.description("explanation 3.14") //
.details(edb -> edb.description("explanation noMatch").value(0f)))
.build();
SearchDocument searchDocument = DocumentAdapters.from(searchHit, jsonpMapper);
SoftAssertions softly = new SoftAssertions();
Explanation explanation = searchDocument.getExplanation();
softly.assertThat(explanation).isNotNull();
softly.assertThat(explanation.isMatch()).isTrue();
softly.assertThat(explanation.getValue()).isCloseTo(3.14, Offset.offset(0.001));
softly.assertThat(explanation.getDescription()).isEqualTo("explanation 3.14");
List<Explanation> details = explanation.getDetails();
softly.assertThat(details)
.containsExactly(new Explanation(null, 0.0, "explanation noMatch", Collections.emptyList()));
softly.assertAll();
}
@Test // DATAES-979 #1973
@DisplayName("should adapt returned matched queries")
void shouldAdaptReturnedMatchedQueries() {
Hit<EntityAsMap> searchHit = new Hit.Builder<EntityAsMap>() //
.index("index") //
.id("42") //
.matchedQueries("query1", "query2") //
.build();
SearchDocument searchDocument = DocumentAdapters.from(searchHit, jsonpMapper);
SoftAssertions softly = new SoftAssertions();
List<String> matchedQueries = searchDocument.getMatchedQueries();
softly.assertThat(matchedQueries).isNotNull();
softly.assertThat(matchedQueries).hasSize(2);
softly.assertThat(matchedQueries).isEqualTo(Arrays.asList("query1", "query2"));
softly.assertAll();
}
}
@@ -18,16 +18,29 @@ package org.springframework.data.elasticsearch.core;
import static org.springframework.data.elasticsearch.client.elc.QueryBuilders.*;
import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.FunctionBoostMode;
import co.elastic.clients.elasticsearch._types.query_dsl.FunctionScoreMode;
import co.elastic.clients.elasticsearch.core.search.FieldCollapse;
import co.elastic.clients.json.JsonData;
import java.util.Map;
import org.junit.jupiter.api.DisplayName;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.elasticsearch.ELCQueries;
import org.springframework.data.elasticsearch.client.elc.NativeQuery;
import org.springframework.data.elasticsearch.client.elc.NativeQueryBuilder;
import org.springframework.data.elasticsearch.core.query.BaseQueryBuilder;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilterBuilder;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.core.query.RescorerQuery;
import org.springframework.data.elasticsearch.core.query.ScriptData;
import org.springframework.data.elasticsearch.core.query.ScriptedField;
import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration;
import org.springframework.data.elasticsearch.utils.IndexNameProvider;
import org.springframework.lang.Nullable;
import org.springframework.test.context.ContextConfiguration;
/**
@@ -54,7 +67,7 @@ public class ElasticsearchELCIntegrationTests extends ElasticsearchIntegrationTe
@Override
protected Query queryWithIds(String... ids) {
return NativeQuery.builder().withIds(ids).build();
return ELCQueries.queryWithIds(ids);
}
@Override
@@ -64,7 +77,7 @@ public class ElasticsearchELCIntegrationTests extends ElasticsearchIntegrationTe
@Override
protected BaseQueryBuilder<?, ?> getBuilderWithMatchAllQuery() {
return NativeQuery.builder().withQuery(matchAllQueryAsQuery());
return ELCQueries.getBuilderWithMatchAllQuery();
}
@Override
@@ -93,4 +106,101 @@ public class ElasticsearchELCIntegrationTests extends ElasticsearchIntegrationTe
.withMinScore(minScore) //
.build();
}
@Override
protected Query getQueryWithCollapse(String collapseField, @Nullable String innerHits, @Nullable Integer size) {
return NativeQuery.builder() //
.withQuery(matchAllQueryAsQuery()) //
.withFieldCollapse(FieldCollapse.of(fc -> {
fc.field(collapseField);
if (innerHits != null) {
fc.innerHits(ih -> ih.name(innerHits).size(size));
}
return fc;
})).build();
}
@Override
protected Query getMatchAllQueryWithFilterForId(String id) {
return NativeQuery.builder() //
.withQuery(matchAllQueryAsQuery()) //
.withFilter(termQueryAsQuery("id", id)) //
.build();
}
@Override
protected Query getQueryForParentId(String type, String id, @Nullable String route) {
NativeQueryBuilder queryBuilder = NativeQuery.builder() //
.withQuery(qb -> qb //
.parentId(p -> p.type(type).id(id)) //
);
if (route != null) {
queryBuilder.withRoute(route);
}
return queryBuilder.build();
}
@Override
protected Query getMatchAllQueryWithIncludesAndInlineExpressionScript(@Nullable String includes, String fieldName,
String script, Map<String, Object> params) {
NativeQueryBuilder nativeQueryBuilder = NativeQuery.builder().withQuery(matchAllQueryAsQuery());
if (includes != null) {
nativeQueryBuilder.withSourceFilter(new FetchSourceFilterBuilder().withIncludes(includes).build());
}
return nativeQueryBuilder.withScriptedField(new ScriptedField( //
fieldName, //
new ScriptData(ScriptType.INLINE, "expression", script, null, params))) //
.build();
}
@Override
protected Query getQueryWithRescorer() {
return NativeQuery.builder() //
.withQuery(q -> q //
.bool(b -> b //
.filter(f -> f.exists(e -> e.field("rate"))) //
.should(s -> s.term(t -> t.field("message").value("message"))) //
)) //
.withResorerQuery( //
new RescorerQuery(NativeQuery.builder() //
.withQuery(q -> q //
.functionScore(fs -> fs //
.functions(f1 -> f1 //
.filter(matchAllQueryAsQuery()) //
.weight(1.0) //
.gauss(d -> d //
.field("rate") //
.placement(dp -> dp //
.origin(JsonData.of(0)) //
.scale(JsonData.of(10)) //
.decay(0.5)) //
)) //
.functions(f2 -> f2 //
.filter(matchAllQueryAsQuery()).weight(100.0) //
.gauss(d -> d //
.field("rate") //
.placement(dp -> dp //
.origin(JsonData.of(0)) //
.scale(JsonData.of(10)) //
.decay(0.5)) //
)) //
.scoreMode(FunctionScoreMode.Sum) //
.maxBoost(80.0) //
.boostMode(FunctionBoostMode.Replace)) //
) //
.build() //
) //
.withScoreMode(RescorerQuery.ScoreMode.Max) //
.withWindowSize(100)) //
.build();
}
}
@@ -27,8 +27,17 @@ import java.util.Map;
import org.elasticsearch.action.support.ActiveShardCount;
import org.elasticsearch.action.support.WriteRequest;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.common.lucene.search.function.CombineFunction;
import org.elasticsearch.common.lucene.search.function.FunctionScoreQuery;
import org.elasticsearch.core.TimeValue;
import org.elasticsearch.index.query.InnerHitBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.index.query.functionscore.GaussDecayFunctionBuilder;
import org.elasticsearch.index.reindex.UpdateByQueryRequest;
import org.elasticsearch.join.query.ParentIdQueryBuilder;
import org.elasticsearch.script.Script;
import org.elasticsearch.search.collapse.CollapseBuilder;
import org.elasticsearch.search.fetch.subphase.FetchSourceContext;
import org.json.JSONException;
import org.junit.jupiter.api.DisplayName;
@@ -38,12 +47,16 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.BaseQueryBuilder;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilterBuilder;
import org.springframework.data.elasticsearch.core.query.IndicesOptions;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.core.query.RescorerQuery;
import org.springframework.data.elasticsearch.core.query.ScriptField;
import org.springframework.data.elasticsearch.core.query.UpdateQuery;
import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration;
import org.springframework.data.elasticsearch.utils.IndexNameProvider;
import org.springframework.lang.Nullable;
import org.springframework.test.context.ContextConfiguration;
/**
@@ -115,6 +128,79 @@ public class ElasticsearchERHLCIntegrationTests extends ElasticsearchIntegration
.withMinScore(minScore).build();
}
@Override
protected Query getQueryWithCollapse(String collapseField, @Nullable String innerHits, @Nullable Integer size) {
CollapseBuilder collapseBuilder = new CollapseBuilder(collapseField);
if (innerHits != null) {
InnerHitBuilder innerHitBuilder = new InnerHitBuilder(innerHits);
if (size != null) {
innerHitBuilder.setSize(size);
}
collapseBuilder.setInnerHits(innerHitBuilder);
}
return new NativeSearchQueryBuilder().withQuery(matchAllQuery()).withCollapseBuilder(collapseBuilder).build();
}
@Override
protected Query getMatchAllQueryWithFilterForId(String id) {
return new NativeSearchQueryBuilder().withQuery(matchAllQuery()).withFilter(boolQuery().filter(termQuery("id", id)))
.build();
}
@Override
protected Query getQueryForParentId(String type, String id, @Nullable String route) {
NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder()
.withQuery(new ParentIdQueryBuilder(type, id));
if (route != null) {
queryBuilder.withRoute(route);
}
return queryBuilder.build();
}
@Override
protected Query getMatchAllQueryWithIncludesAndInlineExpressionScript(@Nullable String includes, String fieldName,
String script, Map<String, java.lang.Object> params) {
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder().withQuery(matchAllQuery());
if (includes != null) {
nativeSearchQueryBuilder.withSourceFilter(new FetchSourceFilterBuilder().withIncludes(includes).build());
}
return nativeSearchQueryBuilder.withScriptField(new ScriptField(fieldName,
new Script(org.elasticsearch.script.ScriptType.INLINE, "expression", script, params))).build();
}
@Override
protected Query getQueryWithRescorer() {
return new NativeSearchQueryBuilder() //
.withQuery( //
boolQuery() //
.filter(existsQuery("rate")) //
.should(termQuery("message", "message"))) //
.withRescorerQuery( //
new RescorerQuery( //
new NativeSearchQueryBuilder() //
.withQuery(QueryBuilders
.functionScoreQuery(new FunctionScoreQueryBuilder.FilterFunctionBuilder[] {
new FunctionScoreQueryBuilder.FilterFunctionBuilder(
new GaussDecayFunctionBuilder("rate", 0, 10, null, 0.5).setWeight(1f)),
new FunctionScoreQueryBuilder.FilterFunctionBuilder(
new GaussDecayFunctionBuilder("rate", 0, 10, null, 0.5).setWeight(100f)) }) //
.scoreMode(FunctionScoreQuery.ScoreMode.SUM) //
.maxBoost(80f) //
.boostMode(CombineFunction.REPLACE)) //
.build())//
.withScoreMode(RescorerQuery.ScoreMode.Max) //
.withWindowSize(100)) //
.build();
}
@Test // DATAES-768
void shouldUseAllOptionsFromUpdateQuery() {
Map<String, Object> doc = new HashMap<>();
@@ -36,19 +36,6 @@ import java.util.stream.IntStream;
import org.assertj.core.api.SoftAssertions;
import org.assertj.core.util.Lists;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.common.lucene.search.function.CombineFunction;
import org.elasticsearch.common.lucene.search.function.FunctionScoreQuery;
import org.elasticsearch.index.query.InnerHitBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder.FilterFunctionBuilder;
import org.elasticsearch.index.query.functionscore.GaussDecayFunctionBuilder;
import org.elasticsearch.join.query.ParentIdQueryBuilder;
import org.elasticsearch.script.Script;
import org.elasticsearch.script.ScriptType;
import org.elasticsearch.search.collapse.CollapseBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Order;
@@ -82,7 +69,8 @@ import org.springframework.data.elasticsearch.core.index.Settings;
import org.springframework.data.elasticsearch.core.join.JoinField;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.*;
import org.springframework.data.elasticsearch.core.query.RescorerQuery.ScoreMode;
import org.springframework.data.elasticsearch.core.query.highlight.Highlight;
import org.springframework.data.elasticsearch.core.query.highlight.HighlightField;
import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest;
import org.springframework.data.elasticsearch.utils.IndexNameProvider;
import org.springframework.data.util.StreamUtils;
@@ -170,7 +158,23 @@ public abstract class ElasticsearchIntegrationTests implements NewElasticsearchC
protected abstract BaseQueryBuilder<?, ?> getBuilderWithWildcardQuery(String field, String value);
@DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation
protected abstract Query getQueryWithCollapse(String collapseField, @Nullable String innerHits,
@Nullable Integer size);
protected abstract Query getMatchAllQueryWithFilterForId(String id);
protected abstract Query getQueryForParentId(String type, String id, @Nullable String route);
protected Query getMatchAllQueryWithInlineExpressionScript(String fieldName, String script,
Map<String, java.lang.Object> params) {
return getMatchAllQueryWithIncludesAndInlineExpressionScript(null, fieldName, script, params);
}
protected abstract Query getMatchAllQueryWithIncludesAndInlineExpressionScript(@Nullable String includes,
String fieldName, String script, Map<String, java.lang.Object> params);
protected abstract Query getQueryWithRescorer();
@Test
public void shouldThrowDataAccessExceptionIfDocumentDoesNotExistWhileDoingPartialUpdate() {
@@ -451,7 +455,6 @@ public abstract class ElasticsearchIntegrationTests implements NewElasticsearchC
assertThat(searchHits.getTotalHits()).isEqualTo(2);
}
@DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation
@Test
public void shouldDoBulkUpdate() {
@@ -618,11 +621,9 @@ public abstract class ElasticsearchIntegrationTests implements NewElasticsearchC
assertThat(operations.count(searchQuery, IndexCoordinates.of("test-index-*"))).isEqualTo(2);
}
@DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation
@Test
public void shouldFilterSearchResultsForGivenFilter() {
// given
String documentId = nextIdAsString();
SampleEntity sampleEntity = SampleEntity.builder().id(documentId).message("some message")
.version(System.currentTimeMillis()).build();
@@ -630,14 +631,11 @@ public abstract class ElasticsearchIntegrationTests implements NewElasticsearchC
IndexQuery indexQuery = getIndexQuery(sampleEntity);
operations.index(indexQuery, IndexCoordinates.of(indexNameProvider.indexName()));
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery())
.withFilter(boolQuery().filter(termQuery("id", documentId))).build();
Query query = getMatchAllQueryWithFilterForId(documentId);
// when
SearchHits<SampleEntity> searchHits = operations.search(searchQuery, SampleEntity.class,
SearchHits<SampleEntity> searchHits = operations.search(query, SampleEntity.class,
IndexCoordinates.of(indexNameProvider.indexName()));
// then
assertThat(searchHits.getTotalHits()).isEqualTo(1);
}
@@ -825,7 +823,6 @@ public abstract class ElasticsearchIntegrationTests implements NewElasticsearchC
assertThat(searchHits.getTotalHits()).isEqualTo(1);
}
@DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation
@Test
public void shouldUseScriptedFields() {
@@ -847,10 +844,8 @@ public abstract class ElasticsearchIntegrationTests implements NewElasticsearchC
params.put("factor", 2);
// when
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).withScriptField(
new ScriptField("scriptedRate", new Script(ScriptType.INLINE, "expression", "doc['rate'] * factor", params)))
.build();
SearchHits<SampleEntity> searchHits = operations.search(searchQuery, SampleEntity.class,
Query query = getMatchAllQueryWithInlineExpressionScript("scriptedRate", "doc['rate'] * factor", params);
SearchHits<SampleEntity> searchHits = operations.search(query, SampleEntity.class,
IndexCoordinates.of(indexNameProvider.indexName()));
// then
@@ -1502,7 +1497,6 @@ public abstract class ElasticsearchIntegrationTests implements NewElasticsearchC
assertThat(indexOperations.exists()).isFalse();
}
@DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation
@Test
public void shouldDoPartialUpdateForExistingDocument() {
@@ -1534,10 +1528,9 @@ public abstract class ElasticsearchIntegrationTests implements NewElasticsearchC
assertThat(indexedEntity.getMessage()).isEqualTo(messageAfterUpdate);
}
@DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation
@Test
void shouldDoUpdateByQueryForExistingDocument() {
// given
final String documentId = nextIdAsString();
final String messageBeforeUpdate = "some test message";
final String messageAfterUpdate = "test message";
@@ -1549,7 +1542,7 @@ public abstract class ElasticsearchIntegrationTests implements NewElasticsearchC
operations.index(indexQuery, IndexCoordinates.of(indexNameProvider.indexName()));
final NativeSearchQuery query = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).build();
final Query query = operations.matchAllQuery();
final UpdateQuery updateQuery = UpdateQuery.builder(query)
.withScriptType(org.springframework.data.elasticsearch.core.ScriptType.INLINE)
@@ -1557,42 +1550,15 @@ public abstract class ElasticsearchIntegrationTests implements NewElasticsearchC
.withParams(Collections.singletonMap("newMessage", messageAfterUpdate)).withAbortOnVersionConflict(true)
.build();
// when
operations.updateByQuery(updateQuery, IndexCoordinates.of(indexNameProvider.indexName()));
// then
SampleEntity indexedEntity = operations.get(documentId, SampleEntity.class,
IndexCoordinates.of(indexNameProvider.indexName()));
assertThat(indexedEntity.getMessage()).isEqualTo(messageAfterUpdate);
}
@DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation
@Test // DATAES-227
public void shouldUseUpsertOnUpdate() {
// given
Map<String, Object> doc = new HashMap<>();
doc.put("id", "1");
doc.put("message", "test");
org.springframework.data.elasticsearch.core.document.Document document = org.springframework.data.elasticsearch.core.document.Document
.from(doc);
UpdateQuery updateQuery = UpdateQuery.builder("1") //
.withDocument(document) //
.withUpsert(document) //
.build();
// when
UpdateRequest request = getRequestFactory().updateRequest(updateQuery, IndexCoordinates.of("index"));
// then
assertThat(request).isNotNull();
assertThat(request.upsertRequest()).isNotNull();
}
@DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation
@Test
public void shouldDoUpsertIfDocumentDoesNotExist() {
@@ -1650,63 +1616,57 @@ public abstract class ElasticsearchIntegrationTests implements NewElasticsearchC
assertThat(entities.size()).isGreaterThanOrEqualTo(1);
}
@DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation
@DisabledIf(value = "newElasticsearchClient",
disabledReason = "todo #2138 can't check response, open ES issue 161 that does not allow seqno")
// and version to be set in the request
@Test // DATAES-487
public void shouldReturnSameEntityForMultiSearch() {
// given
List<IndexQuery> indexQueries = new ArrayList<>();
indexQueries.add(buildIndex(SampleEntity.builder().id("1").message("ab").build()));
indexQueries.add(buildIndex(SampleEntity.builder().id("2").message("bc").build()));
indexQueries.add(buildIndex(SampleEntity.builder().id("3").message("ac").build()));
operations.bulkIndex(indexQueries, IndexCoordinates.of(indexNameProvider.indexName()));
List<Query> queries = new ArrayList<>();
queries.add(getTermQuery("message", "ab"));
queries.add(getTermQuery("message", "bc"));
queries.add(getTermQuery("message", "ac"));
// when
List<NativeSearchQuery> queries = new ArrayList<>();
queries.add(new NativeSearchQueryBuilder().withQuery(termQuery("message", "ab")).build());
queries.add(new NativeSearchQueryBuilder().withQuery(termQuery("message", "bc")).build());
queries.add(new NativeSearchQueryBuilder().withQuery(termQuery("message", "ac")).build());
// then
List<SearchHits<SampleEntity>> searchHits = operations.multiSearch(queries, SampleEntity.class,
IndexCoordinates.of(indexNameProvider.indexName()));
for (SearchHits<SampleEntity> sampleEntity : searchHits) {
assertThat(sampleEntity.getTotalHits()).isEqualTo(1);
}
}
@DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation
@DisabledIf(value = "newElasticsearchClient",
disabledReason = "todo #2138 can't check response, open ES issue 161 that does not allow seqno")
// and version to be set in the request
@Test // DATAES-487
public void shouldReturnDifferentEntityForMultiSearch() {
// given
Class<Book> clazz = Book.class;
IndexOperations bookIndexOperations = operations.indexOps(Book.class);
bookIndexOperations.delete();
bookIndexOperations.create();
indexOperations.putMapping(clazz);
bookIndexOperations.createWithMapping();
bookIndexOperations.refresh();
IndexCoordinates bookIndex = IndexCoordinates.of("test-index-book-core-template");
IndexCoordinates bookIndex = IndexCoordinates.of("i-need-my-own-index");
operations.index(buildIndex(SampleEntity.builder().id("1").message("ab").build()),
IndexCoordinates.of(indexNameProvider.indexName()));
operations.index(buildIndex(Book.builder().id("2").description("bc").build()), bookIndex);
bookIndexOperations.refresh();
// when
List<NativeSearchQuery> queries = new ArrayList<>();
queries.add(new NativeSearchQueryBuilder().withQuery(termQuery("message", "ab")).build());
queries.add(new NativeSearchQueryBuilder().withQuery(termQuery("description", "bc")).build());
List<Query> queries = new ArrayList<>();
queries.add(getTermQuery("message", "ab"));
queries.add(getTermQuery("description", "bc"));
List<SearchHits<?>> searchHitsList = operations.multiSearch(queries, Lists.newArrayList(SampleEntity.class, clazz),
IndexCoordinates.of(indexNameProvider.indexName(), bookIndex.getIndexName()));
// then
bookIndexOperations.delete();
SearchHits<?> searchHits0 = searchHitsList.get(0);
assertThat(searchHits0.getTotalHits()).isEqualTo(1L);
SearchHit<SampleEntity> searchHit0 = (SearchHit<SampleEntity>) searchHits0.getSearchHit(0);
@@ -1950,7 +1910,7 @@ public abstract class ElasticsearchIntegrationTests implements NewElasticsearchC
@Test
public void shouldIndexSampleEntityWithIndexAtRuntime() {
String indexName = "custom-" + indexNameProvider.indexName();
String indexName = indexNameProvider.indexName() + "-custom";
// given
String documentId = nextIdAsString();
@@ -2699,7 +2659,6 @@ public abstract class ElasticsearchIntegrationTests implements NewElasticsearchC
assertThat(sampleEntities.get(2).getContent().getMessage()).isEqualTo(sampleEntity1.getMessage());
}
@DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation
@Test // DATAES-593
public void shouldReturnDocumentWithCollapsedField() {
@@ -2715,11 +2674,9 @@ public abstract class ElasticsearchIntegrationTests implements NewElasticsearchC
operations.bulkIndex(indexQueries, IndexCoordinates.of(indexNameProvider.indexName()));
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).withCollapseField("rate")
.build();
Query query = getQueryWithCollapse("rate", null, null);
// when
SearchHits<SampleEntity> searchHits = operations.search(searchQuery, SampleEntity.class,
SearchHits<SampleEntity> searchHits = operations.search(query, SampleEntity.class,
IndexCoordinates.of(indexNameProvider.indexName()));
// then
@@ -2730,7 +2687,6 @@ public abstract class ElasticsearchIntegrationTests implements NewElasticsearchC
assertThat(searchHits.getSearchHit(1).getContent().getMessage()).isEqualTo("message 2");
}
@DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation
@Test // #1493
@DisplayName("should return document with collapse field and inner hits")
public void shouldReturnDocumentWithCollapsedFieldAndInnerHits() {
@@ -2747,11 +2703,10 @@ public abstract class ElasticsearchIntegrationTests implements NewElasticsearchC
operations.bulkIndex(indexQueries, IndexCoordinates.of(indexNameProvider.indexName()));
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery())
.withCollapseBuilder(new CollapseBuilder("rate").setInnerHits(new InnerHitBuilder("innerHits"))).build();
Query query = getQueryWithCollapse("rate", "innerHits", null);
// when
SearchHits<SampleEntity> searchHits = operations.search(searchQuery, SampleEntity.class,
SearchHits<SampleEntity> searchHits = operations.search(query, SampleEntity.class,
IndexCoordinates.of(indexNameProvider.indexName()));
// then
@@ -2764,7 +2719,7 @@ public abstract class ElasticsearchIntegrationTests implements NewElasticsearchC
assertThat(searchHits.getSearchHit(1).getInnerHits("innerHits").getTotalHits()).isEqualTo(1);
}
@DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation
@Test // #1997
@DisplayName("should return document with inner hits size zero")
void shouldReturnDocumentWithInnerHitsSizeZero() {
@@ -2777,12 +2732,10 @@ public abstract class ElasticsearchIntegrationTests implements NewElasticsearchC
operations.bulkIndex(indexQueries, IndexCoordinates.of(indexNameProvider.indexName()));
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery())
.withCollapseBuilder(new CollapseBuilder("rate").setInnerHits(new InnerHitBuilder("innerHits").setSize(0)))
.build();
Query query = getQueryWithCollapse("rate", "innerHits", 0);
// when
SearchHits<SampleEntity> searchHits = operations.search(searchQuery, SampleEntity.class,
SearchHits<SampleEntity> searchHits = operations.search(query, SampleEntity.class,
IndexCoordinates.of(indexNameProvider.indexName()));
// then
@@ -2867,7 +2820,7 @@ public abstract class ElasticsearchIntegrationTests implements NewElasticsearchC
List<Object> sortValues = searchHit.getSortValues();
assertThat(sortValues).hasSize(2);
assertThat(sortValues.get(0)).isInstanceOf(String.class).isEqualTo("thousands");
// transport client returns Long, RestHghlevelClient Integer, new ElasticsearchClient String
// transport client returns Long, RestHighlevelClient Integer, new ElasticsearchClient String
java.lang.Object o = sortValues.get(1);
if (o instanceof Integer) {
Integer i = (Integer) o;
@@ -2882,7 +2835,6 @@ public abstract class ElasticsearchIntegrationTests implements NewElasticsearchC
}
}
@DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation
@Test // DATAES-715
void shouldReturnHighlightFieldsInSearchHit() {
IndexCoordinates index = IndexCoordinates.of("test-index-highlight-entity-template");
@@ -2893,11 +2845,10 @@ public abstract class ElasticsearchIntegrationTests implements NewElasticsearchC
operations.index(indexQuery, index);
operations.indexOps(index).refresh();
NativeSearchQuery query = new NativeSearchQueryBuilder() //
.withQuery(termQuery("message", "message")) //
.withHighlightFields(new HighlightBuilder.Field("message")) //
Query query = getBuilderWithTermQuery("message", "message") //
.withHighlightQuery(
new HighlightQuery(new Highlight(singletonList(new HighlightField("message"))), HighlightEntity.class))
.build();
SearchHits<HighlightEntity> searchHits = operations.search(query, HighlightEntity.class, index);
assertThat(searchHits).isNotNull();
@@ -2910,10 +2861,9 @@ public abstract class ElasticsearchIntegrationTests implements NewElasticsearchC
assertThat(highlightField.get(1)).contains("<em>message</em>");
}
@DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation
@Test // #1686
void shouldRunRescoreQueryInSearchQuery() {
IndexCoordinates index = IndexCoordinates.of("test-index-rescore-entity-template");
IndexCoordinates index = IndexCoordinates.of(indexNameProvider.getPrefix() + "rescore-entity");
// matches main query better
SampleEntity entity = SampleEntity.builder() //
@@ -2933,17 +2883,7 @@ public abstract class ElasticsearchIntegrationTests implements NewElasticsearchC
operations.bulkIndex(indexQueries, index);
NativeSearchQuery query = new NativeSearchQueryBuilder() //
.withQuery(boolQuery().filter(existsQuery("rate")).should(termQuery("message", "message"))) //
.withRescorerQuery(
new RescorerQuery(new NativeSearchQueryBuilder().withQuery(QueryBuilders
.functionScoreQuery(new FunctionScoreQueryBuilder.FilterFunctionBuilder[] {
new FilterFunctionBuilder(new GaussDecayFunctionBuilder("rate", 0, 10, null, 0.5).setWeight(1f)),
new FilterFunctionBuilder(
new GaussDecayFunctionBuilder("rate", 0, 10, null, 0.5).setWeight(100f)) })
.scoreMode(FunctionScoreQuery.ScoreMode.SUM).maxBoost(80f).boostMode(CombineFunction.REPLACE)).build())
.withScoreMode(ScoreMode.Max).withWindowSize(100))
.build();
Query query = getQueryWithRescorer();
SearchHits<SampleEntity> searchHits = operations.search(query, SampleEntity.class, index);
@@ -2956,6 +2896,7 @@ public abstract class ElasticsearchIntegrationTests implements NewElasticsearchC
assertThat(searchHit.getScore()).isEqualTo(80f);
}
@Test
// DATAES-738
void shouldSaveEntityWithIndexCoordinates() {
@@ -3128,7 +3069,9 @@ public abstract class ElasticsearchIntegrationTests implements NewElasticsearchC
assertThatSeqNoPrimaryTermIsFilled(retrieved);
}
@DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation
@DisabledIf(value = "newElasticsearchClient",
disabledReason = "todo #2138 can't check response, open ES issue 161 that does not allow seqno")
// and version to be set in the request
@Test // DATAES-799
void multiSearchShouldReturnSeqNoPrimaryTerm() {
OptimisticEntity original = new OptimisticEntity();
@@ -3158,7 +3101,6 @@ public abstract class ElasticsearchIntegrationTests implements NewElasticsearchC
assertThatSeqNoPrimaryTermIsFilled(retrieved);
}
@DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation
@Test // DATAES-799
void shouldThrowOptimisticLockingFailureExceptionWhenConcurrentUpdateOccursOnEntityWithSeqNoPrimaryTermProperty() {
OptimisticEntity original = new OptimisticEntity();
@@ -3175,7 +3117,6 @@ public abstract class ElasticsearchIntegrationTests implements NewElasticsearchC
assertThatThrownBy(() -> operations.save(forEdit2)).isInstanceOf(OptimisticLockingFailureException.class);
}
@DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation
@Test // DATAES-799
void shouldThrowOptimisticLockingFailureExceptionWhenConcurrentUpdateOccursOnVersionedEntityWithSeqNoPrimaryTermProperty() {
OptimisticAndVersionedEntity original = new OptimisticAndVersionedEntity();
@@ -3204,7 +3145,6 @@ public abstract class ElasticsearchIntegrationTests implements NewElasticsearchC
operations.save(forEdit);
}
@DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation
@Test
void shouldSupportCRUDOpsForEntityWithJoinFields() throws Exception {
String qId1 = java.util.UUID.randomUUID().toString();
@@ -3258,9 +3198,8 @@ public abstract class ElasticsearchIntegrationTests implements NewElasticsearchC
operations.save(
Arrays.asList(sampleQuestionEntity1, sampleQuestionEntity2, sampleAnswerEntity1, sampleAnswerEntity2), index);
SearchHits<SampleJoinEntity> hits = operations.search(
new NativeSearchQueryBuilder().withQuery(new ParentIdQueryBuilder("answer", qId1)).build(),
SampleJoinEntity.class);
Query query = getQueryForParentId("answer", qId1, null);
SearchHits<SampleJoinEntity> hits = operations.search(query, SampleJoinEntity.class);
List<String> hitIds = hits.getSearchHits().stream()
.map(sampleJoinEntitySearchHit -> sampleJoinEntitySearchHit.getId()).collect(Collectors.toList());
@@ -3287,8 +3226,7 @@ public abstract class ElasticsearchIntegrationTests implements NewElasticsearchC
// when
operations.bulkUpdate(queries, IndexCoordinates.of(indexNameProvider.indexName()));
SearchHits<SampleJoinEntity> updatedHits = operations.search(
new NativeSearchQueryBuilder().withQuery(new ParentIdQueryBuilder("answer", qId2)).build(),
SearchHits<SampleJoinEntity> updatedHits = operations.search(getQueryForParentId("answer", qId2, null),
SampleJoinEntity.class);
List<String> hitIds = updatedHits.getSearchHits().stream().map(new Function<SearchHit<SampleJoinEntity>, String>() {
@@ -3300,8 +3238,7 @@ public abstract class ElasticsearchIntegrationTests implements NewElasticsearchC
assertThat(hitIds.size()).isEqualTo(1);
assertThat(hitIds.get(0)).isEqualTo(aId2);
updatedHits = operations.search(
new NativeSearchQueryBuilder().withQuery(new ParentIdQueryBuilder("answer", qId1)).build(),
updatedHits = operations.search(getQueryForParentId("answer", qId1, null),
SampleJoinEntity.class);
hitIds = updatedHits.getSearchHits().stream().map(new Function<SearchHit<SampleJoinEntity>, String>() {
@@ -3315,20 +3252,15 @@ public abstract class ElasticsearchIntegrationTests implements NewElasticsearchC
}
private void shouldDeleteEntityWithJoinFields(String qId2, String aId2) throws Exception {
Query query = new NativeSearchQueryBuilder().withQuery(new ParentIdQueryBuilder("answer", qId2)).withRoute(qId2)
.build();
operations.delete(query, SampleJoinEntity.class, IndexCoordinates.of(indexNameProvider.indexName()));
SearchHits<SampleJoinEntity> deletedHits = operations.search(
new NativeSearchQueryBuilder().withQuery(new ParentIdQueryBuilder("answer", qId2)).build(),
operations.delete(getQueryForParentId("answer", qId2, qId2), SampleJoinEntity.class,
IndexCoordinates.of(indexNameProvider.indexName()));
SearchHits<SampleJoinEntity> deletedHits = operations.search(getQueryForParentId("answer", qId2, null),
SampleJoinEntity.class);
List<String> hitIds = deletedHits.getSearchHits().stream().map(new Function<SearchHit<SampleJoinEntity>, String>() {
@Override
public String apply(SearchHit<SampleJoinEntity> sampleJoinEntitySearchHit) {
return sampleJoinEntitySearchHit.getId();
}
}).collect(Collectors.toList());
List<String> hitIds = deletedHits.getSearchHits().stream()
.map(sampleJoinEntitySearchHit -> sampleJoinEntitySearchHit.getId()).collect(Collectors.toList());
assertThat(hitIds.size()).isEqualTo(0);
}
@@ -3539,7 +3471,6 @@ public abstract class ElasticsearchIntegrationTests implements NewElasticsearchC
assertThat(retrieved).isEqualTo(saved);
}
@DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation
@Test // #1488
@DisplayName("should set scripted fields on immutable objects")
void shouldSetScriptedFieldsOnImmutableObjects() {
@@ -3549,13 +3480,10 @@ public abstract class ElasticsearchIntegrationTests implements NewElasticsearchC
Map<String, Object> params = new HashMap<>();
params.put("factor", 2);
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery())
.withSourceFilter(new FetchSourceFilterBuilder().withIncludes("*").build())
.withScriptField(new ScriptField("scriptedRate",
new Script(ScriptType.INLINE, "expression", "doc['rate'] * factor", params)))
.build();
Query query = getMatchAllQueryWithIncludesAndInlineExpressionScript("*", "scriptedRate", "doc['rate'] * factor",
params);
SearchHits<ImmutableWithScriptedEntity> searchHits = operations.search(searchQuery,
SearchHits<ImmutableWithScriptedEntity> searchHits = operations.search(query,
ImmutableWithScriptedEntity.class);
assertThat(searchHits.getTotalHits()).isEqualTo(1);
@@ -3565,6 +3493,7 @@ public abstract class ElasticsearchIntegrationTests implements NewElasticsearchC
assertThat(foundEntity.getScriptedRate()).isEqualTo(84.0);
}
@Test // #1893
@DisplayName("should index document from source with version")
void shouldIndexDocumentFromSourceWithVersion() {
@@ -3875,7 +3804,7 @@ public abstract class ElasticsearchIntegrationTests implements NewElasticsearchC
}
}
@Document(indexName = "test-index-book-core-template")
@Document(indexName = "i-need-my-own-index")
static class Book {
@Nullable
@Id private String id;
@@ -4419,7 +4348,7 @@ public abstract class ElasticsearchIntegrationTests implements NewElasticsearchC
}
}
@Document(indexName = "immutable-class")
@Document(indexName = "#{@indexNameProvider.indexName()}-immutable")
private static final class ImmutableEntity {
@Id
@Nullable private final String id;
@@ -4477,7 +4406,7 @@ public abstract class ElasticsearchIntegrationTests implements NewElasticsearchC
}
}
@Document(indexName = "immutable-scripted")
@Document(indexName = "#{@indexNameProvider.indexName()}-immutable-scripted")
public static final class ImmutableWithScriptedEntity {
@Id private final String id;
@Field(type = Integer)
@@ -15,11 +15,30 @@
*/
package org.springframework.data.elasticsearch.core;
import static org.assertj.core.api.Assertions.*;
import static org.springframework.data.elasticsearch.client.elc.QueryBuilders.*;
import co.elastic.clients.elasticsearch._types.aggregations.Aggregate;
import co.elastic.clients.elasticsearch._types.aggregations.Buckets;
import co.elastic.clients.elasticsearch._types.aggregations.StringTermsAggregate;
import co.elastic.clients.elasticsearch._types.aggregations.StringTermsBucket;
import co.elastic.clients.elasticsearch.core.search.FieldCollapse;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.elasticsearch.ELCQueries;
import org.springframework.data.elasticsearch.client.elc.Aggregation;
import org.springframework.data.elasticsearch.client.elc.ElasticsearchAggregation;
import org.springframework.data.elasticsearch.client.elc.NativeQuery;
import org.springframework.data.elasticsearch.core.query.BaseQueryBuilder;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchTemplateConfiguration;
import org.springframework.data.elasticsearch.utils.IndexNameProvider;
import org.springframework.lang.Nullable;
import org.springframework.test.context.ContextConfiguration;
/**
@@ -37,9 +56,66 @@ public class ReactiveElasticsearchELCIntegrationTests extends ReactiveElasticsea
return new IndexNameProvider("reactive-template");
}
}
@Override
protected Query getTermsAggsQuery(String aggsName, String aggsField) {
return ELCQueries.getTermsAggsQuery(aggsName, aggsField);
}
@Override
public boolean usesNewElasticsearchClient() {
return true;
protected BaseQueryBuilder<?, ?> getBuilderWithMatchAllQuery() {
return ELCQueries.getBuilderWithMatchAllQuery();
}
@Override
protected BaseQueryBuilder<?, ?> getBuilderWithTermQuery(String field, String value) {
return ELCQueries.getBuilderWithTermQuery(field, value);
}
@Override
protected Query getQueryWithCollapse(String collapseField, @Nullable String innerHits, @Nullable Integer size) {
return NativeQuery.builder() //
.withQuery(matchAllQueryAsQuery()) //
.withFieldCollapse(FieldCollapse.of(fc -> {
fc.field(collapseField);
if (innerHits != null) {
fc.innerHits(ih -> ih.name(innerHits).size(size));
}
return fc;
})).build();
}
@Override
protected Query queryWithIds(String... ids) {
return ELCQueries.queryWithIds(ids);
}
@Override
protected <A extends AggregationContainer<?>> void assertThatAggregationsAreCorrect(A aggregationContainer) {
Aggregation aggregation = ((ElasticsearchAggregation) aggregationContainer).aggregation();
assertThat(aggregation.getName()).isEqualTo("messages");
Aggregate aggregate = aggregation.getAggregate();
assertThat(aggregate.isSterms()).isTrue();
StringTermsAggregate parsedStringTerms = (StringTermsAggregate) aggregate.sterms();
Buckets<StringTermsBucket> buckets = parsedStringTerms.buckets();
assertThat(buckets.isArray()).isTrue();
List<StringTermsBucket> bucketList = buckets.array();
assertThat(bucketList.size()).isEqualTo(3);
AtomicInteger count = new AtomicInteger();
bucketList.forEach(stringTermsBucket -> {
if ("message".equals(stringTermsBucket.key())) {
count.getAndIncrement();
assertThat(stringTermsBucket.docCount()).isEqualTo(3);
}
if ("some".equals(stringTermsBucket.key())) {
count.getAndIncrement();
assertThat(stringTermsBucket.docCount()).isEqualTo(2);
}
if ("other".equals(stringTermsBucket.key())) {
count.getAndIncrement();
assertThat(stringTermsBucket.docCount()).isEqualTo(1);
}
});
assertThat(count.get()).isEqualTo(3);
}
}
@@ -15,11 +15,23 @@
*/
package org.springframework.data.elasticsearch.core;
import static org.assertj.core.api.Assertions.*;
import static org.elasticsearch.index.query.QueryBuilders.*;
import org.elasticsearch.index.query.InnerHitBuilder;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.collapse.CollapseBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.elasticsearch.core.query.BaseQueryBuilder;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchRestTemplateConfiguration;
import org.springframework.data.elasticsearch.utils.IndexNameProvider;
import org.springframework.lang.Nullable;
import org.springframework.test.context.ContextConfiguration;
/**
@@ -37,4 +49,55 @@ public class ReactiveElasticsearchERHLCIntegrationTests extends ReactiveElastics
return new IndexNameProvider("reactive-template-es7");
}
}
@Override
protected Query getTermsAggsQuery(String aggsName, String aggsField) {
return new NativeSearchQueryBuilder().withQuery(matchAllQuery())
.addAggregation(AggregationBuilders.terms("messages").field("message")).build();
}
@Override
protected BaseQueryBuilder<?, ?> getBuilderWithMatchAllQuery() {
return new NativeSearchQueryBuilder().withQuery(matchAllQuery());
}
@Override
protected BaseQueryBuilder<?, ?> getBuilderWithTermQuery(String field, String value) {
return new NativeSearchQueryBuilder().withQuery(termQuery(field, value));
}
@Override
protected Query getQueryWithCollapse(String collapseField, @Nullable String innerHits, @Nullable Integer size) {
CollapseBuilder collapseBuilder = new CollapseBuilder(collapseField);
if (innerHits != null) {
InnerHitBuilder innerHitBuilder = new InnerHitBuilder(innerHits);
if (size != null) {
innerHitBuilder.setSize(size);
}
collapseBuilder.setInnerHits(innerHitBuilder);
}
return new NativeSearchQueryBuilder().withQuery(matchAllQuery()).withCollapseBuilder(collapseBuilder).build();
}
@Override
protected Query queryWithIds(String... ids) {
return new NativeSearchQueryBuilder().withIds(ids).build();
}
@Override
protected <A extends AggregationContainer<?>> void assertThatAggregationsAreCorrect(A aggregationContainer) {
Aggregation aggregation = (Aggregation) aggregationContainer.aggregation();
assertThat(aggregation.getName()).isEqualTo("messages");
assertThat(aggregation instanceof ParsedStringTerms);
ParsedStringTerms parsedStringTerms = (ParsedStringTerms) aggregation;
assertThat(parsedStringTerms.getBuckets().size()).isEqualTo(3);
assertThat(parsedStringTerms.getBucketByKey("message").getDocCount()).isEqualTo(3);
assertThat(parsedStringTerms.getBucketByKey("some").getDocCount()).isEqualTo(2);
assertThat(parsedStringTerms.getBucketByKey("other").getDocCount()).isEqualTo(1);
}
}
@@ -24,9 +24,9 @@ import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import java.lang.Boolean;
import java.lang.Integer;
import java.lang.Long;
import java.lang.Object;
import java.net.ConnectException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
@@ -40,36 +40,26 @@ import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.elasticsearch.index.query.IdsQueryBuilder;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.json.JSONException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledIf;
import org.skyscreamer.jsonassert.JSONAssert;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Version;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.NewElasticsearchClientDevelopment;
import org.springframework.data.elasticsearch.RestStatusException;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import org.springframework.data.elasticsearch.annotations.Mapping;
import org.springframework.data.elasticsearch.annotations.Setting;
import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient;
import org.springframework.data.elasticsearch.core.document.Explanation;
import org.springframework.data.elasticsearch.core.index.AliasAction;
import org.springframework.data.elasticsearch.core.index.AliasActionParameters;
@@ -98,7 +88,7 @@ import org.springframework.util.StringUtils;
*/
@SuppressWarnings("SpringJavaAutowiredMembersInspection")
@SpringIntegrationTest
public abstract class ReactiveElasticsearchIntegrationTests implements NewElasticsearchClientDevelopment {
public abstract class ReactiveElasticsearchIntegrationTests {
@Autowired private ReactiveElasticsearchOperations operations;
@Autowired private IndexNameProvider indexNameProvider;
@@ -118,28 +108,18 @@ public abstract class ReactiveElasticsearchIntegrationTests implements NewElasti
}
// endregion
protected abstract Query getTermsAggsQuery(String aggsName, String aggsField);
protected abstract BaseQueryBuilder<?, ?> getBuilderWithMatchAllQuery();
protected abstract BaseQueryBuilder<?, ?> getBuilderWithTermQuery(String field, String value);
protected abstract Query getQueryWithCollapse(String collapseField, @Nullable String innerHits,
@Nullable Integer size);
protected abstract Query queryWithIds(String... ids);
// region Tests
@DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation
@Test // DATAES-504
public void executeShouldProvideResource() {
Mono.from(operations.execute(ReactiveElasticsearchClient::ping)) //
.as(StepVerifier::create) //
.expectNext(true) //
.verifyComplete();
}
@DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation
@Test // DATAES-504
public void executeShouldConvertExceptions() {
Mono.from(operations.execute(client -> {
throw new RuntimeException(new ConnectException("we're doomed"));
})) //
.as(StepVerifier::create) //
.expectError(DataAccessResourceFailureException.class) //
.verify();
}
@Test // DATAES-504
public void insertWithIdShouldWork() {
@@ -157,7 +137,6 @@ public abstract class ReactiveElasticsearchIntegrationTests implements NewElasti
.verifyComplete();
}
@DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation
@Test // DATAES-504
public void insertWithAutogeneratedIdShouldUpdateEntityId() {
@@ -175,7 +154,6 @@ public abstract class ReactiveElasticsearchIntegrationTests implements NewElasti
return operations.exists(id, IndexCoordinates.of(index));
}
@DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation
@Test // DATAES-504
public void insertWithExplicitIndexNameShouldOverwriteMetadata() {
@@ -290,7 +268,6 @@ public abstract class ReactiveElasticsearchIntegrationTests implements NewElasti
.verifyComplete();
}
@DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation
@Test // DATAES-519
public void existsShouldReturnFalseWhenIndexDoesNotExist() {
@@ -300,7 +277,6 @@ public abstract class ReactiveElasticsearchIntegrationTests implements NewElasti
.verifyComplete();
}
@DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation
@Test // DATAES-504
public void existsShouldReturnTrueWhenFound() {
@@ -313,7 +289,6 @@ public abstract class ReactiveElasticsearchIntegrationTests implements NewElasti
.verifyComplete();
}
@DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation
@Test // DATAES-504
public void existsShouldReturnFalseWhenNotFound() {
@@ -432,7 +407,6 @@ public abstract class ReactiveElasticsearchIntegrationTests implements NewElasti
.verifyComplete();
}
@DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation
@Test // DATAES-595, DATAES-767
public void shouldThrowDataAccessExceptionWhenInvalidPreferenceForGivenCriteria() {
@@ -445,6 +419,8 @@ public abstract class ReactiveElasticsearchIntegrationTests implements NewElasti
CriteriaQuery queryWithInvalidPreference = new CriteriaQuery(
new Criteria("message").contains("some").and("message").contains("message"));
queryWithInvalidPreference.setPreference("_only_nodes:oops");
// add a pageable to not use scrolling,otherwise the exception class does not match
queryWithInvalidPreference.setPageable(PageRequest.of(0, 10));
operations.search(queryWithInvalidPreference, SampleEntity.class) //
.as(StepVerifier::create) //
@@ -498,7 +474,6 @@ public abstract class ReactiveElasticsearchIntegrationTests implements NewElasti
.verifyComplete();
}
@DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation
@Test // DATAES-567
public void aggregateShouldReturnAggregations() {
@@ -508,24 +483,17 @@ public abstract class ReactiveElasticsearchIntegrationTests implements NewElasti
index(sampleEntity1, sampleEntity2, sampleEntity3);
NativeSearchQuery query = new NativeSearchQueryBuilder().withQuery(matchAllQuery())
.addAggregation(AggregationBuilders.terms("messages").field("message")).build();
Query query = getTermsAggsQuery("messages", "message");
operations.aggregate(query, SampleEntity.class) //
.as(StepVerifier::create) //
.consumeNextWith(aggregationContainer -> {
Aggregation aggregation = ((ElasticsearchAggregation) aggregationContainer).aggregation();
assertThat(aggregation.getName()).isEqualTo("messages");
assertThat(aggregation instanceof ParsedStringTerms);
ParsedStringTerms parsedStringTerms = (ParsedStringTerms) aggregation;
assertThat(parsedStringTerms.getBuckets().size()).isEqualTo(3);
assertThat(parsedStringTerms.getBucketByKey("message").getDocCount()).isEqualTo(3);
assertThat(parsedStringTerms.getBucketByKey("some").getDocCount()).isEqualTo(2);
assertThat(parsedStringTerms.getBucketByKey("other").getDocCount()).isEqualTo(1);
assertThatAggregationsAreCorrect(aggregationContainer);
}).verifyComplete();
}
@DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation
protected abstract <A extends AggregationContainer<?>> void assertThatAggregationsAreCorrect(A aggregationContainer);
@Test // DATAES-567, DATAES-767
public void aggregateShouldErrorWhenIndexDoesNotExist() {
operations
@@ -621,7 +589,6 @@ public abstract class ReactiveElasticsearchIntegrationTests implements NewElasti
.verifyComplete();
}
@DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation
@Test // DATAES-519
public void deleteByQueryShouldReturnZeroWhenIndexDoesNotExist() {
@@ -634,7 +601,6 @@ public abstract class ReactiveElasticsearchIntegrationTests implements NewElasti
}).verifyComplete();
}
@DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation
@Test // DATAES-547
public void shouldDeleteAcrossIndex() {
@@ -650,11 +616,9 @@ public abstract class ReactiveElasticsearchIntegrationTests implements NewElasti
operations.indexOps(thisIndex).refresh().then(operations.indexOps(thatIndex).refresh()).block();
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder() //
.withQuery(termQuery("message", "test")) //
.build();
Query query = getBuilderWithTermQuery("message", "test").build();
operations.delete(searchQuery, SampleEntity.class, IndexCoordinates.of(indexPrefix + '*')) //
operations.delete(query, SampleEntity.class, IndexCoordinates.of(indexPrefix + '*')) //
.map(ByQueryResponse::getDeleted) //
.as(StepVerifier::create) //
.expectNext(2L) //
@@ -663,7 +627,6 @@ public abstract class ReactiveElasticsearchIntegrationTests implements NewElasti
operations.indexOps(thisIndex).delete().then(operations.indexOps(thatIndex).delete()).block();
}
@DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation
@Test // DATAES-547
public void shouldDeleteAcrossIndexWhenNoMatchingDataPresent() {
@@ -679,11 +642,9 @@ public abstract class ReactiveElasticsearchIntegrationTests implements NewElasti
operations.indexOps(thisIndex).refresh().then(operations.indexOps(thatIndex).refresh()).block();
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder() //
.withQuery(termQuery("message", "negative")) //
.build();
Query query = getBuilderWithTermQuery("message", "negative").build();
operations.delete(searchQuery, SampleEntity.class, IndexCoordinates.of(indexPrefix + '*')) //
operations.delete(query, SampleEntity.class, IndexCoordinates.of(indexPrefix + '*')) //
.map(ByQueryResponse::getDeleted) //
.as(StepVerifier::create) //
.expectNext(0L) //
@@ -692,7 +653,6 @@ public abstract class ReactiveElasticsearchIntegrationTests implements NewElasti
operations.indexOps(thisIndex).delete().then(operations.indexOps(thatIndex).delete()).block();
}
@DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation
@Test // DATAES-504
public void deleteByQueryShouldReturnNumberOfDeletedDocuments() {
@@ -707,7 +667,6 @@ public abstract class ReactiveElasticsearchIntegrationTests implements NewElasti
.verifyComplete();
}
@DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation
@Test // DATAES-504
public void deleteByQueryShouldReturnZeroIfNothingDeleted() {
@@ -722,7 +681,6 @@ public abstract class ReactiveElasticsearchIntegrationTests implements NewElasti
.verifyComplete();
}
@DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation
@Test // DATAES-593
public void shouldReturnDocumentWithCollapsedField() {
@@ -734,28 +692,20 @@ public abstract class ReactiveElasticsearchIntegrationTests implements NewElasti
entity3.setRate(1);
index(entity1, entity2, entity3);
NativeSearchQuery query = new NativeSearchQueryBuilder() //
.withQuery(matchAllQuery()) //
.withCollapseField("rate") //
.withPageable(PageRequest.of(0, 25)) //
.build();
Query query = getQueryWithCollapse("rate", null, null);
operations.search(query, SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())) //
.as(StepVerifier::create) //
.expectNextCount(2) //
.verifyComplete();
}
@DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation
@Test
void shouldReturnSortFields() {
SampleEntity entity = randomEntity("test message");
entity.rate = 42;
index(entity);
NativeSearchQuery query = new NativeSearchQueryBuilder() //
.withQuery(matchAllQuery()) //
.withSort(new FieldSortBuilder("rate").order(SortOrder.DESC)) //
Query query = getBuilderWithMatchAllQuery().withSort(Sort.by(Sort.Direction.DESC, "rate"))
.build();
operations.search(query, SampleEntity.class) //
@@ -763,12 +713,23 @@ public abstract class ReactiveElasticsearchIntegrationTests implements NewElasti
.consumeNextWith(it -> {
List<Object> sortValues = it.getSortValues();
assertThat(sortValues).hasSize(1);
assertThat(sortValues.get(0)).isEqualTo(42);
// old client returns Integer, new ElasticsearchClient String
java.lang.Object o = sortValues.get(0);
if (o instanceof Integer) {
Integer i = (Integer) o;
assertThat(o).isInstanceOf(Integer.class).isEqualTo(42);
} else if (o instanceof Long) {
Long l = (Long) o;
assertThat(o).isInstanceOf(Long.class).isEqualTo(42L);
} else if (o instanceof String) {
assertThat(o).isInstanceOf(String.class).isEqualTo("42");
} else {
fail("unexpected object type " + o);
}
}) //
.verifyComplete();
}
@DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation
@Test // DATAES-623, #1678
public void shouldReturnObjectsForGivenIdsUsingMultiGet() {
SampleEntity entity1 = randomEntity("test message 1");
@@ -788,7 +749,6 @@ public abstract class ReactiveElasticsearchIntegrationTests implements NewElasti
.verifyComplete();
}
@DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation
@Test // DATAES-623
public void shouldReturnObjectsForGivenIdsUsingMultiGetWithFields() {
SampleEntity entity1 = randomEntity("test message 1");
@@ -809,7 +769,6 @@ public abstract class ReactiveElasticsearchIntegrationTests implements NewElasti
.verifyComplete();
}
@DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation
@Test // DATAES-623. #1678
public void shouldDoBulkUpdate() {
SampleEntity entity1 = randomEntity("test message 1");
@@ -847,7 +806,6 @@ public abstract class ReactiveElasticsearchIntegrationTests implements NewElasti
.verifyComplete();
}
@DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation
@Test // DATAES-623
void shouldSaveAll() {
SampleEntity entity1 = randomEntity("test message 1");
@@ -858,7 +816,7 @@ public abstract class ReactiveElasticsearchIntegrationTests implements NewElasti
operations.saveAll(Mono.just(Arrays.asList(entity1, entity2)), IndexCoordinates.of(indexNameProvider.indexName())) //
.then().block();
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).build();
Query searchQuery = operations.matchAllQuery();
operations.search(searchQuery, SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())) //
.as(StepVerifier::create) //
.expectNextMatches(hit -> entity1.equals(hit.getContent()) || entity2.equals(hit.getContent())) //
@@ -891,7 +849,6 @@ public abstract class ReactiveElasticsearchIntegrationTests implements NewElasti
assertThat(retrieved.seqNoPrimaryTerm.getPrimaryTerm()).isPositive();
}
@DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation
@Test // DATAES-799, #1678
void multiGetShouldReturnSeqNoPrimaryTerm() {
OptimisticEntity original = new OptimisticEntity();
@@ -910,7 +867,6 @@ public abstract class ReactiveElasticsearchIntegrationTests implements NewElasti
return new NativeSearchQueryBuilder().withIds(singletonList(id)).build();
}
@DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation
@Test // DATAES-799
void searchShouldReturnSeqNoPrimaryTerm() {
OptimisticEntity original = new OptimisticEntity();
@@ -927,10 +883,9 @@ public abstract class ReactiveElasticsearchIntegrationTests implements NewElasti
}
private Query searchQueryForOne(String id) {
return new NativeSearchQueryBuilder().withFilter(new IdsQueryBuilder().addIds(id)).build();
return queryWithIds(id);
}
@DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation
@Test // DATAES-799
void shouldThrowOptimisticLockingFailureExceptionWhenConcurrentUpdateOccursOnEntityWithSeqNoPrimaryTermProperty() {
OptimisticEntity original = new OptimisticEntity();
@@ -950,7 +905,6 @@ public abstract class ReactiveElasticsearchIntegrationTests implements NewElasti
.verify();
}
@DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation
@Test // DATAES-799
void shouldThrowOptimisticLockingFailureExceptionWhenConcurrentUpdateOccursOnVersionedEntityWithSeqNoPrimaryTermProperty() {
OptimisticAndVersionedEntity original = new OptimisticAndVersionedEntity();
@@ -979,7 +933,6 @@ public abstract class ReactiveElasticsearchIntegrationTests implements NewElasti
operations.save(forEdit).as(StepVerifier::create).expectNextCount(1).verifyComplete();
}
@DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation
@Test // DATAES-909
void shouldDoUpdate() {
SampleEntity entity = randomEntity("test message");
@@ -1119,7 +1072,6 @@ public abstract class ReactiveElasticsearchIntegrationTests implements NewElasti
}).verifyComplete();
}
@DisabledIf("newElasticsearchClient") // todo #1973 still needs implementation
@Test // #1646, #1718
@DisplayName("should return a list of info for specific index")
void shouldReturnInformationListOfAllIndices() {
@@ -85,7 +85,7 @@ public class ReactiveElasticsearchTemplateUnitTests {
.as(StepVerifier::create) //
.verifyComplete();
assertThat(captor.getValue().getRefreshPolicy()).isEqualTo(WriteRequest.RefreshPolicy.IMMEDIATE);
assertThat(captor.getValue().getRefreshPolicy()).isEqualTo(WriteRequest.RefreshPolicy.NONE);
}
@Test // DATAES-504
@@ -170,7 +170,7 @@ public class ReactiveElasticsearchTemplateUnitTests {
.as(StepVerifier::create) //
.verifyComplete();
assertThat(captor.getValue().getRefreshPolicy()).isEqualTo(WriteRequest.RefreshPolicy.IMMEDIATE);
assertThat(captor.getValue().getRefreshPolicy()).isEqualTo(WriteRequest.RefreshPolicy.NONE);
}
@Test // DATAES-504
@@ -198,7 +198,7 @@ public class ReactiveElasticsearchTemplateUnitTests {
.as(StepVerifier::create) //
.verifyComplete();
assertThat(captor.getValue().isRefresh()).isTrue();
assertThat(captor.getValue().isRefresh()).isFalse();
}
@Test // DATAES-504
@@ -207,13 +207,13 @@ public class ReactiveElasticsearchTemplateUnitTests {
ArgumentCaptor<DeleteByQueryRequest> captor = ArgumentCaptor.forClass(DeleteByQueryRequest.class);
when(client.deleteBy(captor.capture())).thenReturn(Mono.empty());
template.setRefreshPolicy(RefreshPolicy.NONE);
template.setRefreshPolicy(RefreshPolicy.IMMEDIATE);
template.delete(new StringQuery(QueryBuilders.matchAllQuery().toString()), Object.class, index) //
.as(StepVerifier::create) //
.verifyComplete();
assertThat(captor.getValue().isRefresh()).isFalse();
assertThat(captor.getValue().isRefresh()).isTrue();
}
@Test // DATAES-504
@@ -116,6 +116,30 @@ class RequestFactoryTests {
assertThat(searchRequest.source().from()).isEqualTo(30);
}
@Test // DATAES-227
public void shouldUseUpsertOnUpdate() {
// given
Map<String, Object> doc = new HashMap<>();
doc.put("id", "1");
doc.put("message", "test");
org.springframework.data.elasticsearch.core.document.Document document = org.springframework.data.elasticsearch.core.document.Document
.from(doc);
UpdateQuery updateQuery = UpdateQuery.builder("1") //
.withDocument(document) //
.withUpsert(document) //
.build();
// when
UpdateRequest request = requestFactory.updateRequest(updateQuery, IndexCoordinates.of("index"));
// then
assertThat(request).isNotNull();
assertThat(request.upsertRequest()).isNotNull();
}
@Test // DATAES-693
public void shouldReturnSourceWhenRequested() {
// given
@@ -21,11 +21,15 @@ import co.elastic.clients.elasticsearch._types.aggregations.Aggregate;
import co.elastic.clients.elasticsearch._types.aggregations.Aggregation;
import co.elastic.clients.elasticsearch._types.aggregations.StatsBucketAggregate;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.elasticsearch.ELCQueries;
import org.springframework.data.elasticsearch.client.elc.ElasticsearchAggregation;
import org.springframework.data.elasticsearch.client.elc.ElasticsearchAggregations;
import org.springframework.data.elasticsearch.client.elc.NativeQuery;
import org.springframework.data.elasticsearch.client.elc.QueryBuilders;
@@ -55,18 +59,17 @@ public class AggregationELCIntegrationTests extends AggregationIntegrationTests
@Override
protected Query getTermsAggsQuery(String aggsName, String aggsField) {
return NativeQuery.builder() //
.withQuery(QueryBuilders.matchAllQueryAsQuery()) //
.withAggregation(aggsName, Aggregation.of(a -> a //
.terms(ta -> ta.field(aggsField)))) //
.withMaxResults(0) //
.build();
return ELCQueries.getTermsAggsQuery(aggsName, aggsField);
}
@Override
protected void assertThatAggsHasResult(AggregationsContainer<?> aggregationsContainer, String aggsName) {
Map<String, Aggregate> aggregations = ((ElasticsearchAggregations) aggregationsContainer).aggregations();
assertThat(aggregations).containsKey(aggsName);
List<ElasticsearchAggregation> aggregations = ((ElasticsearchAggregations) aggregationsContainer).aggregations();
List<String> aggNames = aggregations.stream() //
.map(ElasticsearchAggregation::aggregation) //
.map(org.springframework.data.elasticsearch.client.elc.Aggregation::getName) //
.collect(Collectors.toList());
assertThat(aggNames).contains(aggsName);
}
@Override
@@ -84,9 +87,13 @@ public class AggregationELCIntegrationTests extends AggregationIntegrationTests
@Override
protected void assertThatPipelineAggsAreCorrect(AggregationsContainer<?> aggregationsContainer, String aggsName,
String pipelineAggsName) {
Map<String, Aggregate> aggregations = ((ElasticsearchAggregations) aggregationsContainer).aggregations();
assertThat(aggregations).containsKey(aggsName);
Aggregate aggregate = aggregations.get(pipelineAggsName);
Map<String, Aggregate> aggregates = ((ElasticsearchAggregations) aggregationsContainer).aggregations().stream() //
.map(ElasticsearchAggregation::aggregation) //
.collect(Collectors.toMap(org.springframework.data.elasticsearch.client.elc.Aggregation::getName,
org.springframework.data.elasticsearch.client.elc.Aggregation::getAggregate));
assertThat(aggregates).containsKey(aggsName);
Aggregate aggregate = aggregates.get(pipelineAggsName);
assertThat(aggregate.isStatsBucket()).isTrue();
StatsBucketAggregate statsBucketAggregate = aggregate.statsBucket();
assertThat(statsBucketAggregate.min()).isEqualTo(1.0);
@@ -234,7 +234,7 @@ public abstract class AggregationIntegrationTests {
*/
static class ArticleEntityBuilder {
private ArticleEntity result;
private final ArticleEntity result;
public ArticleEntityBuilder(String id) {
result = new ArticleEntity(id);
@@ -116,7 +116,7 @@ public abstract class CompletionIntegrationTests implements NewElasticsearchClie
operations.bulkIndex(indexQueries, AnnotatedCompletionEntity.class);
}
@DisabledIf("newElasticsearchClient") // todo #1973, ES issue 150
@DisabledIf(value = "newElasticsearchClient", disabledReason = "todo #2139, ES issue 150")
@Test
public void shouldFindSuggestionsForGivenCriteriaQueryUsingCompletionEntity() {
@@ -148,7 +148,7 @@ public abstract class CompletionIntegrationTests implements NewElasticsearchClie
operations.get("1", CompletionEntity.class);
}
@DisabledIf("newElasticsearchClient") // todo #1973, ES issue 150
@DisabledIf(value = "newElasticsearchClient", disabledReason = "todo #2139, ES issue 150")
@Test
public void shouldFindSuggestionsForGivenCriteriaQueryUsingAnnotatedCompletionEntity() {
@@ -172,7 +172,7 @@ public abstract class CompletionIntegrationTests implements NewElasticsearchClie
assertThat(options.get(1).getText()).isIn("Marchand", "Mohsin");
}
@DisabledIf("newElasticsearchClient") // todo #1973, ES issue 150
@DisabledIf(value = "newElasticsearchClient", disabledReason = "todo #2139, ES 1issue 150")
@Test
public void shouldFindSuggestionsWithWeightsForGivenCriteriaQueryUsingAnnotatedCompletionEntity() {
@@ -67,7 +67,7 @@ public abstract class ReactiveSuggestIntegrationTests implements NewElasticsearc
operations.indexOps(IndexCoordinates.of(indexNameProvider.getPrefix() + "*")).delete().block();
}
@DisabledIf("newElasticsearchClient") // todo #1973, ES issue 150
@DisabledIf(value = "newElasticsearchClient", disabledReason = "todo #2139, ES issue 150")
@Test // #1302
@DisplayName("should find suggestions for given prefix completion")
void shouldFindSuggestionsForGivenPrefixCompletion() {
@@ -0,0 +1,44 @@
/*
* Copyright 2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.repository.support;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration;
import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories;
import org.springframework.data.elasticsearch.utils.IndexNameProvider;
import org.springframework.test.context.ContextConfiguration;
/**
* @author Peter-Josef Meisch
* @since 4.4
*/
@ContextConfiguration(classes = {ElasticsearchRepositoryELCIntegrationTests.Config.class })
public class ElasticsearchRepositoryELCIntegrationTests extends ElasticsearchRepositoryIntegrationTests {
@Configuration
@Import({ElasticsearchTemplateConfiguration.class })
@EnableElasticsearchRepositories(basePackages = {"org.springframework.data.elasticsearch.repository.support" },
considerNestedRepositories = true)
static class Config {
@Bean
IndexNameProvider indexNameProvider() {
return new IndexNameProvider("repository");
}
}
}
@@ -0,0 +1,44 @@
/*
* Copyright 2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.repository.support;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration;
import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories;
import org.springframework.data.elasticsearch.utils.IndexNameProvider;
import org.springframework.test.context.ContextConfiguration;
/**
* @author Peter-Josef Meisch
* @since 4.4
*/
@ContextConfiguration(classes = {ElasticsearchRepositoryERHLCIntegrationTests.Config.class })
public class ElasticsearchRepositoryERHLCIntegrationTests extends ElasticsearchRepositoryIntegrationTests {
@Configuration
@Import({ElasticsearchRestTemplateConfiguration.class })
@EnableElasticsearchRepositories(basePackages = {"org.springframework.data.elasticsearch.repository.support" },
considerNestedRepositories = true)
static class Config {
@Bean
IndexNameProvider indexNameProvider() {
return new IndexNameProvider("repository-es7");
}
}
}
@@ -17,12 +17,9 @@ package org.springframework.data.elasticsearch.repository.support;
import static org.assertj.core.api.Assertions.*;
import static org.elasticsearch.index.query.QueryBuilders.*;
import static org.springframework.data.elasticsearch.annotations.FieldType.*;
import static org.springframework.data.elasticsearch.utils.IdGenerator.*;
import java.io.IOException;
import java.lang.Long;
import java.lang.Object;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -30,12 +27,10 @@ import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import org.junit.jupiter.api.AfterEach;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Version;
@@ -45,18 +40,16 @@ import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Order;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import org.springframework.data.elasticsearch.core.IndexOperations;
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.junit.jupiter.ElasticsearchRestTemplateConfiguration;
import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories;
import org.springframework.data.elasticsearch.utils.IndexInitializer;
import org.springframework.data.elasticsearch.utils.IndexNameProvider;
import org.springframework.data.util.StreamUtils;
import org.springframework.lang.Nullable;
import org.springframework.test.context.ContextConfiguration;
/**
* @author Rizwan Idrees
@@ -68,29 +61,22 @@ import org.springframework.test.context.ContextConfiguration;
* @author Murali Chevuri
*/
@SpringIntegrationTest
@ContextConfiguration(classes = { SimpleElasticsearchRepositoryIntegrationTests.Config.class })
class SimpleElasticsearchRepositoryIntegrationTests {
@Configuration
@Import({ ElasticsearchRestTemplateConfiguration.class })
@EnableElasticsearchRepositories(basePackages = { "org.springframework.data.elasticsearch.repository.support" },
considerNestedRepositories = true)
static class Config {}
abstract class ElasticsearchRepositoryIntegrationTests {
@Autowired private SampleElasticsearchRepository repository;
@Autowired private ElasticsearchOperations operations;
private IndexOperations indexOperations;
@Autowired private IndexNameProvider indexNameProvider;
@BeforeEach
void before() {
indexOperations = operations.indexOps(SampleEntity.class);
IndexInitializer.init(indexOperations);
indexNameProvider.increment();
operations.indexOps(SampleEntity.class).createWithMapping();
}
@AfterEach
void after() {
indexOperations.delete();
@Test
@org.junit.jupiter.api.Order(Integer.MAX_VALUE)
public void cleanup() {
operations.indexOps(IndexCoordinates.of(indexNameProvider.getPrefix() + "*")).delete();
}
@Test
@@ -369,7 +355,7 @@ class SimpleElasticsearchRepositoryIntegrationTests {
repository.deleteAllById(Arrays.asList(id1, id3));
// then
assertThat(repository.findAll()).extracting(SampleEntity::getId).containsExactly(id2);
Assertions.assertThat(repository.findAll()).extracting(SampleEntity::getId).containsExactly(id2);
}
@Test
@@ -539,8 +525,8 @@ class SimpleElasticsearchRepositoryIntegrationTests {
repository.deleteAll(sampleEntities);
// then
assertThat(repository.findById(documentId1)).isNotPresent();
assertThat(repository.findById(documentId2)).isNotPresent();
Assertions.assertThat(repository.findById(documentId1)).isNotPresent();
Assertions.assertThat(repository.findById(documentId2)).isNotPresent();
}
@Test
@@ -677,14 +663,14 @@ class SimpleElasticsearchRepositoryIntegrationTests {
return sampleEntities;
}
@Document(indexName = "test-index-sample-simple-repository")
@Document(indexName = "#{@indexNameProvider.indexName()}")
static class SampleEntity {
@Nullable
@Id private String id;
@Nullable
@Field(type = Text, store = true, fielddata = true) private String type;
@Field(type = FieldType.Text, store = true, fielddata = true) private String type;
@Nullable
@Field(type = Text, store = true, fielddata = true) private String message;
@Field(type = FieldType.Text, store = true, fielddata = true) private String message;
@Nullable private int rate;
@Nullable private boolean available;
@Nullable
@@ -0,0 +1,44 @@
/*
* Copyright 2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.repository.support;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchTemplateConfiguration;
import org.springframework.data.elasticsearch.repository.config.EnableReactiveElasticsearchRepositories;
import org.springframework.data.elasticsearch.utils.IndexNameProvider;
import org.springframework.test.context.ContextConfiguration;
/**
* @author Peter-Josef Meisch
* @since 4.4
*/
@ContextConfiguration(classes = { SimpleReactiveElasticsearchRepositoryELCIntegrationTests.Config.class })
public class SimpleReactiveElasticsearchRepositoryELCIntegrationTests
extends SimpleReactiveElasticsearchRepositoryIntegrationTests {
@Configuration
@Import({ ReactiveElasticsearchTemplateConfiguration.class })
@EnableReactiveElasticsearchRepositories(considerNestedRepositories = true)
static class Config {
@Bean
IndexNameProvider indexNameProvider() {
return new IndexNameProvider("simple-reactive-repository");
}
}
}
@@ -0,0 +1,43 @@
/*
* Copyright 2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.repository.support;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchRestTemplateConfiguration;
import org.springframework.data.elasticsearch.repository.config.EnableReactiveElasticsearchRepositories;
import org.springframework.data.elasticsearch.utils.IndexNameProvider;
import org.springframework.test.context.ContextConfiguration;
/**
* @author Peter-Josef Meisch
*/
@ContextConfiguration(classes = { SimpleReactiveElasticsearchRepositoryERHLCIntegrationTests.Config.class })
public class SimpleReactiveElasticsearchRepositoryERHLCIntegrationTests
extends SimpleReactiveElasticsearchRepositoryIntegrationTests {
@Configuration
@Import({ ReactiveElasticsearchRestTemplateConfiguration.class })
@EnableReactiveElasticsearchRepositories(considerNestedRepositories = true)
static class Config {
@Bean
IndexNameProvider indexNameProvider() {
return new IndexNameProvider("simple-reactive-repository-es7");
}
}
}
@@ -16,51 +16,44 @@
package org.springframework.data.elasticsearch.repository.support;
import static org.assertj.core.api.Assertions.*;
import static org.springframework.data.elasticsearch.annotations.FieldType.*;
import static org.springframework.data.elasticsearch.core.query.Query.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import java.lang.Boolean;
import java.lang.Long;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.reactivestreams.Publisher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Version;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Order;
import org.springframework.data.elasticsearch.RestStatusException;
import org.springframework.data.elasticsearch.NoSuchIndexException;
import org.springframework.data.elasticsearch.annotations.CountQuery;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import org.springframework.data.elasticsearch.annotations.Highlight;
import org.springframework.data.elasticsearch.annotations.HighlightField;
import org.springframework.data.elasticsearch.annotations.Query;
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchRestTemplateConfiguration;
import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest;
import org.springframework.data.elasticsearch.repository.config.EnableReactiveElasticsearchRepositories;
import org.springframework.data.elasticsearch.utils.IndexNameProvider;
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
import org.springframework.lang.Nullable;
import org.springframework.test.context.ContextConfiguration;
/**
* @author Christoph Strobl
@@ -68,28 +61,23 @@ import org.springframework.test.context.ContextConfiguration;
* @author Jens Schauder
*/
@SpringIntegrationTest
@ContextConfiguration(classes = { SimpleReactiveElasticsearchRepositoryTests.Config.class })
class SimpleReactiveElasticsearchRepositoryTests {
@Configuration
@Import({ ReactiveElasticsearchRestTemplateConfiguration.class })
@EnableReactiveElasticsearchRepositories(considerNestedRepositories = true)
static class Config {}
static final String INDEX = "test-index-sample-simple-reactive";
abstract class SimpleReactiveElasticsearchRepositoryIntegrationTests {
@Autowired ReactiveElasticsearchOperations operations;
@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
@Autowired ReactiveSampleEntityRepository repository;
@Autowired private IndexNameProvider indexNameProvider;
@BeforeEach
void setUp() {
operations.indexOps(IndexCoordinates.of(INDEX)).delete().block();
void before() {
indexNameProvider.increment();
operations.indexOps(SampleEntity.class).createWithMapping().block();
}
@AfterEach
void after() {
operations.indexOps(IndexCoordinates.of(INDEX)).delete().block();
@Test
@org.junit.jupiter.api.Order(Integer.MAX_VALUE)
public void cleanup() {
operations.indexOps(IndexCoordinates.of(indexNameProvider.getPrefix() + "*")).delete().block();
}
@Test // DATAES-519
@@ -103,7 +91,7 @@ class SimpleReactiveElasticsearchRepositoryTests {
}
private Mono<Boolean> documentWithIdExistsInIndex(String id) {
return operations.exists(id, IndexCoordinates.of(INDEX));
return operations.exists(id, IndexCoordinates.of(indexNameProvider.indexName()));
}
@Test // DATAES-519
@@ -121,9 +109,12 @@ class SimpleReactiveElasticsearchRepositoryTests {
@Test // DATAES-519, DATAES-767, DATAES-822
void findByIdShouldErrorIfIndexDoesNotExist() {
operations.indexOps(SampleEntity.class).delete().block();
repository.findById("id-two") //
.as(StepVerifier::create) //
.expectError(RestStatusException.class);
.expectError(NoSuchIndexException.class) //
.verify();
}
@Test // DATAES-519
@@ -267,9 +258,12 @@ class SimpleReactiveElasticsearchRepositoryTests {
@Test // DATAES-519, DATAES-767, DATAES-822
void countShouldErrorWhenIndexDoesNotExist() {
operations.indexOps(SampleEntity.class).delete().block();
repository.count() //
.as(StepVerifier::create) //
.expectError(RestStatusException.class);
.expectError(NoSuchIndexException.class) //
.verify();
}
@Test // DATAES-519
@@ -595,7 +589,7 @@ class SimpleReactiveElasticsearchRepositoryTests {
}
Mono<Void> bulkIndex(SampleEntity... entities) {
return operations.saveAll(Arrays.asList(entities), IndexCoordinates.of(INDEX)).then();
return operations.saveAll(Arrays.asList(entities), IndexCoordinates.of(indexNameProvider.indexName())).then();
}
interface ReactiveSampleEntityRepository extends ReactiveCrudRepository<SampleEntity, String> {
@@ -635,14 +629,14 @@ class SimpleReactiveElasticsearchRepositoryTests {
Mono<Long> retrieveCountByText(String message);
}
@Document(indexName = INDEX)
@Document(indexName = "#{@indexNameProvider.indexName()}")
static class SampleEntity {
@Nullable
@Id private String id;
@Nullable
@Field(type = Text, store = true, fielddata = true) private String type;
@Field(type = FieldType.Text, store = true, fielddata = true) private String type;
@Nullable
@Field(type = Text, store = true, fielddata = true) private String message;
@Field(type = FieldType.Text, store = true, fielddata = true) private String message;
@Nullable private int rate;
@Nullable private boolean available;
@Nullable
@@ -15,7 +15,7 @@
#
#
sde.testcontainers.image-name=docker.elastic.co/elasticsearch/elasticsearch
sde.testcontainers.image-version=7.17.1
sde.testcontainers.image-version=7.17.3
#
#
# needed as we do a DELETE /* at the end of the tests, will be required from 8.0 on, produces a warning since 7.13