1
0
mirror of synced 2026-05-25 05:33:18 +00:00

Compare commits

..

79 Commits

Author SHA1 Message Date
Mark Paluch 122b0eb3dd Update CI Properties.
See #2988
2024-11-15 10:39:25 +01:00
Mark Paluch 48de54bf82 After release cleanups.
See #2988
2024-11-15 10:23:55 +01:00
Mark Paluch 17b7a89792 Prepare next development iteration.
See #2988
2024-11-15 10:23:53 +01:00
Mark Paluch e6c31972ca Release version 5.2.12 (2023.1.12).
See #2988
2024-11-15 10:21:37 +01:00
Mark Paluch 509157a969 Prepare 5.2.12 (2023.1.12).
See #2988
2024-11-15 10:21:22 +01:00
Mark Paluch 08aad9bc65 Upgrade to Maven Wrapper 3.9.9.
See #3000
2024-11-07 09:51:02 +01:00
Mark Paluch 0f0cf23779 After release cleanups.
See #2980
2024-10-18 10:17:03 +02:00
Mark Paluch f02c338b61 Prepare next development iteration.
See #2980
2024-10-18 10:17:02 +02:00
Mark Paluch 737650ac54 Release version 5.2.11 (2023.1.11).
See #2980
2024-10-18 10:14:33 +02:00
Mark Paluch b3f21736b6 Prepare 5.2.11 (2023.1.11).
See #2980
2024-10-18 10:14:17 +02:00
Mark Paluch fbed601efa Consistently run all CI steps with the same user.
See #2982
2024-10-09 09:27:26 +02:00
Jens Schauder 5f2d71ef32 After release cleanups.
See #2965
2024-09-13 10:53:00 +02:00
Jens Schauder 2904427e9b Prepare next development iteration.
See #2965
2024-09-13 10:52:59 +02:00
Jens Schauder 167f2246d3 Release version 5.2.10 (2023.1.10).
See #2965
2024-09-13 10:50:08 +02:00
Jens Schauder f09ce87f0e Prepare 5.2.10 (2023.1.10).
See #2965
2024-09-13 10:49:51 +02:00
Peter-Josef Meisch 1d5c78a06a Remove Blockhound
Original Pull Request #2978
Closes #2977

(cherry picked from commit d06c122fd5)
(cherry picked from commit 8117e5a174)
2024-09-04 18:19:22 +02:00
Peter-Josef Meisch 5a2dcd3b01 Add excludeFromSource handling to multifield.
Original Pull Request #2975
Closes #2971

(cherry picked from commit 555b570246)
(cherry picked from commit a179dd0643)
2024-08-31 21:36:11 +02:00
Jens Schauder 74b05d21b5 After release cleanups.
See #2938
2024-08-16 09:01:34 +02:00
Jens Schauder 1895219322 Prepare next development iteration.
See #2938
2024-08-16 09:01:33 +02:00
Jens Schauder bd62128b52 Release version 5.2.9 (2023.1.9).
See #2938
2024-08-16 08:58:34 +02:00
Jens Schauder 16eb208d77 Prepare 5.2.9 (2023.1.9).
See #2938
2024-08-16 08:58:12 +02:00
Mark Paluch 0575323975 Update CI properties.
See #2938
2024-08-08 10:17:45 +02:00
Mark Paluch 4289f73b24 Upgrade to Maven Wrapper 3.9.8.
See #2957
2024-08-08 10:17:24 +02:00
Eric Haag 7f246724d2 Migrate build to Spring Develocity Conventions extension.
* Migrate build to Spring Develocity Conventions extension.

* Adopt Develocity environment variables.

Closes #2944
2024-08-01 14:54:29 +02:00
Mark Paluch ce6b4d853f Bundle Javadoc with Antora documentation site.
Closes #2948.
2024-07-31 14:53:30 +02:00
Jens Schauder c87d106074 After release cleanups.
See #2931
2024-07-12 13:36:35 +02:00
Jens Schauder bf21b697ca Prepare next development iteration.
See #2931
2024-07-12 13:36:34 +02:00
Jens Schauder eead581ff0 Release version 5.2.8 (2023.1.8).
See #2931
2024-07-12 13:33:06 +02:00
Jens Schauder aef71db320 Prepare 5.2.8 (2023.1.8).
See #2931
2024-07-12 13:32:46 +02:00
Mark Paluch 452c76fb12 Switch to Broadcom docker proxy.
Closes #2934
2024-06-20 11:21:14 +02:00
Mark Paluch c9a47997b9 After release cleanups.
See #2912
2024-06-14 10:06:43 +02:00
Mark Paluch 4a42c673ce Prepare next development iteration.
See #2912
2024-06-14 10:06:42 +02:00
Mark Paluch 4729b9cfdc Release version 5.2.7 (2023.1.7).
See #2912
2024-06-14 10:04:22 +02:00
Mark Paluch aa16c0c5ab Prepare 5.2.7 (2023.1.7).
See #2912
2024-06-14 10:04:05 +02:00
Peter-Josef Meisch 5e507fa163 Fix max dim value for dense vector.
Closes #2911

(cherry picked from commit e997b39f68)
(cherry picked from commit ba9edf8ec8)
2024-05-18 18:27:59 +02:00
Mark Paluch a355537d78 After release cleanups.
See #2895
2024-05-17 11:24:38 +02:00
Mark Paluch 4fbf9f8b5a Prepare next development iteration.
See #2895
2024-05-17 11:24:37 +02:00
Mark Paluch 2cb27c26d7 Release version 5.2.6 (2023.1.6).
See #2895
2024-05-17 11:22:24 +02:00
Mark Paluch c322797eab Prepare 5.2.6 (2023.1.6).
See #2895
2024-05-17 11:22:00 +02:00
Peter-Josef Meisch c81958f62d Fix implementation of explicit refresh policy.
Original Pull Request #2908
Closes #2907

(cherry picked from commit 94a40a7a75)
2024-05-10 09:35:20 +02:00
Peter-Josef Meisch ed18be6f1c Fix parameter in Order constructor.
Closes #2897

(cherry picked from commit ad66510e9e)
2024-04-15 21:11:35 +02:00
Mark Paluch b3a21fbb7c After release cleanups.
See #2873
2024-04-12 10:17:29 +02:00
Mark Paluch d874b9d000 Prepare next development iteration.
See #2873
2024-04-12 10:17:28 +02:00
Mark Paluch 36e6a4b328 Release version 5.2.5 (2023.1.5).
See #2873
2024-04-12 10:15:11 +02:00
Mark Paluch 3710e47abd Prepare 5.2.5 (2023.1.5).
See #2873
2024-04-12 10:14:56 +02:00
Mark Paluch c40abc267b After release cleanups.
See #2850
2024-03-15 10:20:09 +01:00
Mark Paluch b479184d83 Prepare next development iteration.
See #2850
2024-03-15 10:20:08 +01:00
Mark Paluch f2370bc7df Release version 5.2.4 (2023.1.4).
See #2850
2024-03-15 10:17:49 +01:00
Mark Paluch 5f045d104e Prepare 5.2.4 (2023.1.4).
See #2850
2024-03-15 10:17:30 +01:00
Peter-Josef Meisch b52e8d1431 Fix setting setting id in bulkrequest.
Original Pull Request #2862
Closes #2861

(cherry picked from commit debf04b499)
2024-02-28 21:45:49 +01:00
Mark Paluch e86bb771be After release cleanups.
See #2825
2024-02-16 13:53:53 +01:00
Mark Paluch c9fbc17109 Prepare next development iteration.
See #2825
2024-02-16 13:53:51 +01:00
Mark Paluch 397e155d5e Release version 5.2.3 (2023.1.3).
See #2825
2024-02-16 13:50:30 +01:00
Mark Paluch 4e52594d12 Prepare 5.2.3 (2023.1.3).
See #2825
2024-02-16 13:50:02 +01:00
Peter-Josef Meisch 310f0ed567 Fix criteria filter in native query.
Original Pull Request #2846
Closes #2840

(cherry picked from commit e9ecebd9ef)
2024-02-06 21:23:50 +01:00
Peter-Josef Meisch dab0be04db Fix store null values implementation.
Original Pull Request #2842

(cherry picked from commit 0a1e20579e)
2024-01-31 17:32:41 +01:00
Mark Paluch e8f269d9ec Refine Artifactory build name.
See #2772
2024-01-31 15:13:58 +01:00
Peter-Josef Meisch 1e9824ae2d Upgrade to Elasticsearch 8.11.4.
Original Pull Request #2829
Closes #2827
2024-01-19 07:18:40 +01:00
Mark Paluch c9b8b3f33d After release cleanups.
See #2804
2024-01-12 10:54:33 +01:00
Mark Paluch a806553169 Prepare next development iteration.
See #2804
2024-01-12 10:54:32 +01:00
Mark Paluch 27011d382b Release version 5.2.2 (2023.1.2).
See #2804
2024-01-12 10:51:32 +01:00
Mark Paluch 489a2605b4 Prepare 5.2.2 (2023.1.2).
See #2804
2024-01-12 10:50:44 +01:00
Peter-Josef Meisch 953f840d7c Upgrade to Elasticsearch 8.11.3.
Original Pull Request #2823
Closes #2821
2024-01-06 17:37:27 +01:00
Mark Paluch d905a4b25f Extend license header copyright years to 2024.
See #2818
2024-01-02 13:54:48 +01:00
Peter-Josef Meisch e2e6b5005b Make org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchTemplate.ClientCallback public.
Original Pull Request #2815
Closes #2814

(cherry picked from commit 260dadd4d6)
2023-12-29 13:57:56 +01:00
Peter-Josef Meisch 0d4e8ad6eb Upgrade to Elasticsearch 8.11.2.
Original Pull Request #2809
2023-12-24 14:31:20 +01:00
Mark Paluch a9bacd23b0 After release cleanups.
See #2771
2023-12-15 14:15:21 +01:00
Mark Paluch 031f7c8c78 Prepare next development iteration.
See #2771
2023-12-15 14:15:19 +01:00
Mark Paluch 76187a4934 Release version 5.2.1 (2023.1.1).
See #2771
2023-12-15 14:11:53 +01:00
Mark Paluch b693494e17 Prepare 5.2.1 (2023.1.1).
See #2771
2023-12-15 14:11:23 +01:00
Mark Paluch b324935d22 Update CI properties.
See #2771
2023-12-14 08:49:56 +01:00
Mark Paluch 920f7c029f Upgrade to Maven Wrapper 3.9.6.
See #2799
2023-12-14 08:34:15 +01:00
Peter-Josef Meisch 051777ee25 Removed junk characters from code.
(cherry picked from commit 415d5e0385)
2023-11-30 20:44:26 +01:00
Peter-Josef Meisch 0fb98eda39 Fix type of returned sort values.
Original Pull Request #2786
Closes #2777

(cherry picked from commit 3833975a1a)
2023-11-30 20:44:25 +01:00
Mark Paluch df76b43cb4 Introduce property for Jenkins user and Artifactory server details.
Closes #2781
2023-11-27 14:23:49 +01:00
Runbing b681197d0f Fixed the URL for the Spring Data Commons documentation.
I fixed broken links in the Spring Data Elasticsearch documentation for Spring Data Commons.

Closes #2776
2023-11-20 11:31:58 +01:00
Mark Paluch 8a1323c11a Update CI trigger versions.
See #2737
2023-11-17 14:53:43 +01:00
Mark Paluch 033d71ccb6 After release cleanups.
See #2737
2023-11-17 14:33:56 +01:00
Mark Paluch 0ae2a52de8 Prepare next development iteration.
See #2737
2023-11-17 14:33:54 +01:00
232 changed files with 1799 additions and 9577 deletions
+1 -1
View File
@@ -1,3 +1,3 @@
#Thu Nov 07 09:47:28 CET 2024
#Thu Nov 07 09:51:02 CET 2024
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.9.9/apache-maven-3.9.9-bin.zip
Vendored
+1 -1
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/3.2.x", threshold: hudson.model.Result.SUCCESS)
}
options {
+5 -2
View File
@@ -1,4 +1,6 @@
= 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]] image:https://img.shields.io/badge/Revved%20up%20by-Develocity-06A0CE?logo=Gradle&labelColor=02303A["Revved up by Develocity", link="https://ge.spring.io/scans?search.rootProjectNames=Spring Data Elasticsearch"]
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]] image:https://img.shields.io/badge/Revved%20up%20by-Gradle%20Enterprise-06A0CE?logo=Gradle&labelColor=02303A["Revved up by Gradle Enterprise", link="https://ge.spring.io/scans?search.rootProjectNames=Spring Data Elasticsearch"]
The primary goal of the https://projects.spring.io/spring-data[Spring Data] project is to make it easier to build Spring-powered applications that use new data access technologies such as non-relational databases, map-reduce frameworks, and cloud based data services.
@@ -124,7 +126,8 @@ Wed love to help!
https://docs.spring.io/spring-data/elasticsearch/docs/current/reference/html/[reference documentation], and https://docs.spring.io/spring-data/elasticsearch/docs/current/api/[Javadocs].
* Learn the Spring basics Spring Data builds on Spring Framework, check the https://spring.io[spring.io] web-site for a wealth of reference documentation.
If you are just starting out with Spring, try one of the https://spring.io/guides[guides].
* Ask a question or chat with the community on https://app.gitter.im/#/room/#spring-projects_spring-data:gitter.im[Gitter].
* Ask a question - we monitor https://stackoverflow.com[stackoverflow.com] for questions tagged with https://stackoverflow.com/tags/spring-data[`spring-data-elasticsearch`].
You can also chat with the community on https://gitter.im/spring-projects/spring-data[Gitter].
* Report bugs with Spring Data for Elasticsearch at https://github.com/spring-projects/spring-data-elasticsearch/issues[https://github.com/spring-projects/spring-data-elasticsearch/issues].
== Reporting Issues
+1 -3
View File
@@ -1,6 +1,6 @@
# Java versions
java.main.tag=17.0.13_11-jdk-focal
java.next.tag=23.0.1_11-jdk-noble
java.next.tag=22.0.2_9-jdk-jammy
# Docker container images - standard
docker.java.main.image=library/eclipse-temurin:${java.main.tag}
@@ -11,11 +11,9 @@ docker.mongodb.4.4.version=4.4.25
docker.mongodb.5.0.version=5.0.21
docker.mongodb.6.0.version=6.0.10
docker.mongodb.7.0.version=7.0.2
docker.mongodb.8.0.version=8.0.0
# Supported versions of Redis
docker.redis.6.version=6.2.13
docker.redis.7.version=7.2.4
# Supported versions of Cassandra
docker.cassandra.3.version=3.11.16
+36 -20
View File
@@ -5,12 +5,12 @@
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-elasticsearch</artifactId>
<version>5.4.0</version>
<version>5.2.13-SNAPSHOT</version>
<parent>
<groupId>org.springframework.data.build</groupId>
<artifactId>spring-data-parent</artifactId>
<version>3.4.0</version>
<version>3.2.13-SNAPSHOT</version>
</parent>
<name>Spring Data Elasticsearch</name>
@@ -18,16 +18,16 @@
<url>https://github.com/spring-projects/spring-data-elasticsearch</url>
<properties>
<springdata.commons>3.4.0</springdata.commons>
<springdata.commons>3.2.13-SNAPSHOT</springdata.commons>
<!-- version of the ElasticsearchClient -->
<elasticsearch-java>8.15.3</elasticsearch-java>
<elasticsearch-java>8.11.4</elasticsearch-java>
<hoverfly>0.19.0</hoverfly>
<log4j>2.23.1</log4j>
<jsonassert>1.5.3</jsonassert>
<testcontainers>1.20.0</testcontainers>
<wiremock>3.9.1</wiremock>
<hoverfly>0.14.4</hoverfly>
<log4j>2.18.0</log4j>
<jsonassert>1.5.1</jsonassert>
<testcontainers>1.18.0</testcontainers>
<wiremock>2.35.1</wiremock>
<java-module-name>spring.data.elasticsearch</java-module-name>
@@ -131,6 +131,17 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-client</artifactId> <!-- is Apache 2-->
<version>${elasticsearch-java}</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Jackson JSON Mapper -->
<dependency>
@@ -244,8 +255,8 @@
</dependency>
<dependency>
<groupId>org.wiremock</groupId>
<artifactId>wiremock</artifactId>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock-jre8</artifactId>
<version>${wiremock}</version>
<scope>test</scope>
<exclusions>
@@ -305,13 +316,6 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.tngtech.archunit</groupId>
<artifactId>archunit-junit5</artifactId>
<version>${archunit}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
@@ -450,8 +454,20 @@
</profiles>
<repositories>
<repository>
<id>spring-snapshot</id>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
<releases>
<enabled>false</enabled>
</releases>
</repository>
<repository>
<id>spring-milestone</id>
<url>https://repo.spring.io/milestone</url>
</repository>
</repositories>
</project>
-2
View File
@@ -9,8 +9,6 @@
*** xref:migration-guides/migration-guide-4.4-5.0.adoc[]
*** xref:migration-guides/migration-guide-5.0-5.1.adoc[]
*** xref:migration-guides/migration-guide-5.1-5.2.adoc[]
*** xref:migration-guides/migration-guide-5.2-5.3.adoc[]
*** xref:migration-guides/migration-guide-5.3-5.4.adoc[]
* xref:elasticsearch.adoc[]
@@ -150,7 +150,7 @@ ClientConfiguration clientConfiguration = ClientConfiguration.builder()
return headers;
})
.withClientConfigurer( <.>
ElasticsearchHttpClientConfigurationCallback.from(clientBuilder -> {
ElasticsearchClientConfigurationCallback.from(clientBuilder -> {
// ...
return clientBuilder;
}))
@@ -192,7 +192,6 @@ This callback provides a `org.elasticsearch.client.RestClientBuilder` that can b
[source,java]
----
ClientConfiguration.builder()
.connectedTo("localhost:9200", "localhost:9291")
.withClientConfigurer(ElasticsearchClients.ElasticsearchRestClientConfigurationCallback.from(restClientBuilder -> {
// configure the Elasticsearch RestClient
return restClientBuilder;
@@ -211,7 +210,6 @@ used by the `RestClient`.
[source,java]
----
ClientConfiguration.builder()
.connectedTo("localhost:9200", "localhost:9291")
.withClientConfigurer(ElasticsearchClients.ElasticsearchHttpClientConfigurationCallback.from(httpAsyncClientBuilder -> {
// configure the HttpAsyncClient
return httpAsyncClientBuilder;
@@ -1,24 +1,13 @@
[[new-features]]
= What's new
[[new-features.5-4-0]]
== New in Spring Data Elasticsearch 5.4
[[new-features.5-2-3]]
== New in Spring Data Elasticsearch 5.2.2
* Upgrade to Elasticsearch 8.11.4
* Upgrade to Elasticsearch 8.15.3.
* Allow to customize the mapped type name for `@InnerField` and `@Field` annotations.
* Support for Elasticsearch SQL.
* Add support for retrieving request executionDuration.
[[new-features.5-3-0]]
== New in Spring Data Elasticsearch 5.3
* Upgrade to Elasticsearch 8.13.2.
* Add support for highlight queries in highlighting.
* Add shard statistics to the `SearchHit` class.
* Add support for multi search template API.
* Add support for SpEL in @Query.
* Add support for field aliases in the index mapping.
* Add support for has_child and has_parent queries.
[[new-features.5-2-2]]
== New in Spring Data Elasticsearch 5.2.2
* Upgrade to Elasticsearch 8.11.3
[[new-features.5-2-0]]
== New in Spring Data Elasticsearch 5.2
@@ -52,7 +52,7 @@ public class Statement {
return routing;
}
public void setRouting(String routing) {
public void setRouting(Routing routing) {
this.routing = routing;
}
@@ -199,7 +199,7 @@ void init() {
repository.save(
Statement.builder()
.withText("+1 for the sun")
.withRouting(savedWeather.getId())
,withRouting(savedWeather.getId())
.withRelation(new JoinField<>("vote", sunnyAnswer.getId())) <5>
.build());
}
@@ -226,7 +226,6 @@ SearchHits<Statement> hasVotes() {
Query query = NativeQuery.builder()
.withQuery(co.elastic.clients.elasticsearch._types.query_dsl.Query.of(qb -> qb
.hasChild(hc -> hc
.type("answer")
.queryName("vote")
.query(matchAllQueryAsQuery())
.scoreMode(ChildScoreMode.None)
@@ -192,7 +192,8 @@ public String getProperty() {
This annotation can be set on a String property of an entity.
This property will not be written to the mapping, it will not be stored in Elasticsearch and its value will not be read from an Elasticsearch document.
After an entity is persisted, for example with a call to `ElasticsearchOperations.save(T entity)`, the entity returned from that call will contain the name of the index that an entity was saved to in that property.
After an entity is persisted, for example with a call to `ElasticsearchOperations.save(T entity)`, the entity
returned from that call will contain the name of the index that an entity was saved to in that property.
This is useful when the index name is dynamically set by a bean, or when writing to a write alias.
Putting some value into such a property does not set the index into which an entity is stored!
@@ -422,6 +423,7 @@ Looking at the `Configuration` from the xref:elasticsearch/object-mapping.adoc#e
@Configuration
public class Config extends ElasticsearchConfiguration {
@NonNull
@Override
public ClientConfiguration clientConfiguration() {
return ClientConfiguration.builder() //
@@ -12,10 +12,10 @@ class Book {
@Id
private String id;
@Field(type = FieldType.Text)
@Field(type = FieldType.text)
private String name;
@Field(type = FieldType.Text)
@Field(type = FieldType.text)
private String summary;
@Field(type = FieldType.Integer)
@@ -316,7 +316,7 @@ Repository methods can be defined to have the following return types for returni
.Declare query on the method using the `@Query` annotation.
====
The arguments passed to the method can be inserted into placeholders in the query string. The placeholders are of the form `?0`, `?1`, `?2` etc. for the first, second, third parameter and so on.
The arguments passed to the method can be inserted into placeholders in the query string. the placeholders are of the form `?0`, `?1`, `?2` etc. for the first, second, third parameter and so on.
[source,java]
----
interface BookRepository extends ElasticsearchRepository<Book, String> {
@@ -361,202 +361,3 @@ would make an https://www.elastic.co/guide/en/elasticsearch/reference/current/qu
}
----
====
[[elasticsearch.query-methods.at-query.spel]]
=== Using SpEL Expressions
.Declare query on the method using the `@Query` annotation with SpEL expression.
====
https://docs.spring.io/spring-framework/reference/core/expressions.html[SpEL expression] is also supported when defining query in `@Query`.
[source,java]
----
interface BookRepository extends ElasticsearchRepository<Book, String> {
@Query("""
{
"bool":{
"must":[
{
"term":{
"name": "#{#name}"
}
}
]
}
}
""")
Page<Book> findByName(String name, Pageable pageable);
}
----
If for example the function is called with the parameter _John_, it would produce the following query body:
[source,json]
----
{
"bool":{
"must":[
{
"term":{
"name": "John"
}
}
]
}
}
----
====
.accessing parameter property.
====
Supposing that we have the following class as query parameter type:
[source,java]
----
public record QueryParameter(String value) {
}
----
It's easy to access the parameter by `#` symbol, then reference the property `value` with a simple `.`:
[source,java]
----
interface BookRepository extends ElasticsearchRepository<Book, String> {
@Query("""
{
"bool":{
"must":[
{
"term":{
"name": "#{#parameter.value}"
}
}
]
}
}
""")
Page<Book> findByName(QueryParameter parameter, Pageable pageable);
}
----
We can pass `new QueryParameter("John")` as the parameter now, and it will produce the same query string as above.
====
.accessing bean property.
====
https://docs.spring.io/spring-framework/reference/core/expressions/language-ref/bean-references.html[Bean property] is also supported to access. Given that there is a bean named `queryParameter` of type `QueryParameter`, we can access the bean with symbol `@` rather than `#`, and there is no need to declare a parameter of type `QueryParameter` in the query method:
[source,java]
----
interface BookRepository extends ElasticsearchRepository<Book, String> {
@Query("""
{
"bool":{
"must":[
{
"term":{
"name": "#{@queryParameter.value}"
}
}
]
}
}
""")
Page<Book> findByName(Pageable pageable);
}
----
====
.SpEL and `Collection` param.
====
`Collection` parameter is also supported and is as easy to use as normal `String`, such as the following `terms` query:
[source,java]
----
interface BookRepository extends ElasticsearchRepository<Book, String> {
@Query("""
{
"bool":{
"must":[
{
"terms":{
"name": #{#names}
}
}
]
}
}
""")
Page<Book> findByName(Collection<String> names, Pageable pageable);
}
----
NOTE: collection values should not be quoted when declaring the elasticsearch json query.
A collection of `names` like `List.of("name1", "name2")` will produce the following terms query:
[source,json]
----
{
"bool":{
"must":[
{
"terms":{
"name": ["name1", "name2"]
}
}
]
}
}
----
====
.access property in the `Collection` param.
====
https://docs.spring.io/spring-framework/reference/core/expressions/language-ref/collection-projection.html[SpEL Collection Projection] is convenient to use when values in the `Collection` parameter is not plain `String`:
[source,java]
----
interface BookRepository extends ElasticsearchRepository<Book, String> {
@Query("""
{
"bool":{
"must":[
{
"terms":{
"name": #{#parameters.![value]}
}
}
]
}
}
""")
Page<Book> findByName(Collection<QueryParameter> parameters, Pageable pageable);
}
----
This will extract all the `value` property values as a new `Collection` from `QueryParameter` collection, thus takes the same effect as above.
====
.alter parameter name by using `@Param`
====
When accessing the parameter by SpEL, it's also useful to alter the parameter name to another one by `@Param` annotation in Sping Data:
[source,java]
----
interface BookRepository extends ElasticsearchRepository<Book, String> {
@Query("""
{
"bool":{
"must":[
{
"terms":{
"name": #{#another.![value]}
}
}
]
}
}
""")
Page<Book> findByName(@Param("another") Collection<QueryParameter> parameters, Pageable pageable);
}
----
====
@@ -194,7 +194,7 @@ In the following code this is used to run a query for a given gender and maximum
var runtimeField = new RuntimeField("age", "long", """ <.>
Instant currentDate = Instant.ofEpochMilli(new Date().getTime());
Instant startDate = doc['birthDate'].value.toInstant();
Instant startDate = doc['birth-date'].value.toInstant();
emit (ChronoUnit.DAYS.between(startDate, currentDate) / 365);
""");
@@ -81,7 +81,7 @@ When a document is retrieved with the methods of the `DocumentOperations` inter
When searching with the methods of the `SearchOperations` interface, additional information is available for each entity, for example the _score_ or the _sortValues_ of the found entity.
In order to return this information, each entity is wrapped in a `SearchHit` object that contains this entity-specific additional information.
These `SearchHit` objects themselves are returned within a `SearchHits` object which additionally contains informations about the whole search like the _maxScore_ or requested aggregations or the execution duration it took to complete the request.
These `SearchHit` objects themselves are returned within a `SearchHits` object which additionally contains informations about the whole search like the _maxScore_ or requested aggregations.
The following classes and interfaces are now available:
.SearchHit<T>
@@ -6,9 +6,7 @@ The following table shows the Elasticsearch and Spring versions that are used by
[cols="^,^,^,^",options="header"]
|===
| Spring Data Release Train | Spring Data Elasticsearch | Elasticsearch | Spring Framework
| 2024.1 (in development) | 5.3.x | 8.15.3 | 6.1.x
| 2024.0 | 5.3.1 | 8.13.4 | 6.1.x
| 2023.1 (Vaughan) | 5.2.x | 8.11.1 | 6.1.x
| 2023.1 (Vaughan) | 5.2.x | 8.11.4 | 6.1.x
| 2023.0 (Ullmann) | 5.1.x | 8.7.1 | 6.0.x
| 2022.0 (Turing) | 5.0.xfootnote:oom[Out of maintenance] | 8.5.3 | 6.0.x
| 2021.2 (Raj) | 4.4.xfootnote:oom[] | 7.17.3 | 5.3.x
@@ -49,7 +49,7 @@ Also the reactive implementation that was provided up to now has been moved here
If you are using `ElasticsearchRestTemplate` directly and not the `ElasticsearchOperations` interface you'll need to adjust your imports as well.
When working with the `NativeSearchQuery` class, you'll need to switch to the `NativeQuery` class, which can take a
`Query` instance coming from the new Elasticsearch client libraries.
`Query` instance comign from the new Elasticsearch client libraries.
You'll find plenty of examples in the test code.
[[elasticsearch-migration-guide-4.4-5.0.breaking-changes-records]]
@@ -1,21 +0,0 @@
[[elasticsearch-migration-guide-5.2-5.3]]
= Upgrading from 5.2.x to 5.3.x
This section describes breaking changes from version 5.2.x to 5.3.x and how removed features can be replaced by new introduced features.
[[elasticsearch-migration-guide-5.2-5.3.breaking-changes]]
== Breaking Changes
During the parameter replacement in `@Query` annotated repository methods previous versions wrote the String `"null"` into the query that was sent to Elasticsearch when the actual parameter value was `null`.
As Elasticsearch does not store `null` values, this behaviour could lead to problems, for example whent the fields to be searched contains the string `"null"`.
In Version 5.3 a `null` value in a parameter will cause a `ConversionException` to be thrown.
If you are using `"null"` as the
`null_value` defined in a field mapping, then pass that string into the query instead of a Java `null`.
[[elasticsearch-migration-guide-5.2-5.3.deprecations]]
== Deprecations
=== Removals
The deprecated classes `org.springframework.data.elasticsearch.ELCQueries`
and `org.springframework.data.elasticsearch.client.elc.QueryBuilders` have been removed, use `org.springframework.data.elasticsearch.client.elc.Queries` instead.
@@ -1,23 +0,0 @@
[[elasticsearch-migration-guide-5.3-5.4]]
= Upgrading from 5.3.x to 5.4.x
This section describes breaking changes from version 5.3.x to 5.4.x and how removed features can be replaced by new introduced features.
[[elasticsearch-migration-guide-5.3-5.4.breaking-changes]]
== Breaking Changes
[[elasticsearch-migration-guide-5.3-5.4.breaking-changes.knn-search]]
=== knn search
The `withKnnQuery` method in `NativeQueryBuilder` has been replaced with `withKnnSearches` to build a `NativeQuery` with knn search.
`KnnQuery` and `KnnSearch` are two different classes in elasticsearch java client and are used for different queries, with different parameters supported:
- `KnnSearch`: is https://www.elastic.co/guide/en/elasticsearch/reference/8.13/search-search.html#search-api-knn[the top level `knn` query] in the elasticsearch request;
- `KnnQuery`: is https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-knn-query.html[the `knn` query inside `query` clause];
If `KnnQuery` is still preferable, please be sure to construct it inside `query` clause manually, by means of `withQuery(co.elastic.clients.elasticsearch._types.query_dsl.Query query)` clause in `NativeQueryBuilder`.
[[elasticsearch-migration-guide-5.3-5.4.deprecations]]
== Deprecations
=== Removals
@@ -1,79 +0,0 @@
/*
* Copyright 2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
/**
* Identifies an alias for the index.
*
* @author Youssef Aouichaoui
* @since 5.4
*/
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE })
@Repeatable(Aliases.class)
public @interface Alias {
/**
* @return Index alias name. Alias for {@link #alias}.
*/
@AliasFor("alias")
String value() default "";
/**
* @return Index alias name. Alias for {@link #value}.
*/
@AliasFor("value")
String alias() default "";
/**
* @return Query used to limit documents the alias can access.
*/
Filter filter() default @Filter;
/**
* @return Used to route indexing operations to a specific shard.
*/
String indexRouting() default "";
/**
* @return Used to route indexing and search operations to a specific shard.
*/
String routing() default "";
/**
* @return Used to route search operations to a specific shard.
*/
String searchRouting() default "";
/**
* @return Is the alias hidden?
*/
boolean isHidden() default false;
/**
* @return Is it the 'write index' for the alias?
*/
boolean isWriteIndex() default false;
}
@@ -1,36 +0,0 @@
/*
* Copyright 2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Container annotation that aggregates several {@link Alias} annotations.
*
* @author Youssef Aouichaoui
* @see Alias
* @since 5.4
*/
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE })
public @interface Aliases {
Alias[] value();
}
@@ -39,173 +39,41 @@ public enum DateFormat {
basic_t_time("'T'HHmmss.SSSXXX"), //
basic_t_time_no_millis("'T'HHmmssXXX"), //
basic_week_date("YYYY'W'wwe"), // week-based-year!
/**
* @since 5.3
*/
strict_basic_week_date("YYYY'W'wwe"), // week-based-year!
basic_week_date_time("YYYY'W'wwe'T'HHmmss.SSSX"), // here Elasticsearch uses a different zone format
/**
* @since 5.3
*/
strict_basic_week_date_time("YYYY'W'wwe'T'HHmmss.SSSX"), // here Elasticsearch uses a different zone format
basic_week_date_time_no_millis("YYYY'W'wwe'T'HHmmssX"), //
/**
* @since 5.3
*/
strict_basic_week_date_time_no_millis("YYYY'W'wwe'T'HHmmssX"), //
date("uuuu-MM-dd"), //
/**
* @since 5.3
*/
strict_date("uuuu-MM-dd"), //
date_hour("uuuu-MM-dd'T'HH"), //
/**
* @since 5.3
*/
strict_date_hour("uuuu-MM-dd'T'HH"), //
date_hour_minute("uuuu-MM-dd'T'HH:mm"), //
/**
* @since 5.3
*/
strict_date_hour_minute("uuuu-MM-dd'T'HH:mm"), //
date_hour_minute_second("uuuu-MM-dd'T'HH:mm:ss"), //
/**
* @since 5.3
*/
strict_date_hour_minute_second("uuuu-MM-dd'T'HH:mm:ss"), //
date_hour_minute_second_fraction("uuuu-MM-dd'T'HH:mm:ss.SSS"), //
/**
* @since 5.3
*/
strict_date_hour_minute_second_fraction("uuuu-MM-dd'T'HH:mm:ss.SSS"), //
date_hour_minute_second_millis("uuuu-MM-dd'T'HH:mm:ss.SSS"), //
/**
* @since 5.3
*/
strict_date_hour_minute_second_millis("uuuu-MM-dd'T'HH:mm:ss.SSS"), //
date_optional_time("uuuu-MM-dd['T'HH:mm:ss.SSSXXX]"), //
/**
* @since 5.3
*/
strict_date_optional_time("uuuu-MM-dd['T'HH:mm:ss.SSSXXX]"), //
strict_date_optional_time_nanos("uuuu-MM-dd['T'HH:mm:ss.SSSSSSXXX]"), //
date_time("uuuu-MM-dd'T'HH:mm:ss.SSSXXX"), //
/**
* @since 5.3
*/
strict_date_time("uuuu-MM-dd'T'HH:mm:ss.SSSXXX"), //
date_time_no_millis("uuuu-MM-dd'T'HH:mm:ssVV"), // here Elasticsearch uses the zone-id in its implementation
/**
* @since 5.3
*/
strict_date_time_no_millis("uuuu-MM-dd'T'HH:mm:ssVV"), // here Elasticsearch uses the zone-id in its implementation
epoch_millis("epoch_millis"), //
epoch_second("epoch_second"), //
hour("HH"), //
/**
* @since 5.3
*/
strict_hour("HH"), //
hour_minute("HH:mm"), //
/**
* @since 5.3
*/
strict_hour_minute("HH:mm"), //
hour_minute_second("HH:mm:ss"), //
/**
* @since 5.3
*/
strict_hour_minute_second("HH:mm:ss"), //
hour_minute_second_fraction("HH:mm:ss.SSS"), //
/**
* @since 5.3
*/
strict_hour_minute_second_fraction("HH:mm:ss.SSS"), //
hour_minute_second_millis("HH:mm:ss.SSS"), //
/**
* @since 5.3
*/
strict_hour_minute_second_millis("HH:mm:ss.SSS"), //
ordinal_date("uuuu-DDD"), //
/**
* @since 5.3
*/
strict_ordinal_date("uuuu-DDD"), //
ordinal_date_time("uuuu-DDD'T'HH:mm:ss.SSSXXX"), //
/**
* @since 5.3
*/
strict_ordinal_date_time("uuuu-DDD'T'HH:mm:ss.SSSXXX"), //
ordinal_date_time_no_millis("uuuu-DDD'T'HH:mm:ssXXX"), //
/**
* @since 5.3
*/
strict_ordinal_date_time_no_millis("uuuu-DDD'T'HH:mm:ssXXX"), //
time("HH:mm:ss.SSSXXX"), //
/**
* @since 5.3
*/
strict_time("HH:mm:ss.SSSXXX"), //
time_no_millis("HH:mm:ssXXX"), //
/**
* @since 5.3
*/
strict_time_no_millis("HH:mm:ssXXX"), //
t_time("'T'HH:mm:ss.SSSXXX"), //
/**
* @since 5.3
*/
strict_t_time("'T'HH:mm:ss.SSSXXX"), //
t_time_no_millis("'T'HH:mm:ssXXX"), //
/**
* @since 5.3
*/
strict_t_time_no_millis("'T'HH:mm:ssXXX"), //
week_date("YYYY-'W'ww-e"), //
/**
* @since 5.3
*/
strict_week_date("YYYY-'W'ww-e"), //
week_date_time("YYYY-'W'ww-e'T'HH:mm:ss.SSSXXX"), //
/**
* @since 5.3
*/
strict_week_date_time("YYYY-'W'ww-e'T'HH:mm:ss.SSSXXX"), //
week_date_time_no_millis("YYYY-'W'ww-e'T'HH:mm:ssXXX"), //
/**
* @since 5.3
*/
strict_week_date_time_no_millis("YYYY-'W'ww-e'T'HH:mm:ssXXX"), //
weekyear(""), // no TemporalAccessor available for these 3
/**
* @since 5.3
*/
strict_weekyear(""), // no TemporalAccessor available for these 3
weekyear_week(""), //
/**
* @since 5.3
*/
strict_weekyear_week(""), //
weekyear_week_day(""), //
/**
* @since 5.3
*/
strict_strict_weekyear_week_day(""), //
year("uuuu"), //
/**
* @since 5.3
*/
strict_year("uuuu"), //
year_month("uuuu-MM"), //
/**
* @since 5.3
*/
strict_year_month("uuuu-MM"), //
year_month_day("uuuu-MM-dd"), //
/**
* @since 5.3
*/
strict_year_month_day("uuuu-MM-dd"); //
year_month_day("uuuu-MM-dd"); //
private final String pattern;
@@ -100,13 +100,6 @@ public @interface Document {
*/
boolean storeVersionInSource() default true;
/**
* Aliases for the index.
*
* @since 5.4
*/
Alias[] aliases() default {};
/**
* @since 4.3
*/
@@ -37,8 +37,6 @@ import org.springframework.core.annotation.AliasFor;
* @author Brian Kimmig
* @author Morgan Lutz
* @author Sascha Woo
* @author Haibo Liu
* @author Andriy Redko
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.METHOD })
@@ -130,10 +128,6 @@ public @interface Field {
boolean norms() default true;
/**
* NOte that null_value setting are not supported in Elasticsearch for all types. For example setting a null_value on
* a field with type text will throw an exception in the server when the mapping is written to Elasticsearch. Alas,
* the Elasticsearch documentation does not specify on which types it is allowed on which it is not.
*
* @since 4.0
*/
String nullValue() default "";
@@ -201,27 +195,6 @@ public @interface Field {
*/
int dims() default -1;
/**
* to be used in combination with {@link FieldType#Dense_Vector}
*
* @since 5.4
*/
String elementType() default FieldElementType.DEFAULT;
/**
* to be used in combination with {@link FieldType#Dense_Vector}
*
* @since 5.4
*/
KnnSimilarity knnSimilarity() default KnnSimilarity.DEFAULT;
/**
* to be used in combination with {@link FieldType#Dense_Vector}
*
* @since 5.4
*/
KnnIndexOptions[] knnIndexOptions() default {};
/**
* Controls how Elasticsearch dynamically adds fields to the inner object within the document.<br>
* To be used in combination with {@link FieldType#Object} or {@link FieldType#Nested}
@@ -245,11 +218,4 @@ public @interface Field {
* @since 5.1
*/
boolean storeEmptyValue() default true;
/**
* overrides the field type in the mapping which otherwise will be taken from corresponding {@link FieldType}
*
* @since 5.4
*/
String mappedTypeName() default "";
}
@@ -1,26 +0,0 @@
/*
* Copyright 2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.annotations;
/**
* @author Haibo Liu
* @since 5.4
*/
public final class FieldElementType {
public final static String DEFAULT = "";
public final static String FLOAT = "float";
public final static String BYTE = "byte";
}
@@ -1,38 +0,0 @@
/*
* Copyright 2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.annotations;
import org.springframework.core.annotation.AliasFor;
/**
* Query used to limit documents.
*
* @author Youssef Aouichaoui
* @since 5.4
*/
public @interface Filter {
/**
* @return Query used to limit documents. Alias for {@link #query}.
*/
@AliasFor("query")
String value() default "";
/**
* @return Query used to limit documents. Alias for {@link #value}.
*/
@AliasFor("value")
String query() default "";
}
@@ -21,7 +21,6 @@ import java.lang.annotation.RetentionPolicy;
/**
* @author Peter-Josef Meisch
* @author Haibo Liu
* @since 4.0
*/
@Documented
@@ -60,8 +59,6 @@ public @interface HighlightParameters {
int numberOfFragments() default -1;
Query highlightQuery() default @Query;
String order() default "";
int phraseLimit() default -1;
@@ -15,6 +15,9 @@
*/
package org.springframework.data.elasticsearch.annotations;
import org.springframework.data.annotation.ReadOnlyProperty;
import org.springframework.data.annotation.Transient;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
@@ -22,10 +25,10 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation to mark a String property of an entity to be filled with the name of the index where the entity was stored
* after it is indexed into Elasticsearch. This can be used when the name of the index is dynamically created or when a
* document was indexed into a write alias.
* <p>
* Annotation to mark a String property of an entity to be filled with the name of the index where the entity was
* stored after it is indexed into Elasticsearch. This can be used when the name of the index is dynamically created
* or when a document was indexed into a write alias.
*
* This can not be used to specify the index where an entity should be written to.
*
* @author Peter-Josef Meisch
@@ -29,8 +29,6 @@ import java.lang.annotation.Target;
* @author Aleksei Arsenev
* @author Brian Kimmig
* @author Morgan Lutz
* @author Haibo Liu
* @author Andriy Redko
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
@@ -151,32 +149,4 @@ public @interface InnerField {
* @since 4.2
*/
int dims() default -1;
/**
* to be used in combination with {@link FieldType#Dense_Vector}
*
* @since 5.4
*/
String elementType() default FieldElementType.DEFAULT;
/**
* to be used in combination with {@link FieldType#Dense_Vector}
*
* @since 5.4
*/
KnnSimilarity knnSimilarity() default KnnSimilarity.DEFAULT;
/**
* to be used in combination with {@link FieldType#Dense_Vector}
*
* @since 5.4
*/
KnnIndexOptions[] knnIndexOptions() default {};
/**
* overrides the field type in the mapping which otherwise will be taken from corresponding {@link FieldType}
*
* @since 5.4
*/
String mappedTypeName() default "";
}
@@ -1,38 +0,0 @@
/*
* Copyright 2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.annotations;
/**
* @author Haibo Liu
* @since 5.4
*/
public enum KnnAlgorithmType {
HNSW("hnsw"),
INT8_HNSW("int8_hnsw"),
FLAT("flat"),
INT8_FLAT("int8_flat"),
DEFAULT("");
private final String type;
KnnAlgorithmType(String type) {
this.type = type;
}
public String getType() {
return type;
}
}
@@ -1,40 +0,0 @@
/*
* Copyright 2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.annotations;
/**
* @author Haibo Liu
* @since 5.4
*/
public @interface KnnIndexOptions {
KnnAlgorithmType type() default KnnAlgorithmType.DEFAULT;
/**
* Only applicable to {@link KnnAlgorithmType#HNSW} and {@link KnnAlgorithmType#INT8_HNSW} index types.
*/
int m() default -1;
/**
* Only applicable to {@link KnnAlgorithmType#HNSW} and {@link KnnAlgorithmType#INT8_HNSW} index types.
*/
int efConstruction() default -1;
/**
* Only applicable to {@link KnnAlgorithmType#INT8_HNSW} and {@link KnnAlgorithmType#INT8_FLAT} index types.
*/
float confidenceInterval() default -1F;
}
@@ -1,38 +0,0 @@
/*
* Copyright 2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.annotations;
/**
* @author Haibo Liu
* @since 5.4
*/
public enum KnnSimilarity {
L2_NORM("l2_norm"),
DOT_PRODUCT("dot_product"),
COSINE("cosine"),
MAX_INNER_PRODUCT("max_inner_product"),
DEFAULT("");
private final String similarity;
KnnSimilarity(String similarity) {
this.similarity = similarity;
}
public String getSimilarity() {
return similarity;
}
}
@@ -74,14 +74,7 @@ public @interface Mapping {
*/
String runtimeFieldsPath() default "";
/**
* field alias definitions to be written to the index mapping
*
* @since 5.3
*/
MappingAlias[] aliases() default {};
enum Detection {
DEFAULT, TRUE, FALSE
DEFAULT, TRUE, FALSE;
}
}
@@ -1,45 +0,0 @@
/*
* Copyright 2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.annotations;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Defines a field alias in the index mapping.
*
* @author Peter-Josef Meisch
* @since 5.3
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Documented
@Inherited
public @interface MappingAlias {
/**
* the name of the alias.
*/
String name();
/**
* the path of the alias.
*/
String path();
}
@@ -25,11 +25,11 @@ import org.springframework.data.util.ReactiveWrappers;
*/
public class ElasticsearchAotPredicates {
public static final Predicate<ReactiveWrappers.ReactiveLibrary> IS_REACTIVE_LIBRARY_AVAILABLE = (
public static final Predicate<ReactiveWrappers.ReactiveLibrary> IS_REACTIVE_LIBARARY_AVAILABLE = (
lib) -> ReactiveWrappers.isAvailable(lib);
public static boolean isReactorPresent() {
return IS_REACTIVE_LIBRARY_AVAILABLE.test(ReactiveWrappers.ReactiveLibrary.PROJECT_REACTOR);
return IS_REACTIVE_LIBARARY_AVAILABLE.test(ReactiveWrappers.ReactiveLibrary.PROJECT_REACTOR);
}
}
@@ -233,15 +233,6 @@ public interface ClientConfiguration {
*/
TerminalClientConfigurationBuilder usingSsl();
/**
* Connects using https if flag is true.
*
* @param flag whether to use https in the connection
* @return the {@link TerminalClientConfigurationBuilder}
* @since 5.3
*/
TerminalClientConfigurationBuilder usingSsl(boolean flag);
/**
* Connect via {@literal https} using the given {@link SSLContext}.<br />
* <strong>NOTE</strong> You need to leave out the protocol in
@@ -25,6 +25,7 @@ import java.util.function.Supplier;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import org.elasticsearch.client.RestClientBuilder.HttpClientConfigCallback;
import org.springframework.data.elasticsearch.client.ClientConfiguration.ClientConfigurationBuilderWithRequiredEndpoint;
import org.springframework.data.elasticsearch.client.ClientConfiguration.MaybeSecureClientConfigurationBuilder;
import org.springframework.data.elasticsearch.client.ClientConfiguration.TerminalClientConfigurationBuilder;
@@ -105,13 +106,6 @@ class ClientConfigurationBuilder
return this;
}
@Override
public TerminalClientConfigurationBuilder usingSsl(boolean flag) {
this.useSsl = flag;
return this;
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.ClientConfiguration.MaybeSecureClientConfigurationBuilder#usingSsl(javax.net.ssl.SSLContext)
@@ -1,70 +0,0 @@
/*
* Copyright 2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.client.elc;
import java.util.function.Consumer;
import org.springframework.data.elasticsearch.core.query.CriteriaQuery;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.core.query.StringQuery;
import org.springframework.lang.Nullable;
/**
* An abstract class that serves as a base for query processors. It provides a common interface and basic functionality
* for query processing.
*
* @author Aouichaoui Youssef
* @since 5.3
*/
public abstract class AbstractQueryProcessor {
/**
* Convert a spring-data-elasticsearch {@literal query} to an Elasticsearch {@literal query}.
*
* @param query spring-data-elasticsearch {@literal query}.
* @param queryConverter correct mapped field names and the values to the converted values.
* @return an Elasticsearch {@literal query}.
*/
@Nullable
static co.elastic.clients.elasticsearch._types.query_dsl.Query getEsQuery(@Nullable Query query,
@Nullable Consumer<Query> queryConverter) {
if (query == null) {
return null;
}
if (queryConverter != null) {
queryConverter.accept(query);
}
co.elastic.clients.elasticsearch._types.query_dsl.Query esQuery = null;
if (query instanceof CriteriaQuery criteriaQuery) {
esQuery = CriteriaQueryProcessor.createQuery(criteriaQuery.getCriteria());
} else if (query instanceof StringQuery stringQuery) {
esQuery = Queries.wrapperQueryAsQuery(stringQuery.getSource());
} else if (query instanceof NativeQuery nativeQuery) {
if (nativeQuery.getQuery() != null) {
esQuery = nativeQuery.getQuery();
} else if (nativeQuery.getSpringDataQuery() != null) {
esQuery = getEsQuery(nativeQuery.getSpringDataQuery(), queryConverter);
}
} else {
throw new IllegalArgumentException("unhandled Query implementation " + query.getClass().getName());
}
return esQuery;
}
}
@@ -16,6 +16,7 @@
package org.springframework.data.elasticsearch.client.elc;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch.cluster.ElasticsearchClusterClient;
import co.elastic.clients.transport.ElasticsearchTransport;
import org.elasticsearch.client.RestClient;
@@ -39,4 +40,9 @@ public class AutoCloseableElasticsearchClient extends ElasticsearchClient implem
public void close() throws Exception {
transport.close();
}
@Override
public ElasticsearchClusterClient cluster() {
return super.cluster();
}
}
@@ -39,7 +39,6 @@ import org.springframework.data.elasticsearch.core.geo.GeoBox;
import org.springframework.data.elasticsearch.core.geo.GeoJson;
import org.springframework.data.elasticsearch.core.geo.GeoPoint;
import org.springframework.data.elasticsearch.core.query.Criteria;
import org.springframework.data.elasticsearch.utils.geohash.Geohash;
import org.springframework.data.geo.Box;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.Metrics;
@@ -51,7 +50,6 @@ import org.springframework.util.Assert;
* filter.
*
* @author Peter-Josef Meisch
* @author Junghoon Ban
* @since 4.4
*/
class CriteriaFilterProcessor {
@@ -70,17 +68,10 @@ class CriteriaFilterProcessor {
for (Criteria chainedCriteria : criteria.getCriteriaChain()) {
if (chainedCriteria.isOr()) {
Collection<? extends Query> queriesForEntries = queriesForEntries(chainedCriteria);
if (!queriesForEntries.isEmpty()) {
BoolQuery.Builder boolQueryBuilder = QueryBuilders.bool();
queriesForEntries.forEach(boolQueryBuilder::should);
filterQueries.add(new Query(boolQueryBuilder.build()));
}
BoolQuery.Builder boolQueryBuilder = QueryBuilders.bool();
queriesForEntries(chainedCriteria).forEach(boolQueryBuilder::should);
filterQueries.add(new Query(boolQueryBuilder.build()));
} else if (chainedCriteria.isNegating()) {
Assert.notNull(criteria.getField(), "criteria must have a field");
Collection<? extends Query> negatingFilters = buildNegatingFilter(criteria.getField().getName(),
criteria.getFilterCriteriaEntries());
filterQueries.addAll(negatingFilters);
@@ -124,7 +115,6 @@ class CriteriaFilterProcessor {
private static Collection<? extends Query> queriesForEntries(Criteria criteria) {
Assert.notNull(criteria.getField(), "criteria must have a field");
String fieldName = criteria.getField().getName();
Assert.notNull(fieldName, "Unknown field");
@@ -179,17 +169,17 @@ class CriteriaFilterProcessor {
Assert.isTrue(values[1] instanceof String || values[1] instanceof Distance,
"Second element of a geo distance filter must be a text or a Distance");
String dist = (values[1] instanceof Distance distance) ? extractDistanceString(distance) : (String) values[1];
String dist = (values[1] instanceof Distance) ? extractDistanceString((Distance) values[1]) : (String) values[1];
return QueryBuilders.geoDistance() //
.field(fieldName) //
.distance(dist) //
.distanceType(GeoDistanceType.Plane) //
.location(location -> {
if (values[0] instanceof GeoPoint loc) {
if (values[0]instanceof GeoPoint loc) {
location.latlon(latlon -> latlon.lat(loc.getLat()).lon(loc.getLon()));
} else if (values[0] instanceof Point point) {
GeoPoint loc = GeoPoint.fromPoint(point);
} else if (values[0] instanceof Point) {
GeoPoint loc = GeoPoint.fromPoint((Point) values[0]);
location.latlon(latlon -> latlon.lat(loc.getLat()).lon(loc.getLon()));
} else {
String loc = (String) values[0];
@@ -230,8 +220,8 @@ class CriteriaFilterProcessor {
"single-element of boundedBy filter must be type of GeoBox or Box");
GeoBox geoBBox;
if (value instanceof Box box) {
geoBBox = GeoBox.fromBox(box);
if (value instanceof Box) {
geoBBox = GeoBox.fromBox((Box) value);
} else {
geoBBox = (GeoBox) value;
}
@@ -254,7 +244,7 @@ class CriteriaFilterProcessor {
Assert.isTrue(allElementsAreOfType(values, GeoPoint.class) || allElementsAreOfType(values, String.class),
" both elements of boundedBy filter must be type of GeoPoint or text(format lat,lon or geohash)");
if (values[0] instanceof GeoPoint topLeft) {
if (values[0]instanceof GeoPoint topLeft) {
GeoPoint bottomRight = (GeoPoint) values[1];
queryBuilder.boundingBox(bb -> bb //
.tlbr(tlbr -> tlbr //
@@ -276,10 +266,7 @@ class CriteriaFilterProcessor {
.tlbr(tlbr -> tlbr //
.topLeft(glb -> {
if (isGeoHash) {
// although the builder in 8.13.2 supports geohash, the server throws an error, so we convert to a
// lat,lon string here
glb.text(Geohash.toLatLon(topLeft));
// glb.geohash(gh -> gh.geohash(topLeft));
glb.geohash(gh -> gh.geohash(topLeft));
} else {
glb.text(topLeft);
}
@@ -287,8 +274,7 @@ class CriteriaFilterProcessor {
}) //
.bottomRight(glb -> {
if (isGeoHash) {
glb.text(Geohash.toLatLon(bottomRight));
// glb.geohash(gh -> gh.geohash(bottomRight));
glb.geohash(gh -> gh.geohash(bottomRight));
} else {
glb.text(bottomRight);
}
@@ -16,27 +16,21 @@
package org.springframework.data.elasticsearch.client.elc;
import static org.springframework.data.elasticsearch.client.elc.Queries.*;
import static org.springframework.data.elasticsearch.client.elc.TypeUtils.*;
import static org.springframework.util.StringUtils.*;
import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch._types.query_dsl.ChildScoreMode;
import co.elastic.clients.elasticsearch._types.query_dsl.Operator;
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
import co.elastic.clients.elasticsearch.core.search.InnerHits;
import co.elastic.clients.json.JsonData;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import org.springframework.data.elasticsearch.annotations.FieldType;
import org.springframework.data.elasticsearch.core.query.Criteria;
import org.springframework.data.elasticsearch.core.query.Field;
import org.springframework.data.elasticsearch.core.query.HasChildQuery;
import org.springframework.data.elasticsearch.core.query.HasParentQuery;
import org.springframework.data.elasticsearch.core.query.InnerHitsQuery;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -48,7 +42,7 @@ import org.springframework.util.Assert;
* @author Ezequiel Antúnez Camacho
* @since 4.4
*/
class CriteriaQueryProcessor extends AbstractQueryProcessor {
class CriteriaQueryProcessor {
/**
* creates a query from the criteria
@@ -116,18 +110,11 @@ class CriteriaQueryProcessor extends AbstractQueryProcessor {
}
}
var filterQuery = CriteriaFilterProcessor.createQuery(criteria);
if (shouldQueries.isEmpty() && mustNotQueries.isEmpty() && mustQueries.isEmpty()) {
if (filterQuery.isEmpty()) {
return null;
}
// we need something to add the filter to
mustQueries.add(Query.of(qb -> qb.matchAll(m -> m)));
return null;
}
return new Query.Builder().bool(boolQueryBuilder -> {
Query query = new Query.Builder().bool(boolQueryBuilder -> {
if (!shouldQueries.isEmpty()) {
boolQueryBuilder.should(shouldQueries);
@@ -141,10 +128,10 @@ class CriteriaQueryProcessor extends AbstractQueryProcessor {
boolQueryBuilder.must(mustQueries);
}
filterQuery.ifPresent(boolQueryBuilder::filter);
return boolQueryBuilder;
}).build();
return query;
}
@Nullable
@@ -187,12 +174,6 @@ class CriteriaQueryProcessor extends AbstractQueryProcessor {
.scoreMode(ChildScoreMode.Avg));
}
if (criteria.isNegating() && criteria.isOr()) {
final Query query = queryBuilder.build();
queryBuilder = new Query.Builder();
queryBuilder.bool(mnqb -> mnqb.mustNot(query));
}
return queryBuilder.build();
}
@@ -246,57 +227,51 @@ class CriteriaQueryProcessor extends AbstractQueryProcessor {
queryBuilder.queryString(queryStringQuery(fieldName, '*' + searchText, true, boost));
break;
case EXPRESSION:
queryBuilder.queryString(queryStringQuery(fieldName, Objects.requireNonNull(value).toString(), boost));
queryBuilder.queryString(queryStringQuery(fieldName, value.toString(), boost));
break;
case LESS:
queryBuilder
.range(rb -> rb
.untyped(ut -> ut
.field(fieldName)
.lt(JsonData.of(value))
.boost(boost)));
queryBuilder //
.range(rb -> rb //
.field(fieldName) //
.lt(JsonData.of(value)) //
.boost(boost)); //
break;
case LESS_EQUAL:
queryBuilder
.range(rb -> rb
.untyped(ut -> ut
.field(fieldName)
.lte(JsonData.of(value))
.boost(boost)));
queryBuilder //
.range(rb -> rb //
.field(fieldName) //
.lte(JsonData.of(value)) //
.boost(boost)); //
break;
case GREATER:
queryBuilder
.range(rb -> rb
.untyped(ut -> ut
.field(fieldName)
.gt(JsonData.of(value))
.boost(boost)));
queryBuilder //
.range(rb -> rb //
.field(fieldName) //
.gt(JsonData.of(value)) //
.boost(boost)); //
break;
case GREATER_EQUAL:
queryBuilder
.range(rb -> rb
.untyped(ut -> ut
.field(fieldName)
.gte(JsonData.of(value))
.boost(boost)));
queryBuilder //
.range(rb -> rb //
.field(fieldName) //
.gte(JsonData.of(value)) //
.boost(boost)); //
break;
case BETWEEN:
Object[] ranges = (Object[]) value;
Assert.notNull(value, "value for a between condition must not be null");
queryBuilder
.range(rb -> rb
.untyped(ut -> {
ut.field(fieldName);
if (ranges[0] != null) {
ut.gte(JsonData.of(ranges[0]));
}
queryBuilder //
.range(rb -> {
rb.field(fieldName);
if (ranges[0] != null) {
rb.gte(JsonData.of(ranges[0]));
}
if (ranges[1] != null) {
ut.lte(JsonData.of(ranges[1]));
}
ut.boost(boost); //
return ut;
}));
if (ranges[1] != null) {
rb.lte(JsonData.of(ranges[1]));
}
rb.boost(boost); //
return rb;
}); //
break;
case FUZZY:
@@ -307,10 +282,10 @@ class CriteriaQueryProcessor extends AbstractQueryProcessor {
.boost(boost)); //
break;
case MATCHES:
queryBuilder.match(matchQuery(fieldName, Objects.requireNonNull(value).toString(), Operator.Or, boost));
queryBuilder.match(matchQuery(fieldName, value.toString(), Operator.Or, boost));
break;
case MATCHES_ALL:
queryBuilder.match(matchQuery(fieldName, Objects.requireNonNull(value).toString(), Operator.And, boost));
queryBuilder.match(matchQuery(fieldName, value.toString(), Operator.And, boost));
break;
case IN:
@@ -359,35 +334,9 @@ class CriteriaQueryProcessor extends AbstractQueryProcessor {
queryBuilder //
.regexp(rb -> rb //
.field(fieldName) //
.value(Objects.requireNonNull(value).toString()) //
.value(value.toString()) //
.boost(boost)); //
break;
case HAS_CHILD:
if (value instanceof HasChildQuery query) {
queryBuilder.hasChild(hcb -> hcb
.type(query.getType())
.query(getEsQuery(query.getQuery(), null))
.innerHits(getInnerHits(query.getInnerHitsQuery()))
.ignoreUnmapped(query.getIgnoreUnmapped())
.minChildren(query.getMinChildren())
.maxChildren(query.getMaxChildren())
.scoreMode(scoreMode(query.getScoreMode())));
} else {
throw new CriteriaQueryException("value for " + fieldName + " is not a has_child query");
}
break;
case HAS_PARENT:
if (value instanceof HasParentQuery query) {
queryBuilder.hasParent(hpb -> hpb
.parentType(query.getParentType())
.query(getEsQuery(query.getQuery(), null))
.innerHits(getInnerHits(query.getInnerHitsQuery()))
.ignoreUnmapped(query.getIgnoreUnmapped())
.score(query.getScore()));
} else {
throw new CriteriaQueryException("value for " + fieldName + " is not a has_parent query");
}
break;
default:
throw new CriteriaQueryException("Could not build query for " + entry);
}
@@ -410,7 +359,7 @@ class CriteriaQueryProcessor extends AbstractQueryProcessor {
if (item != null) {
if (!sb.isEmpty()) {
if (sb.length() > 0) {
sb.append(' ');
}
sb.append('"');
@@ -442,19 +391,4 @@ class CriteriaQueryProcessor extends AbstractQueryProcessor {
return sb.toString();
}
/**
* Convert a spring-data-elasticsearch {@literal inner_hits} to an Elasticsearch {@literal inner_hits} query.
*
* @param query spring-data-elasticsearch {@literal inner_hits}.
* @return an Elasticsearch {@literal inner_hits} query.
*/
@Nullable
private static InnerHits getInnerHits(@Nullable InnerHitsQuery query) {
if (query == null) {
return null;
}
return InnerHits.of(iqb -> iqb.from(query.getFrom()).size(query.getSize()).name(query.getName()));
}
}
@@ -49,8 +49,6 @@ import org.springframework.util.Assert;
* {@link org.springframework.data.elasticsearch.core.document.Document}
*
* @author Peter-Josef Meisch
* @author Haibo Liu
* @author Mohamed El Harrougui
* @since 4.4
*/
final class DocumentAdapters {
@@ -75,7 +73,7 @@ final class DocumentAdapters {
Map<String, SearchDocumentResponse> innerHits = new LinkedHashMap<>();
hit.innerHits().forEach((name, innerHitsResult) -> {
// noinspection ReturnOfNull
innerHits.put(name, SearchDocumentResponseBuilder.from(innerHitsResult.hits(), null, null, null, 0, null, null,
innerHits.put(name, SearchDocumentResponseBuilder.from(innerHitsResult.hits(), null, null, null, null,
searchDocument -> null, jsonpMapper));
});
@@ -40,7 +40,6 @@ import org.springframework.data.elasticsearch.VersionConflictException;
* appropriate: any other exception may have resulted from user code, and should not be translated.
*
* @author Peter-Josef Meisch
* @author Junghoon Ban
* @since 4.4
*/
public class ElasticsearchExceptionTranslator implements PersistenceExceptionTranslator {
@@ -60,7 +59,7 @@ public class ElasticsearchExceptionTranslator implements PersistenceExceptionTra
*/
public RuntimeException translateException(Throwable throwable) {
RuntimeException runtimeException = throwable instanceof RuntimeException ex ? ex
RuntimeException runtimeException = throwable instanceof RuntimeException ? (RuntimeException) throwable
: new RuntimeException(throwable.getMessage(), throwable);
RuntimeException potentiallyTranslatedException = translateExceptionIfPossible(runtimeException);
@@ -23,15 +23,12 @@ 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.elasticsearch.sql.ElasticsearchSqlClient;
import co.elastic.clients.elasticsearch.sql.QueryResponse;
import co.elastic.clients.json.JsonpMapper;
import co.elastic.clients.transport.Version;
import java.io.IOException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@@ -53,12 +50,18 @@ import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverte
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.*;
import org.springframework.data.elasticsearch.core.query.BaseQueryBuilder;
import org.springframework.data.elasticsearch.core.query.BulkOptions;
import org.springframework.data.elasticsearch.core.query.ByQueryResponse;
import org.springframework.data.elasticsearch.core.query.IndexQuery;
import org.springframework.data.elasticsearch.core.query.MoreLikeThisQuery;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.core.query.SearchTemplateQuery;
import org.springframework.data.elasticsearch.core.query.UpdateQuery;
import org.springframework.data.elasticsearch.core.query.UpdateResponse;
import org.springframework.data.elasticsearch.core.reindex.ReindexRequest;
import org.springframework.data.elasticsearch.core.reindex.ReindexResponse;
import org.springframework.data.elasticsearch.core.script.Script;
import org.springframework.data.elasticsearch.core.sql.SqlResponse;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -69,7 +72,6 @@ import org.springframework.util.Assert;
* @author Peter-Josef Meisch
* @author Hamid Rahimi
* @author Illia Ulianov
* @author Haibo Liu
* @since 4.4
*/
public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
@@ -77,7 +79,6 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
private static final Log LOGGER = LogFactory.getLog(ElasticsearchTemplate.class);
private final ElasticsearchClient client;
private final ElasticsearchSqlClient sqlClient;
private final RequestConverter requestConverter;
private final ResponseConverter responseConverter;
private final JsonpMapper jsonpMapper;
@@ -89,7 +90,6 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
Assert.notNull(client, "client must not be null");
this.client = client;
this.sqlClient = client.sql();
this.jsonpMapper = client._transport().jsonpMapper();
requestConverter = new RequestConverter(elasticsearchConverter, jsonpMapper);
responseConverter = new ResponseConverter(jsonpMapper);
@@ -102,7 +102,6 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
Assert.notNull(client, "client must not be null");
this.client = client;
this.sqlClient = client.sql();
this.jsonpMapper = client._transport().jsonpMapper();
requestConverter = new RequestConverter(elasticsearchConverter, jsonpMapper);
responseConverter = new ResponseConverter(jsonpMapper);
@@ -176,11 +175,6 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
doBulkOperation(queries, bulkOptions, index);
}
@Override
public ByQueryResponse delete(DeleteQuery query, Class<?> clazz) {
return delete(query, clazz, getIndexCoordinatesFor(clazz));
}
@Override
public ByQueryResponse delete(Query query, Class<?> clazz, IndexCoordinates index) {
@@ -194,18 +188,6 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
return responseConverter.byQueryResponse(response);
}
@Override
public ByQueryResponse delete(DeleteQuery query, Class<?> clazz, IndexCoordinates index) {
Assert.notNull(query, "query must not be null");
DeleteByQueryRequest request = requestConverter.documentDeleteByQueryRequest(query, routingResolver.getRouting(),
clazz, index, getRefreshPolicy());
DeleteByQueryResponse response = execute(client -> client.deleteByQuery(request));
return responseConverter.byQueryResponse(response);
}
@Override
public UpdateResponse update(UpdateQuery updateQuery, IndexCoordinates index) {
@@ -455,10 +437,13 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
Assert.notNull(queries, "queries must not be null");
Assert.notNull(clazz, "clazz must not be null");
int size = queries.size();
List<MultiSearchQueryParameter> multiSearchQueryParameters = new ArrayList<>(queries.size());
for (Query query : queries) {
multiSearchQueryParameters.add(new MultiSearchQueryParameter(query, clazz, getIndexCoordinatesFor(clazz)));
}
// noinspection unchecked
return multiSearch(queries, Collections.nCopies(size, clazz), Collections.nCopies(size, index))
.stream().map(searchHits -> (SearchHits<T>) searchHits)
return doMultiSearch(multiSearchQueryParameters).stream().map(searchHits -> (SearchHits<T>) searchHits)
.collect(Collectors.toList());
}
@@ -469,7 +454,14 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
Assert.notNull(classes, "classes must not be null");
Assert.isTrue(queries.size() == classes.size(), "queries and classes must have the same size");
return multiSearch(queries, classes, classes.stream().map(this::getIndexCoordinatesFor).toList());
List<MultiSearchQueryParameter> multiSearchQueryParameters = new ArrayList<>(queries.size());
Iterator<Class<?>> it = classes.iterator();
for (Query query : queries) {
Class<?> clazz = it.next();
multiSearchQueryParameters.add(new MultiSearchQueryParameter(query, clazz, getIndexCoordinatesFor(clazz)));
}
return doMultiSearch(multiSearchQueryParameters);
}
@Override
@@ -481,7 +473,14 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
Assert.notNull(index, "index must not be null");
Assert.isTrue(queries.size() == classes.size(), "queries and classes must have the same size");
return multiSearch(queries, classes, Collections.nCopies(queries.size(), index));
List<MultiSearchQueryParameter> multiSearchQueryParameters = new ArrayList<>(queries.size());
Iterator<Class<?>> it = classes.iterator();
for (Query query : queries) {
Class<?> clazz = it.next();
multiSearchQueryParameters.add(new MultiSearchQueryParameter(query, clazz, index));
}
return doMultiSearch(multiSearchQueryParameters);
}
@Override
@@ -498,50 +497,16 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
Iterator<Class<?>> it = classes.iterator();
Iterator<IndexCoordinates> indexesIt = indexes.iterator();
Assert.isTrue(!queries.isEmpty(), "queries should have at least 1 query");
boolean isSearchTemplateQuery = queries.get(0) instanceof SearchTemplateQuery;
for (Query query : queries) {
Assert.isTrue((query instanceof SearchTemplateQuery) == isSearchTemplateQuery,
"SearchTemplateQuery can't be mixed with other types of query in multiple search");
Class<?> clazz = it.next();
IndexCoordinates index = indexesIt.next();
multiSearchQueryParameters.add(new MultiSearchQueryParameter(query, clazz, index));
}
return multiSearch(multiSearchQueryParameters, isSearchTemplateQuery);
}
private List<SearchHits<?>> multiSearch(List<MultiSearchQueryParameter> multiSearchQueryParameters,
boolean isSearchTemplateQuery) {
return isSearchTemplateQuery ? doMultiTemplateSearch(multiSearchQueryParameters.stream()
.map(p -> new MultiSearchTemplateQueryParameter((SearchTemplateQuery) p.query, p.clazz, p.index))
.toList())
: doMultiSearch(multiSearchQueryParameters);
}
private List<SearchHits<?>> doMultiTemplateSearch(
List<MultiSearchTemplateQueryParameter> mSearchTemplateQueryParameters) {
MsearchTemplateRequest request = requestConverter.searchMsearchTemplateRequest(mSearchTemplateQueryParameters,
routingResolver.getRouting());
MsearchTemplateResponse<EntityAsMap> response = execute(
client -> client.msearchTemplate(request, EntityAsMap.class));
List<MultiSearchResponseItem<EntityAsMap>> responseItems = response.responses();
Assert.isTrue(mSearchTemplateQueryParameters.size() == responseItems.size(),
"number of response items does not match number of requests");
int size = mSearchTemplateQueryParameters.size();
List<Class<?>> classes = mSearchTemplateQueryParameters
.stream().map(MultiSearchTemplateQueryParameter::clazz).collect(Collectors.toList());
List<IndexCoordinates> indices = mSearchTemplateQueryParameters
.stream().map(MultiSearchTemplateQueryParameter::index).collect(Collectors.toList());
return getSearchHitsFromMsearchResponse(size, classes, indices, responseItems);
return doMultiSearch(multiSearchQueryParameters);
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private List<SearchHits<?>> doMultiSearch(List<MultiSearchQueryParameter> multiSearchQueryParameters) {
MsearchRequest request = requestConverter.searchMsearchRequest(multiSearchQueryParameters,
@@ -553,37 +518,22 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
Assert.isTrue(multiSearchQueryParameters.size() == responseItems.size(),
"number of response items does not match number of requests");
int size = multiSearchQueryParameters.size();
List<Class<?>> classes = multiSearchQueryParameters
.stream().map(MultiSearchQueryParameter::clazz).collect(Collectors.toList());
List<IndexCoordinates> indices = multiSearchQueryParameters
.stream().map(MultiSearchQueryParameter::index).collect(Collectors.toList());
List<SearchHits<?>> searchHitsList = new ArrayList<>(multiSearchQueryParameters.size());
return getSearchHitsFromMsearchResponse(size, classes, indices, responseItems);
}
/**
* {@link MsearchResponse} and {@link MsearchTemplateResponse} share the same {@link MultiSearchResponseItem}
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
private List<SearchHits<?>> getSearchHitsFromMsearchResponse(int size, List<Class<?>> classes,
List<IndexCoordinates> indices, List<MultiSearchResponseItem<EntityAsMap>> responseItems) {
List<SearchHits<?>> searchHitsList = new ArrayList<>(size);
Iterator<Class<?>> clazzIter = classes.iterator();
Iterator<IndexCoordinates> indexIter = indices.iterator();
Iterator<MultiSearchQueryParameter> queryIterator = multiSearchQueryParameters.iterator();
Iterator<MultiSearchResponseItem<EntityAsMap>> responseIterator = responseItems.iterator();
while (clazzIter.hasNext() && indexIter.hasNext()) {
while (queryIterator.hasNext()) {
MultiSearchQueryParameter queryParameter = queryIterator.next();
MultiSearchResponseItem<EntityAsMap> responseItem = responseIterator.next();
if (responseItem.isResult()) {
Class clazz = clazzIter.next();
IndexCoordinates index = indexIter.next();
Class clazz = queryParameter.clazz;
ReadDocumentCallback<?> documentCallback = new ReadDocumentCallback<>(elasticsearchConverter, clazz,
index);
queryParameter.index);
SearchDocumentResponseCallback<SearchHits<?>> callback = new ReadSearchDocumentResponseCallback<>(clazz,
index);
queryParameter.index);
SearchHits<?> searchHits = callback.doWith(
SearchDocumentResponseBuilder.from(responseItem.result(), getEntityCreator(documentCallback), jsonpMapper));
@@ -591,8 +541,8 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
searchHitsList.add(searchHits);
} else {
if (LOGGER.isWarnEnabled()) {
LOGGER.warn(String.format("multisearch response contains failure: %s",
responseItem.failure().error().reason()));
LOGGER
.warn(String.format("multisearch responsecontains failure: {}", responseItem.failure().error().reason()));
}
}
}
@@ -606,12 +556,6 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
record MultiSearchQueryParameter(Query query, Class<?> clazz, IndexCoordinates index) {
}
/**
* value class combining the information needed for a single query in a template multisearch request.
*/
record MultiSearchTemplateQueryParameter(SearchTemplateQuery query, Class<?> clazz, IndexCoordinates index) {
}
@Override
public String openPointInTime(IndexCoordinates index, Duration keepAlive, Boolean ignoreUnavailable) {
@@ -662,19 +606,6 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
DeleteScriptRequest request = requestConverter.scriptDelete(name);
return execute(client -> client.deleteScript(request)).acknowledged();
}
@Override
public SqlResponse search(SqlQuery query) {
Assert.notNull(query, "Query must not be null.");
try {
QueryResponse response = sqlClient.query(requestConverter.sqlQueryRequest(query));
return responseConverter.sqlResponse(response);
} catch (IOException e) {
throw exceptionTranslator.translateException(e);
}
}
// endregion
// region client callback
@@ -35,17 +35,14 @@ import org.springframework.util.StringUtils;
* {@link co.elastic.clients.elasticsearch.core.search.Highlight}.
*
* @author Peter-Josef Meisch
* @author Haibo Liu
* @since 4.4
*/
class HighlightQueryBuilder {
private final MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext;
private final RequestConverter requestConverter;
HighlightQueryBuilder(
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext, RequestConverter requestConverter) {
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext) {
this.mappingContext = mappingContext;
this.requestConverter = requestConverter;
}
public co.elastic.clients.elasticsearch.core.search.Highlight getHighlight(Highlight highlight,
@@ -55,7 +52,7 @@ class HighlightQueryBuilder {
// in the old implementation we could use one addParameters method, but in the new Elasticsearch client
// the builder for highlight and highlightfield share no code
addParameters(highlight.getParameters(), highlightBuilder, type);
addParameters(highlight.getParameters(), highlightBuilder);
for (HighlightField highlightField : highlight.getFields()) {
String mappedName = mapFieldName(highlightField.getName(), type);
@@ -72,7 +69,7 @@ class HighlightQueryBuilder {
* the builder for highlight and highlight fields don't share code, so we have these two methods here that basically are almost copies
*/
private void addParameters(HighlightParameters parameters,
co.elastic.clients.elasticsearch.core.search.Highlight.Builder builder, @Nullable Class<?> type) {
co.elastic.clients.elasticsearch.core.search.Highlight.Builder builder) {
if (StringUtils.hasLength(parameters.getBoundaryChars())) {
builder.boundaryChars(parameters.getBoundaryChars());
@@ -106,10 +103,6 @@ class HighlightQueryBuilder {
builder.numberOfFragments(parameters.getNumberOfFragments());
}
if (parameters.getHighlightQuery() != null) {
builder.highlightQuery(requestConverter.getQuery(parameters.getHighlightQuery(), type));
}
if (StringUtils.hasLength(parameters.getOrder())) {
builder.order(highlighterOrder(parameters.getOrder()));
}
@@ -181,10 +174,6 @@ class HighlightQueryBuilder {
builder.numberOfFragments(parameters.getNumberOfFragments());
}
if (parameters.getHighlightQuery() != null) {
builder.highlightQuery(requestConverter.getQuery(parameters.getHighlightQuery(), type));
}
if (StringUtils.hasLength(parameters.getOrder())) {
builder.order(highlighterOrder(parameters.getOrder()));
}
@@ -21,12 +21,13 @@ import co.elastic.clients.elasticsearch.indices.*;
import co.elastic.clients.transport.ElasticsearchTransport;
import co.elastic.clients.transport.endpoints.BooleanResponse;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.elasticsearch.UncategorizedElasticsearchException;
@@ -45,8 +46,6 @@ import org.springframework.data.elasticsearch.core.index.GetIndexTemplateRequest
import org.springframework.data.elasticsearch.core.index.GetTemplateRequest;
import org.springframework.data.elasticsearch.core.index.PutIndexTemplateRequest;
import org.springframework.data.elasticsearch.core.index.PutTemplateRequest;
import org.springframework.data.elasticsearch.core.mapping.Alias;
import org.springframework.data.elasticsearch.core.mapping.CreateIndexSettings;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.lang.Nullable;
@@ -61,6 +60,8 @@ import org.springframework.util.Assert;
public class IndicesTemplate extends ChildTemplate<ElasticsearchTransport, ElasticsearchIndicesClient>
implements IndexOperations {
private static final Log LOGGER = LogFactory.getLog(IndicesTemplate.class);
// we need a cluster client as well because ES has put some methods from the indices API into the cluster client
// (component templates)
private final ClusterTemplate clusterTemplate;
@@ -84,7 +85,7 @@ public class IndicesTemplate extends ChildTemplate<ElasticsearchTransport, Elast
}
public IndicesTemplate(ElasticsearchIndicesClient client, ClusterTemplate clusterTemplate,
ElasticsearchConverter elasticsearchConverter, IndexCoordinates boundIndex) {
ElasticsearchConverter elasticsearchConverter, IndexCoordinates boundIndex) {
super(client, elasticsearchConverter);
Assert.notNull(clusterTemplate, "cluster must not be null");
@@ -136,14 +137,11 @@ public class IndicesTemplate extends ChildTemplate<ElasticsearchTransport, Elast
protected boolean doCreate(IndexCoordinates indexCoordinates, Map<String, Object> settings,
@Nullable Document mapping) {
Set<Alias> aliases = (boundClass != null) ? getAliasesFor(boundClass) : new HashSet<>();
CreateIndexSettings indexSettings = CreateIndexSettings.builder(indexCoordinates)
.withAliases(aliases)
.withSettings(settings)
.withMapping(mapping)
.build();
CreateIndexRequest createIndexRequest = requestConverter.indicesCreateRequest(indexSettings);
Assert.notNull(indexCoordinates, "indexCoordinates must not be null");
Assert.notNull(settings, "settings must not be null");
CreateIndexRequest createIndexRequest = requestConverter.indicesCreateRequest(indexCoordinates, settings, mapping);
CreateIndexResponse createIndexResponse = execute(client -> client.create(createIndexRequest));
return Boolean.TRUE.equals(createIndexResponse.acknowledged());
}
@@ -238,7 +236,8 @@ public class IndicesTemplate extends ChildTemplate<ElasticsearchTransport, Elast
GetMappingRequest getMappingRequest = requestConverter.indicesGetMappingRequest(indexCoordinates);
GetMappingResponse getMappingResponse = execute(client -> client.getMapping(getMappingRequest));
return responseConverter.indicesGetMapping(getMappingResponse, indexCoordinates);
Document mappingResponse = responseConverter.indicesGetMapping(getMappingResponse, indexCoordinates);
return mappingResponse;
}
@Override
@@ -450,14 +449,5 @@ public class IndicesTemplate extends ChildTemplate<ElasticsearchTransport, Elast
public IndexCoordinates getIndexCoordinatesFor(Class<?> clazz) {
return getRequiredPersistentEntity(clazz).getIndexCoordinates();
}
/**
* Get the {@link Alias} of the provided class.
*
* @param clazz provided class that can be used to extract aliases.
*/
public Set<Alias> getAliasesFor(Class<?> clazz) {
return getRequiredPersistentEntity(clazz).getAliases();
}
// endregion
}
@@ -19,6 +19,7 @@ import co.elastic.clients.json.JsonpMapper;
import jakarta.json.stream.JsonGenerator;
import java.io.ByteArrayOutputStream;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import org.apache.commons.logging.Log;
@@ -43,13 +44,17 @@ final class JsonUtils {
mapper.serialize(object, generator);
generator.close();
String json = "{}";
json = baos.toString(StandardCharsets.UTF_8);
try {
json = baos.toString("UTF-8");
} catch (UnsupportedEncodingException e) {
LOGGER.warn("could not read json", e);
}
return json;
}
@Nullable
public static String queryToJson(@Nullable co.elastic.clients.elasticsearch._types.query_dsl.Query query,
JsonpMapper mapper) {
public static String queryToJson(@Nullable co.elastic.clients.elasticsearch._types.query_dsl.Query query, JsonpMapper mapper) {
if (query == null) {
return null;
@@ -15,7 +15,7 @@
*/
package org.springframework.data.elasticsearch.client.elc;
import co.elastic.clients.elasticsearch._types.KnnSearch;
import co.elastic.clients.elasticsearch._types.KnnQuery;
import co.elastic.clients.elasticsearch._types.SortOptions;
import co.elastic.clients.elasticsearch._types.aggregations.Aggregation;
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
@@ -29,6 +29,7 @@ import java.util.List;
import java.util.Map;
import org.springframework.data.elasticsearch.core.query.BaseQuery;
import org.springframework.data.elasticsearch.core.query.ScriptedField;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -38,7 +39,6 @@ import org.springframework.util.Assert;
*
* @author Peter-Josef Meisch
* @author Sascha Woo
* @author Haibo Liu
* @since 4.4
*/
public class NativeQuery extends BaseQuery {
@@ -53,7 +53,7 @@ public class NativeQuery extends BaseQuery {
private List<SortOptions> sortOptions = Collections.emptyList();
private Map<String, JsonData> searchExtensions = Collections.emptyMap();
@Nullable private List<KnnSearch> knnSearches = Collections.emptyList();
@Nullable private KnnQuery knnQuery;
public NativeQuery(NativeQueryBuilder builder) {
super(builder);
@@ -70,7 +70,7 @@ public class NativeQuery extends BaseQuery {
"Cannot add an NativeQuery in a NativeQuery");
}
this.springDataQuery = builder.getSpringDataQuery();
this.knnSearches = builder.getKnnSearches();
this.knnQuery = builder.getKnnQuery();
}
public NativeQuery(@Nullable Query query) {
@@ -122,11 +122,11 @@ public class NativeQuery extends BaseQuery {
}
/**
* @since 5.3.1
* @since 5.1
*/
@Nullable
public List<KnnSearch> getKnnSearches() {
return knnSearches;
public KnnQuery getKnnQuery() {
return knnQuery;
}
@Nullable
@@ -16,7 +16,6 @@
package org.springframework.data.elasticsearch.client.elc;
import co.elastic.clients.elasticsearch._types.KnnQuery;
import co.elastic.clients.elasticsearch._types.KnnSearch;
import co.elastic.clients.elasticsearch._types.SortOptions;
import co.elastic.clients.elasticsearch._types.aggregations.Aggregation;
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
@@ -27,7 +26,6 @@ import co.elastic.clients.util.ObjectBuilder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -40,7 +38,6 @@ import org.springframework.util.Assert;
/**
* @author Peter-Josef Meisch
* @author Sascha Woo
* @author Haibo Liu
* @since 4.4
*/
public class NativeQueryBuilder extends BaseQueryBuilder<NativeQuery, NativeQueryBuilder> {
@@ -50,12 +47,11 @@ public class NativeQueryBuilder extends BaseQueryBuilder<NativeQuery, NativeQuer
private final Map<String, Aggregation> aggregations = new LinkedHashMap<>();
@Nullable private Suggester suggester;
@Nullable private FieldCollapse fieldCollapse;
private final List<SortOptions> sortOptions = new ArrayList<>();
private final Map<String, JsonData> searchExtensions = new LinkedHashMap<>();
private List<SortOptions> sortOptions = new ArrayList<>();
private Map<String, JsonData> searchExtensions = new LinkedHashMap<>();
@Nullable private org.springframework.data.elasticsearch.core.query.Query springDataQuery;
@Nullable private KnnQuery knnQuery;
@Nullable private List<KnnSearch> knnSearches = Collections.emptyList();
public NativeQueryBuilder() {}
@@ -96,14 +92,6 @@ public class NativeQueryBuilder extends BaseQueryBuilder<NativeQuery, NativeQuer
return knnQuery;
}
/**
* @since 5.3.1
*/
@Nullable
public List<KnnSearch> getKnnSearches() {
return knnSearches;
}
@Nullable
public org.springframework.data.elasticsearch.core.query.Query getSpringDataQuery() {
return springDataQuery;
@@ -214,30 +202,13 @@ public class NativeQueryBuilder extends BaseQueryBuilder<NativeQuery, NativeQuer
}
/**
* @since 5.4
* @since 5.1
*/
public NativeQueryBuilder withKnnSearches(List<KnnSearch> knnSearches) {
this.knnSearches = knnSearches;
public NativeQueryBuilder withKnnQuery(KnnQuery knnQuery) {
this.knnQuery = knnQuery;
return this;
}
/**
* @since 5.4
*/
public NativeQueryBuilder withKnnSearches(Function<KnnSearch.Builder, ObjectBuilder<KnnSearch>> fn) {
Assert.notNull(fn, "fn must not be null");
return withKnnSearches(fn.apply(new KnnSearch.Builder()).build());
}
/**
* @since 5.4
*/
public NativeQueryBuilder withKnnSearches(KnnSearch knnSearch) {
return withKnnSearches(List.of(knnSearch));
}
public NativeQuery build() {
Assert.isTrue(query == null || springDataQuery == null, "Cannot have both a native query and a Spring Data query");
return new NativeQuery(this);
@@ -0,0 +1,174 @@
/*
* Copyright 2022-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.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;
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
import co.elastic.clients.elasticsearch._types.query_dsl.QueryStringQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.TermQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.WildcardQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.WrapperQuery;
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;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Utility class simplifying the creation of some more complex queries and type.
*
* @author Peter-Josef Meisch
* @since 4.4
* @deprecated since 5.1, use {@link Queries} instead.
*/
@Deprecated(forRemoval = true)
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) {
Assert.notNull(fieldName, "fieldName must not be null");
Assert.notNull(query, "query must not be null");
return MatchQuery.of(mb -> mb.field(fieldName).query(FieldValue.of(query)).operator(operator).boost(boost));
}
public static Query matchQueryAsQuery(String fieldName, String query, @Nullable Operator operator,
@Nullable Float boost) {
Function<Query.Builder, ObjectBuilder<Query>> builder = b -> b.match(matchQuery(fieldName, query, operator, boost));
return builder.apply(new Query.Builder()).build();
}
public static MatchAllQuery matchAllQuery() {
return MatchAllQuery.of(b -> b);
}
public static Query matchAllQueryAsQuery() {
Function<Query.Builder, ObjectBuilder<Query>> builder = b -> b.matchAll(matchAllQuery());
return builder.apply(new Query.Builder()).build();
}
public static QueryStringQuery queryStringQuery(String fieldName, String query, @Nullable Float boost) {
return queryStringQuery(fieldName, query, null, null, boost);
}
public static QueryStringQuery queryStringQuery(String fieldName, String query, Operator defaultOperator,
@Nullable Float boost) {
return queryStringQuery(fieldName, query, null, defaultOperator, boost);
}
public static QueryStringQuery queryStringQuery(String fieldName, String query, @Nullable Boolean analyzeWildcard,
@Nullable Float boost) {
return queryStringQuery(fieldName, query, analyzeWildcard, null, boost);
}
public static QueryStringQuery queryStringQuery(String fieldName, String query, @Nullable Boolean analyzeWildcard,
@Nullable Operator defaultOperator, @Nullable Float boost) {
Assert.notNull(fieldName, "fieldName must not be null");
Assert.notNull(query, "query must not be null");
return QueryStringQuery.of(qs -> qs.fields(fieldName).query(query).analyzeWildcard(analyzeWildcard)
.defaultOperator(defaultOperator).boost(boost));
}
public static TermQuery termQuery(String fieldName, String value) {
Assert.notNull(fieldName, "fieldName must not be null");
Assert.notNull(value, "value must not be null");
return TermQuery.of(t -> t.field(fieldName).value(FieldValue.of(value)));
}
public static Query termQueryAsQuery(String fieldName, String value) {
Function<Query.Builder, ObjectBuilder<Query>> builder = q -> q.term(termQuery(fieldName, value));
return builder.apply(new Query.Builder()).build();
}
public static WildcardQuery wildcardQuery(String field, String value) {
Assert.notNull(field, "field must not be null");
Assert.notNull(value, "value must not be null");
return WildcardQuery.of(w -> w.field(field).wildcard(value));
}
public static Query wildcardQueryAsQuery(String field, String value) {
Function<Query.Builder, ObjectBuilder<Query>> builder = q -> q.wildcard(wildcardQuery(field, value));
return builder.apply(new Query.Builder()).build();
}
public static Query wrapperQueryAsQuery(String query) {
Function<Query.Builder, ObjectBuilder<Query>> builder = q -> q.wrapper(wrapperQuery(query));
return builder.apply(new Query.Builder()).build();
}
public static WrapperQuery wrapperQuery(String query) {
Assert.notNull(query, "query must not be null");
String encodedValue = Base64.getEncoder().encodeToString(query.getBytes(StandardCharsets.UTF_8));
return WrapperQuery.of(wq -> wq.query(encodedValue));
}
public static LatLonGeoLocation latLon(GeoPoint geoPoint) {
Assert.notNull(geoPoint, "geoPoint must not be null");
return latLon(geoPoint.getLat(), geoPoint.getLon());
}
public static LatLonGeoLocation latLon(double lat, double lon) {
return LatLonGeoLocation.of(_0 -> _0.lat(lat).lon(lon));
}
}
@@ -36,7 +36,6 @@ import org.springframework.util.Assert;
* Reactive version of {@link co.elastic.clients.elasticsearch.ElasticsearchClient}.
*
* @author Peter-Josef Meisch
* @author maryantocinn
* @since 4.4
*/
public class ReactiveElasticsearchClient extends ApiClient<ElasticsearchTransport, ReactiveElasticsearchClient>
@@ -70,10 +69,6 @@ public class ReactiveElasticsearchClient extends ApiClient<ElasticsearchTranspor
return new ReactiveElasticsearchIndicesClient(transport, transportOptions);
}
public ReactiveElasticsearchSqlClient sql() {
return new ReactiveElasticsearchSqlClient(transport, transportOptions);
}
// endregion
// region info
@@ -127,7 +122,7 @@ public class ReactiveElasticsearchClient extends ApiClient<ElasticsearchTranspor
// java.lang.Class<TDocument>)
// noinspection unchecked
JsonEndpoint<GetRequest, GetResponse<T>, ErrorResponse> endpoint = (JsonEndpoint<GetRequest, GetResponse<T>, ErrorResponse>) GetRequest._ENDPOINT;
endpoint = new EndpointWithResponseMapperAttr<>(endpoint, "co.elastic.clients:Deserializer:_global.get.Response.TDocument",
endpoint = new EndpointWithResponseMapperAttr<>(endpoint, "co.elastic.clients:Deserializer:_global.get.TDocument",
getDeserializer(tClass));
return Mono.fromFuture(transport.performRequestAsync(request, endpoint, transportOptions));
@@ -146,7 +141,7 @@ public class ReactiveElasticsearchClient extends ApiClient<ElasticsearchTranspor
// noinspection unchecked
JsonEndpoint<UpdateRequest<?, ?>, UpdateResponse<T>, ErrorResponse> endpoint = new EndpointWithResponseMapperAttr(
UpdateRequest._ENDPOINT, "co.elastic.clients:Deserializer:_global.update.Response.TDocument",
UpdateRequest._ENDPOINT, "co.elastic.clients:Deserializer:_global.update.TDocument",
this.getDeserializer(clazz));
return Mono.fromFuture(transport.performRequestAsync(request, endpoint, this.transportOptions));
}
@@ -172,7 +167,7 @@ public class ReactiveElasticsearchClient extends ApiClient<ElasticsearchTranspor
// 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.Response.TDocument",
endpoint = new EndpointWithResponseMapperAttr<>(endpoint, "co.elastic.clients:Deserializer:_global.mget.TDocument",
this.getDeserializer(clazz));
return Mono.fromFuture(transport.performRequestAsync(request, endpoint, transportOptions));
@@ -228,26 +223,6 @@ public class ReactiveElasticsearchClient extends ApiClient<ElasticsearchTranspor
return deleteByQuery(fn.apply(new DeleteByQueryRequest.Builder()).build());
}
/**
* @since 5.4
*/
public Mono<CountResponse> count(CountRequest request) {
Assert.notNull(request, "request must not be null");
return Mono.fromFuture(transport.performRequestAsync(request, CountRequest._ENDPOINT, transportOptions));
}
/**
* @since 5.4
*/
public Mono<CountResponse> count(Function<CountRequest.Builder, ObjectBuilder<CountRequest>> fn) {
Assert.notNull(fn, "fn must not be null");
return count(fn.apply(new CountRequest.Builder()).build());
}
// endregion
// region search
@@ -303,7 +278,7 @@ public class ReactiveElasticsearchClient extends ApiClient<ElasticsearchTranspor
// noinspection unchecked
JsonEndpoint<ScrollRequest, ScrollResponse<T>, ErrorResponse> endpoint = (JsonEndpoint<ScrollRequest, ScrollResponse<T>, ErrorResponse>) ScrollRequest._ENDPOINT;
endpoint = new EndpointWithResponseMapperAttr<>(endpoint,
"co.elastic.clients:Deserializer:_global.scroll.Response.TDocument", getDeserializer(tDocumentClass));
"co.elastic.clients:Deserializer:_global.scroll.TDocument", getDeserializer(tDocumentClass));
return Mono.fromFuture(transport.performRequestAsync(request, endpoint, transportOptions));
}
@@ -1,72 +0,0 @@
/*
* Copyright 2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.client.elc;
import co.elastic.clients.ApiClient;
import co.elastic.clients.elasticsearch._types.ElasticsearchException;
import co.elastic.clients.elasticsearch.sql.QueryRequest;
import co.elastic.clients.elasticsearch.sql.QueryResponse;
import co.elastic.clients.transport.ElasticsearchTransport;
import co.elastic.clients.transport.TransportOptions;
import co.elastic.clients.util.ObjectBuilder;
import reactor.core.publisher.Mono;
import java.io.IOException;
import java.util.function.Function;
import org.jetbrains.annotations.Nullable;
/**
* Reactive version of {@link co.elastic.clients.elasticsearch.sql.ElasticsearchSqlClient}.
*
* @author Aouichaoui Youssef
* @since 5.4
*/
public class ReactiveElasticsearchSqlClient extends ApiClient<ElasticsearchTransport, ReactiveElasticsearchSqlClient> {
public ReactiveElasticsearchSqlClient(ElasticsearchTransport transport, @Nullable TransportOptions transportOptions) {
super(transport, transportOptions);
}
@Override
public ReactiveElasticsearchSqlClient withTransportOptions(@Nullable TransportOptions transportOptions) {
return new ReactiveElasticsearchSqlClient(transport, transportOptions);
}
/**
* Executes a SQL request
*
* @param fn a function that initializes a builder to create the {@link QueryRequest}.
*/
public final Mono<QueryResponse> query(Function<QueryRequest.Builder, ObjectBuilder<QueryRequest>> fn)
throws IOException, ElasticsearchException {
return query(fn.apply(new QueryRequest.Builder()).build());
}
/**
* Executes a SQL request.
*/
public Mono<QueryResponse> query(QueryRequest query) {
return Mono.fromFuture(transport.performRequestAsync(query, QueryRequest._ENDPOINT, transportOptions));
}
/**
* Executes a SQL request.
*/
public Mono<QueryResponse> query() {
return Mono.fromFuture(
transport.performRequestAsync(new QueryRequest.Builder().build(), QueryRequest._ENDPOINT, transportOptions));
}
}
@@ -57,12 +57,17 @@ 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;
import org.springframework.data.elasticsearch.core.query.*;
import org.springframework.data.elasticsearch.core.query.BaseQuery;
import org.springframework.data.elasticsearch.core.query.BaseQueryBuilder;
import org.springframework.data.elasticsearch.core.query.BulkOptions;
import org.springframework.data.elasticsearch.core.query.ByQueryResponse;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.core.query.SearchTemplateQuery;
import org.springframework.data.elasticsearch.core.query.UpdateQuery;
import org.springframework.data.elasticsearch.core.query.UpdateResponse;
import org.springframework.data.elasticsearch.core.reindex.ReindexRequest;
import org.springframework.data.elasticsearch.core.reindex.ReindexResponse;
import org.springframework.data.elasticsearch.core.script.Script;
import org.springframework.data.elasticsearch.core.sql.SqlResponse;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
@@ -74,7 +79,6 @@ import org.springframework.util.StringUtils;
*
* @author Peter-Josef Meisch
* @author Illia Ulianov
* @author Junghoon Ban
* @since 4.4
*/
public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearchTemplate {
@@ -82,7 +86,6 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
private static final Log LOGGER = LogFactory.getLog(ReactiveElasticsearchTemplate.class);
private final ReactiveElasticsearchClient client;
private final ReactiveElasticsearchSqlClient sqlClient;
private final RequestConverter requestConverter;
private final ResponseConverter responseConverter;
private final JsonpMapper jsonpMapper;
@@ -94,7 +97,6 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
Assert.notNull(client, "client must not be null");
this.client = client;
this.sqlClient = client.sql();
this.jsonpMapper = client._transport().jsonpMapper();
requestConverter = new RequestConverter(converter, jsonpMapper);
responseConverter = new ResponseConverter(jsonpMapper);
@@ -177,15 +179,6 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
return Mono.from(execute(client -> client.deleteByQuery(request))).map(responseConverter::byQueryResponse);
}
@Override
public Mono<ByQueryResponse> delete(DeleteQuery query, Class<?> entityType, IndexCoordinates index) {
Assert.notNull(query, "query must not be null");
DeleteByQueryRequest request = requestConverter.documentDeleteByQueryRequest(query, routingResolver.getRouting(),
entityType, index, getRefreshPolicy());
return Mono.from(execute(client -> client.deleteByQuery(request))).map(responseConverter::byQueryResponse);
}
@Override
public <T> Mono<T> get(String id, Class<T> entityType, IndexCoordinates index) {
@@ -391,28 +384,7 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
Function<PitSearchAfter, Publisher<? extends ResponseBody<EntityAsMap>>> resourceClosure = psa -> {
baseQuery.setPointInTime(new Query.PointInTime(psa.getPit(), pitKeepAlive));
// only add _shard_doc if there is not a field_collapse and a sort with the same name
boolean addShardDoc = true;
if (query instanceof NativeQuery nativeQuery && nativeQuery.getFieldCollapse() != null) {
var field = nativeQuery.getFieldCollapse().field();
if (nativeQuery.getSortOptions().stream()
.anyMatch(sortOptions -> sortOptions.isField() && sortOptions.field().field().equals(field))) {
addShardDoc = false;
}
if (query.getSort() != null
&& query.getSort().stream().anyMatch(order -> order.getProperty().equals(field))) {
addShardDoc = false;
}
}
if (addShardDoc) {
baseQuery.addSort(Sort.by("_shard_doc"));
}
baseQuery.addSort(Sort.by("_shard_doc"));
SearchRequest firstSearchRequest = requestConverter.searchRequest(baseQuery, routingResolver.getRouting(),
clazz, index, false, true);
@@ -642,14 +614,6 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
return NativeQuery.builder().withIds(ids);
}
@Override
public Mono<SqlResponse> search(SqlQuery query) {
Assert.notNull(query, "Query must not be null.");
co.elastic.clients.elasticsearch.sql.QueryRequest request = requestConverter.sqlQueryRequest(query);
return sqlClient.query(request).onErrorMap(this::translateException).map(responseConverter::sqlResponse);
}
/**
* Callback interface to be used with {@link #execute(ReactiveElasticsearchTemplate.ClientCallback<>)} for operating
* directly on {@link ReactiveElasticsearchClient}.
@@ -681,7 +645,7 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
*/
private RuntimeException translateException(Throwable throwable) {
RuntimeException runtimeException = throwable instanceof RuntimeException ex ? ex
RuntimeException runtimeException = throwable instanceof RuntimeException ? (RuntimeException) throwable
: new RuntimeException(throwable.getMessage(), throwable);
RuntimeException potentiallyTranslatedException = exceptionTranslator
.translateExceptionIfPossible(runtimeException);
@@ -15,7 +15,7 @@
*/
package org.springframework.data.elasticsearch.client.elc;
import static org.springframework.util.StringUtils.*;
import static org.springframework.util.StringUtils.hasText;
import co.elastic.clients.elasticsearch._types.AcknowledgedResponseBase;
import co.elastic.clients.elasticsearch.indices.*;
@@ -24,7 +24,6 @@ import co.elastic.clients.transport.endpoints.BooleanResponse;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
@@ -47,8 +46,6 @@ import org.springframework.data.elasticsearch.core.index.GetIndexTemplateRequest
import org.springframework.data.elasticsearch.core.index.GetTemplateRequest;
import org.springframework.data.elasticsearch.core.index.PutIndexTemplateRequest;
import org.springframework.data.elasticsearch.core.index.PutTemplateRequest;
import org.springframework.data.elasticsearch.core.mapping.Alias;
import org.springframework.data.elasticsearch.core.mapping.CreateIndexSettings;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.lang.Nullable;
@@ -133,14 +130,8 @@ public class ReactiveIndicesTemplate
private Mono<Boolean> doCreate(IndexCoordinates indexCoordinates, Map<String, Object> settings,
@Nullable Document mapping) {
Set<Alias> aliases = (boundClass != null) ? getAliasesFor(boundClass) : new HashSet<>();
CreateIndexSettings indexSettings = CreateIndexSettings.builder(indexCoordinates)
.withAliases(aliases)
.withSettings(settings)
.withMapping(mapping)
.build();
CreateIndexRequest createIndexRequest = requestConverter.indicesCreateRequest(indexSettings);
CreateIndexRequest createIndexRequest = requestConverter.indicesCreateRequest(indexCoordinates, settings, mapping);
Mono<CreateIndexResponse> createIndexResponse = Mono.from(execute(client -> client.create(createIndexRequest)));
return createIndexResponse.map(CreateIndexResponse::acknowledged);
}
@@ -444,15 +435,6 @@ public class ReactiveIndicesTemplate
return elasticsearchConverter.getMappingContext().getRequiredPersistentEntity(clazz).getIndexCoordinates();
}
/**
* Get the {@link Alias} of the provided class.
*
* @param clazz provided class that can be used to extract aliases.
*/
private Set<Alias> getAliasesFor(Class<?> clazz) {
return elasticsearchConverter.getMappingContext().getRequiredPersistentEntity(clazz).getAliases();
}
private Class<?> checkForBoundClass() {
if (boundClass == null) {
throw new InvalidDataAccessApiUsageException("IndexOperations are not bound");
@@ -20,6 +20,7 @@ import static org.springframework.util.CollectionUtils.*;
import co.elastic.clients.elasticsearch._types.Conflicts;
import co.elastic.clients.elasticsearch._types.ExpandWildcard;
import co.elastic.clients.elasticsearch._types.InlineScript;
import co.elastic.clients.elasticsearch._types.NestedSortValue;
import co.elastic.clients.elasticsearch._types.OpType;
import co.elastic.clients.elasticsearch._types.SortOptions;
@@ -43,7 +44,6 @@ 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.msearch.MultisearchBody;
import co.elastic.clients.elasticsearch.core.msearch.MultisearchHeader;
import co.elastic.clients.elasticsearch.core.search.Highlight;
import co.elastic.clients.elasticsearch.core.search.Rescore;
import co.elastic.clients.elasticsearch.core.search.SourceConfig;
@@ -54,7 +54,6 @@ import co.elastic.clients.elasticsearch.indices.update_aliases.Action;
import co.elastic.clients.json.JsonData;
import co.elastic.clients.json.JsonpDeserializer;
import co.elastic.clients.json.JsonpMapper;
import co.elastic.clients.util.ObjectBuilder;
import jakarta.json.stream.JsonParser;
import java.io.ByteArrayInputStream;
@@ -67,8 +66,6 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -88,8 +85,6 @@ import org.springframework.data.elasticsearch.core.index.GetIndexTemplateRequest
import org.springframework.data.elasticsearch.core.index.GetTemplateRequest;
import org.springframework.data.elasticsearch.core.index.PutIndexTemplateRequest;
import org.springframework.data.elasticsearch.core.index.PutTemplateRequest;
import org.springframework.data.elasticsearch.core.mapping.Alias;
import org.springframework.data.elasticsearch.core.mapping.CreateIndexSettings;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
@@ -110,10 +105,10 @@ import org.springframework.util.StringUtils;
* @author Sascha Woo
* @author cdalxndr
* @author scoobyzhang
* @author Haibo Liu
* @since 4.4
*/
class RequestConverter extends AbstractQueryProcessor {
@SuppressWarnings("ClassCanBeRecord")
class RequestConverter {
private static final Log LOGGER = LogFactory.getLog(RequestConverter.class);
@@ -158,6 +153,7 @@ class RequestConverter extends AbstractQueryProcessor {
aliasActions.getActions().forEach(aliasAction -> {
if (aliasAction instanceof AliasAction.Add add) {
var parameters = add.getParameters();
// noinspection DuplicatedCode
String[] parametersAliases = parameters.getAliases();
if (parametersAliases != null) {
for (String aliasName : parametersAliases) {
@@ -171,9 +167,9 @@ class RequestConverter extends AbstractQueryProcessor {
}));
}
private co.elastic.clients.elasticsearch.indices.Alias.Builder buildAlias(AliasActionParameters parameters,
co.elastic.clients.elasticsearch.indices.Alias.Builder aliasBuilder) {
private Alias.Builder buildAlias(AliasActionParameters parameters, Alias.Builder aliasBuilder) {
// noinspection DuplicatedCode
if (parameters.getRouting() != null) {
aliasBuilder.routing(parameters.getRouting());
}
@@ -236,25 +232,17 @@ class RequestConverter extends AbstractQueryProcessor {
return new ExistsRequest.Builder().index(Arrays.asList(indexCoordinates.getIndexNames())).build();
}
public CreateIndexRequest indicesCreateRequest(CreateIndexSettings indexSettings) {
Map<String, co.elastic.clients.elasticsearch.indices.Alias> aliases = new HashMap<>();
for (Alias alias : indexSettings.getAliases()) {
co.elastic.clients.elasticsearch.indices.Alias esAlias = co.elastic.clients.elasticsearch.indices.Alias
.of(ab -> ab.filter(getQuery(alias.getFilter(), null))
.routing(alias.getRouting())
.indexRouting(alias.getIndexRouting())
.searchRouting(alias.getSearchRouting())
.isHidden(alias.getHidden())
.isWriteIndex(alias.getWriteIndex()));
aliases.put(alias.getAlias(), esAlias);
}
public CreateIndexRequest indicesCreateRequest(IndexCoordinates indexCoordinates, Map<String, Object> settings,
@Nullable Document mapping) {
Assert.notNull(indexCoordinates, "indexCoordinates must not be null");
Assert.notNull(settings, "settings must not be null");
// note: the new client does not support the index.storeType anymore
return new CreateIndexRequest.Builder() //
.index(indexSettings.getIndexCoordinates().getIndexName()) //
.aliases(aliases)
.settings(indexSettings(indexSettings.getSettings())) //
.mappings(typeMapping(indexSettings.getMapping())) //
.index(indexCoordinates.getIndexName()) //
.settings(indexSettings(settings)) //
.mappings(typeMapping(mapping)) //
.build();
}
@@ -408,8 +396,11 @@ class RequestConverter extends AbstractQueryProcessor {
.order(putTemplateRequest.getOrder());
if (putTemplateRequest.getSettings() != null) {
Map<String, JsonData> settings = getTemplateParams(putTemplateRequest.getSettings().entrySet());
builder.settings(sb -> sb.otherSettings(settings));
Function<Map.Entry<String, Object>, String> keyMapper = Map.Entry::getKey;
Function<Map.Entry<String, Object>, JsonData> valueMapper = entry -> JsonData.of(entry.getValue(), jsonpMapper);
Map<String, JsonData> settings = putTemplateRequest.getSettings().entrySet().stream()
.collect(Collectors.toMap(keyMapper, valueMapper));
builder.settings(settings);
}
if (putTemplateRequest.getMappings() != null) {
@@ -424,6 +415,7 @@ class RequestConverter extends AbstractQueryProcessor {
if (aliasActions != null) {
aliasActions.getActions().forEach(aliasAction -> {
AliasActionParameters parameters = aliasAction.getParameters();
// noinspection DuplicatedCode
String[] parametersAliases = parameters.getAliases();
if (parametersAliases != null) {
@@ -457,6 +449,7 @@ class RequestConverter extends AbstractQueryProcessor {
aliasActions.getActions().forEach(aliasAction -> {
if (aliasAction instanceof AliasAction.Add add) {
var parameters = add.getParameters();
// noinspection DuplicatedCode
String[] parametersAliases = parameters.getAliases();
if (parametersAliases != null) {
for (String aliasName : parametersAliases) {
@@ -530,22 +523,6 @@ class RequestConverter extends AbstractQueryProcessor {
.of(gtr -> gtr.name(getTemplateRequest.getTemplateName()).flatSettings(true));
}
public co.elastic.clients.elasticsearch.sql.QueryRequest sqlQueryRequest(SqlQuery query) {
Assert.notNull(query, "Query must not be null.");
return co.elastic.clients.elasticsearch.sql.QueryRequest.of(sqb -> {
sqb.query(query.getQuery()).catalog(query.getCatalog()).columnar(query.getColumnar()).cursor(query.getCursor())
.fetchSize(query.getFetchSize()).fieldMultiValueLeniency(query.getFieldMultiValueLeniency())
.indexUsingFrozen(query.getIndexIncludeFrozen()).keepAlive(time(query.getKeepAlive()))
.keepOnCompletion(query.getKeepOnCompletion()).pageTimeout(time(query.getPageTimeout()))
.requestTimeout(time(query.getRequestTimeout()))
.waitForCompletionTimeout(time(query.getWaitForCompletionTimeout())).filter(getQuery(query.getFilter(), null))
.timeZone(Objects.toString(query.getTimeZone(), null)).format("json");
return sqb;
});
}
// endregion
// region documents
@@ -661,7 +638,7 @@ class RequestConverter extends AbstractQueryProcessor {
Object queryObject = query.getObject();
if (queryObject != null) {
builder
builder
.id(StringUtils.hasText(query.getId()) ? query.getId() : getPersistentEntityId(queryObject))
.document(elasticsearchConverter.mapObject(queryObject));
} else if (query.getSource() != null) {
@@ -743,12 +720,16 @@ class RequestConverter extends AbstractQueryProcessor {
scriptData.params().forEach((key, value) -> params.put(key, JsonData.of(value, jsonpMapper)));
}
return co.elastic.clients.elasticsearch._types.Script.of(sb -> {
sb.lang(scriptData.language())
.params(params);
if (scriptData.type() == ScriptType.INLINE) {
sb.source(scriptData.script());
sb.inline(is -> is //
.lang(scriptData.language()) //
.source(scriptData.script()) //
.params(params)); //
} else if (scriptData.type() == ScriptType.STORED) {
sb.id(scriptData.script());
sb.stored(ss -> ss //
.id(scriptData.script()) //
.params(params) //
);
}
return sb;
});
@@ -920,9 +901,7 @@ class RequestConverter extends AbstractQueryProcessor {
ReindexRequest.Script script = reindexRequest.getScript();
if (script != null) {
builder.script(sb -> sb
.lang(script.getLang())
.source(script.getSource()));
builder.script(s -> s.inline(InlineScript.of(i -> i.lang(script.getLang()).source(script.getSource()))));
}
builder.timeout(time(reindexRequest.getTimeout())) //
@@ -988,81 +967,6 @@ class RequestConverter extends AbstractQueryProcessor {
});
}
public DeleteByQueryRequest documentDeleteByQueryRequest(DeleteQuery query, @Nullable String routing, Class<?> clazz,
IndexCoordinates index, @Nullable RefreshPolicy refreshPolicy) {
Assert.notNull(query, "query must not be null");
Assert.notNull(index, "index must not be null");
return DeleteByQueryRequest.of(dqb -> {
dqb.index(Arrays.asList(index.getIndexNames())) //
.query(getQuery(query.getQuery(), clazz))//
.refresh(deleteByQueryRefresh(refreshPolicy))
.requestsPerSecond(query.getRequestsPerSecond())
.maxDocs(query.getMaxDocs())
.scroll(time(query.getScroll()))
.scrollSize(query.getScrollSize());
if (query.getRouting() != null) {
dqb.routing(query.getRouting());
} else if (StringUtils.hasText(routing)) {
dqb.routing(routing);
}
if (query.getQ() != null) {
dqb.q(query.getQ())
.analyzer(query.getAnalyzer())
.analyzeWildcard(query.getAnalyzeWildcard())
.defaultOperator(operator(query.getDefaultOperator()))
.df(query.getDf())
.lenient(query.getLenient());
}
if (query.getExpandWildcards() != null && !query.getExpandWildcards().isEmpty()) {
dqb.expandWildcards(expandWildcards(query.getExpandWildcards()));
}
if (query.getStats() != null && !query.getStats().isEmpty()) {
dqb.stats(query.getStats());
}
if (query.getSlices() != null) {
dqb.slices(sb -> sb.value(query.getSlices()));
}
if (query.getSort() != null) {
ElasticsearchPersistentEntity<?> persistentEntity = getPersistentEntity(clazz);
List<SortOptions> sortOptions = getSortOptions(query.getSort(), persistentEntity);
if (!sortOptions.isEmpty()) {
dqb.sort(
sortOptions.stream()
.map(sortOption -> {
String order = "asc";
var sortField = sortOption.field();
if (sortField.order() != null) {
order = sortField.order().jsonValue();
}
return sortField.field() + ':' + order;
})
.collect(Collectors.toList()));
}
}
if (query.getRefresh() != null) {
dqb.refresh(query.getRefresh());
}
dqb.allowNoIndices(query.getAllowNoIndices())
.conflicts(conflicts(query.getConflicts()))
.ignoreUnavailable(query.getIgnoreUnavailable())
.preference(query.getPreference())
.requestCache(query.getRequestCache())
.searchType(searchType(query.getSearchType()))
.searchTimeout(time(query.getSearchTimeout()))
.terminateAfter(query.getTerminateAfter())
.timeout(time(query.getTimeout()))
.version(query.getVersion());
return dqb;
});
}
public UpdateRequest<Document, ?> documentUpdateRequest(UpdateQuery query, IndexCoordinates index,
@Nullable RefreshPolicy refreshPolicy, @Nullable String routing) {
@@ -1078,15 +982,21 @@ class RequestConverter extends AbstractQueryProcessor {
}
uqb.script(sb -> {
sb.lang(query.getLang()).params(params);
if (query.getScriptType() == ScriptType.INLINE) {
sb.source(query.getScript()); //
sb.inline(is -> is //
.lang(query.getLang()) //
.source(query.getScript()) //
.params(params)); //
} else if (query.getScriptType() == ScriptType.STORED) {
sb.id(query.getScript());
sb.stored(ss -> ss //
.id(query.getScript()) //
.params(params) //
);
}
return sb;
});
}
);
}
uqb //
@@ -1231,40 +1141,11 @@ class RequestConverter extends AbstractQueryProcessor {
builder.routing(routing);
}
addPostFilter(query, builder);
addFilter(query, builder);
return builder.build();
}
public MsearchTemplateRequest searchMsearchTemplateRequest(
List<ElasticsearchTemplate.MultiSearchTemplateQueryParameter> multiSearchTemplateQueryParameters,
@Nullable String routing) {
// basically the same stuff as in template search
return MsearchTemplateRequest.of(mtrb -> {
multiSearchTemplateQueryParameters.forEach(param -> {
var query = param.query();
mtrb.searchTemplates(stb -> stb
.header(msearchHeaderBuilder(query, param.index(), routing))
.body(bb -> {
bb //
.explain(query.getExplain()) //
.id(query.getId()) //
.source(query.getSource()) //
;
if (!CollectionUtils.isEmpty(query.getParams())) {
Map<String, JsonData> params = getTemplateParams(query.getParams().entrySet());
bb.params(params);
}
return bb;
}));
});
return mtrb;
});
}
public MsearchRequest searchMsearchRequest(
List<ElasticsearchTemplate.MultiSearchQueryParameter> multiSearchQueryParameters, @Nullable String routing) {
@@ -1276,7 +1157,28 @@ class RequestConverter extends AbstractQueryProcessor {
var query = param.query();
mrb.searches(sb -> sb //
.header(msearchHeaderBuilder(query, param.index(), routing)) //
.header(h -> {
var searchType = (query instanceof NativeQuery nativeQuery && nativeQuery.getKnnQuery() != null) ? null
: searchType(query.getSearchType());
h //
.index(Arrays.asList(param.index().getIndexNames())) //
.searchType(searchType) //
.requestCache(query.getRequestCache()) //
;
if (StringUtils.hasText(query.getRoute())) {
h.routing(query.getRoute());
} else if (StringUtils.hasText(routing)) {
h.routing(routing);
}
if (query.getPreference() != null) {
h.preference(query.getPreference());
}
return h;
}) //
.body(bb -> {
bb //
.query(getQuery(query, param.clazz()))//
@@ -1341,16 +1243,17 @@ class RequestConverter extends AbstractQueryProcessor {
String script = runtimeField.getScript();
if (script != null) {
rfb.script(s -> {
s.source(script);
rfb
.script(s -> s
.inline(is -> {
is.source(script);
if (runtimeField.getParams() != null) {
s.params(TypeUtils.paramsMap(runtimeField.getParams()));
}
return s;
});
if (runtimeField.getParams() != null) {
is.params(TypeUtils.paramsMap(runtimeField.getParams()));
}
return is;
}));
}
return rfb;
});
runtimeMappings.put(runtimeField.getName(), esRuntimeField);
@@ -1381,35 +1284,6 @@ class RequestConverter extends AbstractQueryProcessor {
});
}
/**
* {@link MsearchRequest} and {@link MsearchTemplateRequest} share the same {@link MultisearchHeader}
*/
private Function<MultisearchHeader.Builder, ObjectBuilder<MultisearchHeader>> msearchHeaderBuilder(Query query,
IndexCoordinates index, @Nullable String routing) {
return h -> {
var searchType = (query instanceof NativeQuery nativeQuery && !isEmpty(nativeQuery.getKnnSearches())) ? null
: searchType(query.getSearchType());
h //
.index(Arrays.asList(index.getIndexNames())) //
.searchType(searchType) //
.requestCache(query.getRequestCache()) //
;
if (StringUtils.hasText(query.getRoute())) {
h.routing(query.getRoute());
} else if (StringUtils.hasText(routing)) {
h.routing(routing);
}
if (query.getPreference() != null) {
h.preference(query.getPreference());
}
return h;
};
}
private <T> void prepareSearchRequest(Query query, @Nullable String routing, @Nullable Class<T> clazz,
IndexCoordinates indexCoordinates, SearchRequest.Builder builder, boolean forCount, boolean forBatchedSearch) {
@@ -1419,7 +1293,7 @@ class RequestConverter extends AbstractQueryProcessor {
ElasticsearchPersistentEntity<?> persistentEntity = getPersistentEntity(clazz);
var searchType = (query instanceof NativeQuery nativeQuery && !isEmpty(nativeQuery.getKnnSearches())) ? null
var searchType = (query instanceof NativeQuery nativeQuery && nativeQuery.getKnnQuery() != null) ? null
: searchType(query.getSearchType());
builder //
@@ -1497,8 +1371,8 @@ class RequestConverter extends AbstractQueryProcessor {
if (query instanceof NativeQuery nativeQuery) {
prepareNativeSearch(nativeQuery, builder);
}
// query.getSort() must be checked after prepareNativeSearch as this already might have a sort set
// that must have higher priority
// query.getSort() must be checked after prepareNativeSearch as this already might hav a sort set that must have
// higher priority
if (query.getSort() != null) {
List<SortOptions> sortOptions = getSortOptions(query.getSort(), persistentEntity);
@@ -1520,15 +1394,7 @@ class RequestConverter extends AbstractQueryProcessor {
}
if (!isEmpty(query.getSearchAfter())) {
var fieldValues = query.getSearchAfter().stream().map(TypeUtils::toFieldValue).toList();
// when there is a field collapse on a native query, and we have a search_after, then the search_after
// must only have one entry
if (query instanceof NativeQuery nativeQuery && nativeQuery.getFieldCollapse() != null) {
builder.searchAfter(fieldValues.get(0));
} else {
builder.searchAfter(fieldValues);
}
builder.searchAfter(query.getSearchAfter().stream().map(TypeUtils::toFieldValue).toList());
}
query.getRescorerQueries().forEach(rescorerQuery -> builder.rescore(getRescore(rescorerQuery)));
@@ -1541,14 +1407,16 @@ class RequestConverter extends AbstractQueryProcessor {
rfb.type(RuntimeFieldType._DESERIALIZER.parse(runtimeField.getType()));
String script = runtimeField.getScript();
if (script != null) {
rfb.script(s -> {
s.source(script);
rfb
.script(s -> s
.inline(is -> {
is.source(script);
if (runtimeField.getParams() != null) {
s.params(TypeUtils.paramsMap(runtimeField.getParams()));
}
return s;
});
if (runtimeField.getParams() != null) {
is.params(TypeUtils.paramsMap(runtimeField.getParams()));
}
return is;
}));
}
return rfb;
@@ -1627,7 +1495,7 @@ class RequestConverter extends AbstractQueryProcessor {
private void addHighlight(Query query, SearchRequest.Builder builder) {
Highlight highlight = query.getHighlightQuery()
.map(highlightQuery -> new HighlightQueryBuilder(elasticsearchConverter.getMappingContext(), this)
.map(highlightQuery -> new HighlightQueryBuilder(elasticsearchConverter.getMappingContext())
.getHighlight(highlightQuery.getHighlight(), highlightQuery.getType()))
.orElse(null);
@@ -1637,7 +1505,7 @@ class RequestConverter extends AbstractQueryProcessor {
private void addHighlight(Query query, MultisearchBody.Builder builder) {
Highlight highlight = query.getHighlightQuery()
.map(highlightQuery -> new HighlightQueryBuilder(elasticsearchConverter.getMappingContext(), this)
.map(highlightQuery -> new HighlightQueryBuilder(elasticsearchConverter.getMappingContext())
.getHighlight(highlightQuery.getHighlight(), highlightQuery.getType()))
.orElse(null);
@@ -1744,8 +1612,8 @@ class RequestConverter extends AbstractQueryProcessor {
.sort(query.getSortOptions()) //
;
if (!isEmpty(query.getKnnSearches())) {
builder.knn(query.getKnnSearches());
if (query.getKnnQuery() != null) {
builder.knn(query.getKnnQuery());
}
if (!isEmpty(query.getAggregations())) {
@@ -1765,8 +1633,8 @@ class RequestConverter extends AbstractQueryProcessor {
.collapse(query.getFieldCollapse()) //
.sort(query.getSortOptions());
if (!isEmpty(query.getKnnSearches())) {
builder.knn(query.getKnnSearches());
if (query.getKnnQuery() != null) {
builder.knn(query.getKnnQuery());
}
if (!isEmpty(query.getAggregations())) {
@@ -1779,22 +1647,51 @@ class RequestConverter extends AbstractQueryProcessor {
}
@Nullable
co.elastic.clients.elasticsearch._types.query_dsl.Query getQuery(@Nullable Query query,
private co.elastic.clients.elasticsearch._types.query_dsl.Query getQuery(@Nullable Query query,
@Nullable Class<?> clazz) {
return getEsQuery(query, (q) -> elasticsearchConverter.updateQuery(q, clazz));
if (query == null) {
return null;
}
elasticsearchConverter.updateQuery(query, clazz);
co.elastic.clients.elasticsearch._types.query_dsl.Query esQuery = null;
if (query instanceof CriteriaQuery) {
esQuery = CriteriaQueryProcessor.createQuery(((CriteriaQuery) query).getCriteria());
} else if (query instanceof StringQuery) {
esQuery = Queries.wrapperQueryAsQuery(((StringQuery) query).getSource());
} else if (query instanceof NativeQuery nativeQuery) {
if (nativeQuery.getQuery() != null) {
esQuery = nativeQuery.getQuery();
} else if (nativeQuery.getSpringDataQuery() != null) {
esQuery = getQuery(nativeQuery.getSpringDataQuery(), clazz);
}
} else {
throw new IllegalArgumentException("unhandled Query implementation " + query.getClass().getName());
}
return esQuery;
}
private void addPostFilter(Query query, SearchRequest.Builder builder) {
private void addFilter(Query query, SearchRequest.Builder builder) {
// we only need to handle NativeQuery here. filter from a CriteriaQuery are added into the query and not as post
// filter anymore, StringQuery do not have post filters
if (query instanceof NativeQuery nativeQuery) {
if (query instanceof CriteriaQuery) {
CriteriaFilterProcessor.createQuery(((CriteriaQuery) query).getCriteria()).ifPresent(builder::postFilter);
} else // noinspection StatementWithEmptyBody
if (query instanceof StringQuery) {
// no filter for StringQuery
} else if (query instanceof NativeQuery nativeQuery) {
if (nativeQuery.getFilter() != null) {
builder.postFilter(nativeQuery.getFilter());
} else if (nativeQuery.getSpringDataQuery() != null) {
addPostFilter(nativeQuery.getSpringDataQuery(), builder);
addFilter(nativeQuery.getSpringDataQuery(), builder);
}
} else {
throw new IllegalArgumentException("unhandled Query implementation " + query.getClass().getName());
}
}
@@ -1878,8 +1775,7 @@ class RequestConverter extends AbstractQueryProcessor {
.id(query.getId()) //
.index(Arrays.asList(index.getIndexNames())) //
.preference(query.getPreference()) //
.searchType(searchType(query.getSearchType())) //
.source(query.getSource()) //
.searchType(searchType(query.getSearchType())).source(query.getSource()) //
;
if (query.getRoute() != null) {
@@ -1898,7 +1794,10 @@ class RequestConverter extends AbstractQueryProcessor {
}
if (!CollectionUtils.isEmpty(query.getParams())) {
Map<String, JsonData> params = getTemplateParams(query.getParams().entrySet());
Function<Map.Entry<String, Object>, String> keyMapper = Map.Entry::getKey;
Function<Map.Entry<String, Object>, JsonData> valueMapper = entry -> JsonData.of(entry.getValue(), jsonpMapper);
Map<String, JsonData> params = query.getParams().entrySet().stream()
.collect(Collectors.toMap(keyMapper, valueMapper));
builder.params(params);
}
@@ -1906,14 +1805,6 @@ class RequestConverter extends AbstractQueryProcessor {
});
}
@NotNull
private Map<String, JsonData> getTemplateParams(Set<Map.Entry<String, Object>> query) {
Function<Map.Entry<String, Object>, String> keyMapper = Map.Entry::getKey;
Function<Map.Entry<String, Object>, JsonData> valueMapper = entry -> JsonData.of(entry.getValue(), jsonpMapper);
return query.stream()
.collect(Collectors.toMap(keyMapper, valueMapper));
}
// endregion
public PutScriptRequest scriptPut(Script script) {
@@ -15,8 +15,9 @@
*/
package org.springframework.data.elasticsearch.client.elc;
import static org.springframework.data.elasticsearch.client.elc.JsonUtils.*;
import static org.springframework.data.elasticsearch.client.elc.TypeUtils.*;
import static org.springframework.data.elasticsearch.client.elc.JsonUtils.toJson;
import static org.springframework.data.elasticsearch.client.elc.TypeUtils.removePrefixFromJson;
import static org.springframework.data.elasticsearch.client.elc.TypeUtils.typeMapping;
import co.elastic.clients.elasticsearch._types.BulkIndexByScrollFailure;
import co.elastic.clients.elasticsearch._types.ErrorCause;
@@ -33,16 +34,9 @@ import co.elastic.clients.elasticsearch.core.mget.MultiGetResponseItem;
import co.elastic.clients.elasticsearch.indices.*;
import co.elastic.clients.elasticsearch.indices.get_index_template.IndexTemplateItem;
import co.elastic.clients.elasticsearch.indices.get_mapping.IndexMappingRecord;
import co.elastic.clients.elasticsearch.sql.QueryResponse;
import co.elastic.clients.json.JsonData;
import co.elastic.clients.json.JsonpMapper;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -53,17 +47,12 @@ import org.springframework.data.elasticsearch.core.IndexInformation;
import org.springframework.data.elasticsearch.core.MultiGetItem;
import org.springframework.data.elasticsearch.core.cluster.ClusterHealth;
import org.springframework.data.elasticsearch.core.document.Document;
import org.springframework.data.elasticsearch.core.index.AliasData;
import org.springframework.data.elasticsearch.core.index.Settings;
import org.springframework.data.elasticsearch.core.index.TemplateData;
import org.springframework.data.elasticsearch.core.index.TemplateResponse;
import org.springframework.data.elasticsearch.core.index.TemplateResponseData;
import org.springframework.data.elasticsearch.core.index.*;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.ByQueryResponse;
import org.springframework.data.elasticsearch.core.query.StringQuery;
import org.springframework.data.elasticsearch.core.reindex.ReindexResponse;
import org.springframework.data.elasticsearch.core.script.Script;
import org.springframework.data.elasticsearch.core.sql.SqlResponse;
import org.springframework.data.elasticsearch.support.DefaultStringObjectMap;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -132,7 +121,8 @@ class ResponseConverter {
.build();
}
private TemplateResponseData clusterGetComponentTemplateData(ComponentTemplateSummary componentTemplateSummary) {
private TemplateResponseData clusterGetComponentTemplateData(
ComponentTemplateSummary componentTemplateSummary) {
var mapping = typeMapping(componentTemplateSummary.mappings());
var settings = new Settings();
@@ -193,7 +183,7 @@ class ResponseConverter {
Map<String, IndexMappingRecord> mappings = getMappingResponse.result();
if (mappings == null || mappings.isEmpty()) {
if (mappings == null || mappings.size() == 0) {
return Document.create();
}
@@ -336,7 +326,7 @@ class ResponseConverter {
}
private TemplateResponseData indexGetComponentTemplateData(IndexTemplateSummary indexTemplateSummary,
List<String> composedOf) {
List<String> composedOf) {
var mapping = typeMapping(indexTemplateSummary.mappings());
Function<IndexSettings, Settings> indexSettingsToSettings = indexSettings -> {
@@ -539,29 +529,6 @@ class ResponseConverter {
}
// endregion
// region sql
public SqlResponse sqlResponse(QueryResponse response) {
SqlResponse.Builder builder = SqlResponse.builder();
builder.withRunning(Boolean.TRUE.equals(response.isRunning()))
.withPartial(Boolean.TRUE.equals(response.isPartial())).withCursor(response.cursor());
final List<SqlResponse.Column> columns = response.columns().stream()
.map(column -> new SqlResponse.Column(column.name(), column.type())).toList();
builder.withColumns(columns);
for (List<JsonData> rowValues : response.rows()) {
SqlResponse.Row.Builder rowBuilder = SqlResponse.Row.builder();
for (int idx = 0; idx < rowValues.size(); idx++) {
rowBuilder.withValue(columns.get(idx), rowValues.get(idx).toJson());
}
builder.withRow(rowBuilder.build());
}
return builder.build();
}
// end region
// region helper functions
private long timeToLong(Time time) {
@@ -574,7 +541,7 @@ class ResponseConverter {
}
@Nullable
static ElasticsearchErrorCause toErrorCause(@Nullable ErrorCause errorCause) {
private static ElasticsearchErrorCause toErrorCause(@Nullable ErrorCause errorCause) {
if (errorCause != null) {
return new ElasticsearchErrorCause( //
@@ -15,8 +15,6 @@
*/
package org.springframework.data.elasticsearch.client.elc;
import co.elastic.clients.elasticsearch._types.ShardFailure;
import co.elastic.clients.elasticsearch._types.ShardStatistics;
import co.elastic.clients.elasticsearch._types.aggregations.Aggregate;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.SearchTemplateResponse;
@@ -29,7 +27,6 @@ import co.elastic.clients.elasticsearch.core.search.Suggestion;
import co.elastic.clients.elasticsearch.core.search.TotalHits;
import co.elastic.clients.json.JsonpMapper;
import java.time.Duration;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -39,7 +36,6 @@ import java.util.stream.Collectors;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.data.elasticsearch.core.SearchShardStatistics;
import org.springframework.data.elasticsearch.core.TotalHitsRelation;
import org.springframework.data.elasticsearch.core.document.SearchDocument;
import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse;
@@ -56,8 +52,6 @@ import org.springframework.util.CollectionUtils;
* Factory class to create {@link SearchDocumentResponse} instances.
*
* @author Peter-Josef Meisch
* @author Haibo Liu
* @author Mohamed El Harrougui
* @since 4.4
*/
class SearchDocumentResponseBuilder {
@@ -84,11 +78,8 @@ class SearchDocumentResponseBuilder {
Map<String, Aggregate> aggregations = responseBody.aggregations();
Map<String, List<Suggestion<EntityAsMap>>> suggest = responseBody.suggest();
var pointInTimeId = responseBody.pitId();
var shards = responseBody.shards();
var executionDurationInMillis = responseBody.took();
return from(hitsMetadata, shards, scrollId, pointInTimeId, executionDurationInMillis, aggregations, suggest,
entityCreator, jsonpMapper);
return from(hitsMetadata, scrollId, pointInTimeId, aggregations, suggest, entityCreator, jsonpMapper);
}
/**
@@ -107,16 +98,13 @@ class SearchDocumentResponseBuilder {
Assert.notNull(entityCreator, "entityCreator must not be null");
Assert.notNull(jsonpMapper, "jsonpMapper must not be null");
var shards = response.shards();
var hitsMetadata = response.hits();
var scrollId = response.scrollId();
var aggregations = response.aggregations();
var suggest = response.suggest();
var pointInTimeId = response.pitId();
var executionDurationInMillis = response.took();
return from(hitsMetadata, shards, scrollId, pointInTimeId, executionDurationInMillis, aggregations, suggest,
entityCreator, jsonpMapper);
return from(hitsMetadata, scrollId, pointInTimeId, aggregations, suggest, entityCreator, jsonpMapper);
}
/**
@@ -132,8 +120,8 @@ class SearchDocumentResponseBuilder {
* @param jsonpMapper to map JsonData objects
* @return the {@link SearchDocumentResponse}
*/
public static <T> SearchDocumentResponse from(HitsMetadata<?> hitsMetadata, @Nullable ShardStatistics shards,
@Nullable String scrollId, @Nullable String pointInTimeId, long executionDurationInMillis, @Nullable Map<String, Aggregate> aggregations,
public static <T> SearchDocumentResponse from(HitsMetadata<?> hitsMetadata, @Nullable String scrollId,
@Nullable String pointInTimeId, @Nullable Map<String, Aggregate> aggregations,
Map<String, List<Suggestion<EntityAsMap>>> suggestES, SearchDocumentResponse.EntityCreator<T> entityCreator,
JsonpMapper jsonpMapper) {
@@ -157,8 +145,6 @@ class SearchDocumentResponseBuilder {
float maxScore = hitsMetadata.maxScore() != null ? hitsMetadata.maxScore().floatValue() : Float.NaN;
Duration executionDuration = Duration.ofMillis(executionDurationInMillis);
List<SearchDocument> searchDocuments = new ArrayList<>();
for (Hit<?> hit : hitsMetadata.hits()) {
searchDocuments.add(DocumentAdapters.from(hit, jsonpMapper));
@@ -169,18 +155,8 @@ class SearchDocumentResponseBuilder {
Suggest suggest = suggestFrom(suggestES, entityCreator);
SearchShardStatistics shardStatistics = shards != null ? shardsFrom(shards) : null;
return new SearchDocumentResponse(totalHits, totalHitsRelation, maxScore, executionDuration, scrollId, pointInTimeId, searchDocuments,
aggregationsContainer, suggest, shardStatistics);
}
private static SearchShardStatistics shardsFrom(ShardStatistics shards) {
List<ShardFailure> failures = shards.failures();
List<SearchShardStatistics.Failure> searchFailures = failures.stream().map(f -> SearchShardStatistics.Failure
.of(f.index(), f.node(), f.status(), f.shard(), null, ResponseConverter.toErrorCause(f.reason()))).toList();
return SearchShardStatistics.of(shards.failed(), shards.successful(), shards.total(), shards.skipped(),
searchFailures);
return new SearchDocumentResponse(totalHits, totalHitsRelation, maxScore, scrollId, pointInTimeId, searchDocuments,
aggregationsContainer, suggest);
}
@Nullable
@@ -242,8 +218,9 @@ class SearchDocumentResponseBuilder {
var phraseSuggest = suggestionES.phrase();
var phraseSuggestOptions = phraseSuggest.options();
List<PhraseSuggestion.Entry.Option> options = new ArrayList<>();
phraseSuggestOptions.forEach(optionES -> options.add(new PhraseSuggestion.Entry.Option(optionES.text(),
optionES.highlighted(), optionES.score(), optionES.collateMatch())));
phraseSuggestOptions.forEach(optionES -> options
.add(new PhraseSuggestion.Entry.Option(optionES.text(), optionES.highlighted(), optionES.score(),
optionES.collateMatch())));
entries.add(new PhraseSuggestion.Entry(phraseSuggest.text(), phraseSuggest.offset(), phraseSuggest.length(),
options, null));
});
@@ -18,8 +18,6 @@ package org.springframework.data.elasticsearch.client.elc;
import co.elastic.clients.elasticsearch._types.*;
import co.elastic.clients.elasticsearch._types.mapping.FieldType;
import co.elastic.clients.elasticsearch._types.mapping.TypeMapping;
import co.elastic.clients.elasticsearch._types.query_dsl.ChildScoreMode;
import co.elastic.clients.elasticsearch._types.query_dsl.Operator;
import co.elastic.clients.elasticsearch.core.search.BoundaryScanner;
import co.elastic.clients.elasticsearch.core.search.HighlighterEncoder;
import co.elastic.clients.elasticsearch.core.search.HighlighterFragmenter;
@@ -42,15 +40,12 @@ import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.core.RefreshPolicy;
import org.springframework.data.elasticsearch.core.document.Document;
import org.springframework.data.elasticsearch.core.query.GeoDistanceOrder;
import org.springframework.data.elasticsearch.core.query.HasChildQuery;
import org.springframework.data.elasticsearch.core.query.IndexQuery;
import org.springframework.data.elasticsearch.core.query.IndicesOptions;
import org.springframework.data.elasticsearch.core.query.Order;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.core.query.RescorerQuery;
import org.springframework.data.elasticsearch.core.query.UpdateResponse;
import org.springframework.data.elasticsearch.core.query.types.ConflictsType;
import org.springframework.data.elasticsearch.core.query.types.OperatorType;
import org.springframework.data.elasticsearch.core.reindex.ReindexRequest;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -505,48 +500,4 @@ final class TypeUtils {
});
return mappedParams;
}
/**
* Convert a spring-data-elasticsearch operator to an Elasticsearch operator.
*
* @param operator spring-data-elasticsearch operator.
* @return an Elasticsearch Operator.
* @since 5.3
*/
@Nullable
static Operator operator(@Nullable OperatorType operator) {
return operator != null ? Operator.valueOf(operator.name()) : null;
}
/**
* Convert a spring-data-elasticsearch {@literal conflicts} to an Elasticsearch {@literal conflicts}.
*
* @param conflicts spring-data-elasticsearch {@literal conflicts}.
* @return an Elasticsearch {@literal conflicts}.
* @since 5.3
*/
@Nullable
static Conflicts conflicts(@Nullable ConflictsType conflicts) {
return conflicts != null ? Conflicts.valueOf(conflicts.name()) : null;
}
/**
* Convert a spring-data-elasticsearch {@literal scoreMode} to an Elasticsearch {@literal scoreMode}.
*
* @param scoreMode spring-data-elasticsearch {@literal scoreMode}.
* @return an Elasticsearch {@literal scoreMode}.
*/
static ChildScoreMode scoreMode(@Nullable HasChildQuery.ScoreMode scoreMode) {
if (scoreMode == null) {
return ChildScoreMode.None;
}
return switch (scoreMode) {
case Avg -> ChildScoreMode.Avg;
case Max -> ChildScoreMode.Max;
case Min -> ChildScoreMode.Min;
case Sum -> ChildScoreMode.Sum;
default -> ChildScoreMode.None;
};
}
}
@@ -57,6 +57,7 @@ import org.springframework.data.elasticsearch.support.VersionInfo;
import org.springframework.data.mapping.callback.EntityCallbacks;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.util.Streamable;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -299,7 +300,6 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
}
@Override
@Deprecated
public ByQueryResponse delete(Query query, Class<?> clazz) {
return delete(query, clazz, getIndexCoordinatesFor(clazz));
}
@@ -779,7 +779,8 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
}
protected interface SearchDocumentResponseCallback<T> {
T doWith(SearchDocumentResponse response);
@NonNull
T doWith(@NonNull SearchDocumentResponse response);
}
protected class ReadSearchDocumentResponseCallback<T> implements SearchDocumentResponseCallback<SearchHits<T>> {
@@ -794,6 +795,7 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
this.type = type;
}
@NonNull
@Override
public SearchHits<T> doWith(SearchDocumentResponse response) {
List<T> entities = response.getSearchDocuments().stream().map(delegate::doWith).collect(Collectors.toList());
@@ -814,6 +816,7 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
this.type = type;
}
@NonNull
@Override
public SearchScrollHits<T> doWith(SearchDocumentResponse response) {
List<T> entities = response.getSearchDocuments().stream().map(delegate::doWith).collect(Collectors.toList());
@@ -46,7 +46,6 @@ import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersiste
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
import org.springframework.data.elasticsearch.core.query.ByQueryResponse;
import org.springframework.data.elasticsearch.core.query.DeleteQuery;
import org.springframework.data.elasticsearch.core.query.IndexQuery;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.core.query.SeqNoPrimaryTerm;
@@ -56,6 +55,7 @@ import org.springframework.data.elasticsearch.core.script.Script;
import org.springframework.data.elasticsearch.core.suggest.response.Suggest;
import org.springframework.data.elasticsearch.support.VersionInfo;
import org.springframework.data.mapping.callback.ReactiveEntityCallbacks;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -409,15 +409,9 @@ abstract public class AbstractReactiveElasticsearchTemplate
abstract protected Mono<String> doDeleteById(String id, @Nullable String routing, IndexCoordinates index);
@Override
@Deprecated
public Mono<ByQueryResponse> delete(Query query, Class<?> entityType) {
return delete(query, entityType, getIndexCoordinatesFor(entityType));
}
@Override
public Mono<ByQueryResponse> delete(DeleteQuery query, Class<?> entityType) {
return delete(query, entityType, getIndexCoordinatesFor(entityType));
}
// endregion
// region SearchDocument
@@ -581,6 +575,7 @@ abstract public class AbstractReactiveElasticsearchTemplate
* @param document the document to convert
* @return a Mono of the entity
*/
@NonNull
Mono<T> toEntity(@Nullable Document document);
}
@@ -598,6 +593,7 @@ abstract public class AbstractReactiveElasticsearchTemplate
this.index = index;
}
@NonNull
public Mono<T> toEntity(@Nullable Document document) {
if (document == null) {
return Mono.empty();
@@ -21,7 +21,6 @@ import java.util.List;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.BulkOptions;
import org.springframework.data.elasticsearch.core.query.ByQueryResponse;
import org.springframework.data.elasticsearch.core.query.DeleteQuery;
import org.springframework.data.elasticsearch.core.query.IndexQuery;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.core.query.UpdateQuery;
@@ -280,22 +279,9 @@ public interface DocumentOperations {
* {@link org.springframework.data.elasticsearch.annotations.Document}
* @return response with detailed information
* @since 4.1
* @deprecated since 5.3.0, use {@link #delete(DeleteQuery, Class)}
*/
@Deprecated
ByQueryResponse delete(Query query, Class<?> clazz);
/**
* Delete all records matching the query.
*
* @param query query defining the objects
* @param clazz The entity class must be annotated with
* {@link org.springframework.data.elasticsearch.annotations.Document}
* @return response with detailed information
* @since 5.3
*/
ByQueryResponse delete(DeleteQuery query, Class<?> clazz);
/**
* Delete all records matching the query.
*
@@ -304,23 +290,9 @@ public interface DocumentOperations {
* {@link org.springframework.data.elasticsearch.annotations.Document}
* @param index the index from which to delete
* @return response with detailed information
* @deprecated since 5.3.0, use {@link #delete(DeleteQuery, Class, IndexCoordinates)}
*/
@Deprecated
ByQueryResponse delete(Query query, Class<?> clazz, IndexCoordinates index);
/**
* Delete all records matching the query.
*
* @param query query defining the objects
* @param clazz The entity class must be annotated with
* {@link org.springframework.data.elasticsearch.annotations.Document}
* @param index the index from which to delete
* @return response with detailed information
* @since 5.3
*/
ByQueryResponse delete(DeleteQuery query, Class<?> clazz, IndexCoordinates index);
/**
* Partially update a document by the given entity.
*
@@ -20,7 +20,6 @@ import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverte
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.routing.RoutingResolver;
import org.springframework.data.elasticsearch.core.script.ScriptOperations;
import org.springframework.data.elasticsearch.core.sql.SqlOperations;
import org.springframework.lang.Nullable;
/**
@@ -36,7 +35,7 @@ import org.springframework.lang.Nullable;
* @author Dmitriy Yakovlev
* @author Peter-Josef Meisch
*/
public interface ElasticsearchOperations extends DocumentOperations, SearchOperations, ScriptOperations, SqlOperations {
public interface ElasticsearchOperations extends DocumentOperations, SearchOperations, ScriptOperations {
/**
* get an {@link IndexOperations} that is bound to the given class
@@ -25,7 +25,6 @@ import java.util.List;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.BulkOptions;
import org.springframework.data.elasticsearch.core.query.ByQueryResponse;
import org.springframework.data.elasticsearch.core.query.DeleteQuery;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.core.query.UpdateQuery;
import org.springframework.data.elasticsearch.core.query.UpdateResponse;
@@ -332,21 +331,9 @@ public interface ReactiveDocumentOperations {
* @param query must not be {@literal null}.
* @param entityType must not be {@literal null}.
* @return a {@link Mono} emitting the number of the removed documents.
* @deprecated since 5.3.0, use {@link #delete(DeleteQuery, Class)}
*/
@Deprecated
Mono<ByQueryResponse> delete(Query query, Class<?> entityType);
/**
* Delete the documents matching the given {@link Query} extracting index from entity metadata.
*
* @param query must not be {@literal null}.
* @param entityType must not be {@literal null}.
* @return a {@link Mono} emitting the number of the removed documents.
* @since 5.3
*/
Mono<ByQueryResponse> delete(DeleteQuery query, Class<?> entityType);
/**
* Delete the documents matching the given {@link Query} extracting index from entity metadata.
*
@@ -354,22 +341,9 @@ public interface ReactiveDocumentOperations {
* @param entityType must not be {@literal null}.
* @param index the target index, must not be {@literal null}
* @return a {@link Mono} emitting the number of the removed documents.
* @deprecated since 5.3.0, use {@link #delete(DeleteQuery, Class, IndexCoordinates)}
*/
@Deprecated
Mono<ByQueryResponse> delete(Query query, Class<?> entityType, IndexCoordinates index);
/**
* Delete the documents matching the given {@link Query} extracting index from entity metadata.
*
* @param query must not be {@literal null}.
* @param entityType must not be {@literal null}.
* @param index the target index, must not be {@literal null}
* @return a {@link Mono} emitting the number of the removed documents.
* @since 5.3
*/
Mono<ByQueryResponse> delete(DeleteQuery query, Class<?> entityType, IndexCoordinates index);
/**
* Partial update of the document.
*
@@ -21,7 +21,6 @@ import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersiste
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.routing.RoutingResolver;
import org.springframework.data.elasticsearch.core.script.ReactiveScriptOperations;
import org.springframework.data.elasticsearch.core.sql.ReactiveSqlOperations;
import org.springframework.lang.Nullable;
/**
@@ -32,7 +31,7 @@ import org.springframework.lang.Nullable;
* @since 3.2
*/
public interface ReactiveElasticsearchOperations
extends ReactiveDocumentOperations, ReactiveSearchOperations, ReactiveScriptOperations, ReactiveSqlOperations {
extends ReactiveDocumentOperations, ReactiveSearchOperations, ReactiveScriptOperations {
/**
* Get the {@link ElasticsearchConverter} used.
@@ -17,8 +17,6 @@ package org.springframework.data.elasticsearch.core;
import reactor.core.publisher.Flux;
import java.time.Duration;
import org.springframework.data.elasticsearch.core.suggest.response.Suggest;
import org.springframework.lang.Nullable;
@@ -27,7 +25,6 @@ import org.springframework.lang.Nullable;
*
* @param <T> the result data class.
* @author Peter-Josef Meisch
* @author Mohamed El Harrougui
* @since 4.4
*/
public interface ReactiveSearchHits<T> {
@@ -40,11 +37,6 @@ public interface ReactiveSearchHits<T> {
float getMaxScore();
/**
* @return the execution duration it took to complete the request
*/
Duration getExecutionDuration();
/**
* @return the {@link SearchHit}s from the search result.
*/
@@ -17,14 +17,11 @@ package org.springframework.data.elasticsearch.core;
import reactor.core.publisher.Flux;
import java.time.Duration;
import org.springframework.data.elasticsearch.core.suggest.response.Suggest;
import org.springframework.lang.Nullable;
/**
* @author Peter-Josef Meisch
* @author Mohamed El Harrougui
* @since 4.4
*/
public class ReactiveSearchHitsImpl<T> implements ReactiveSearchHits<T> {
@@ -61,11 +58,6 @@ public class ReactiveSearchHitsImpl<T> implements ReactiveSearchHits<T> {
return delegate.getMaxScore();
}
@Override
public Duration getExecutionDuration() {
return delegate.getExecutionDuration();
}
@Override
public boolean hasSearchHits() {
return delegate.hasSearchHits();
@@ -22,5 +22,5 @@ package org.springframework.data.elasticsearch.core;
* @since 4.2
*/
public enum RefreshPolicy {
NONE, IMMEDIATE, WAIT_UNTIL
NONE, IMMEDIATE, WAIT_UNTIL;
}
@@ -15,7 +15,6 @@
*/
package org.springframework.data.elasticsearch.core;
import java.time.Duration;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedList;
@@ -47,8 +46,6 @@ import org.springframework.util.Assert;
* @author Matt Gilene
* @author Sascha Woo
* @author Jakob Hoeper
* @author Haibo Liu
* @author Mohamed El Harrougui
* @since 4.0
*/
public class SearchHitMapping<T> {
@@ -87,9 +84,7 @@ public class SearchHitMapping<T> {
"Count of documents must match the count of entities");
long totalHits = searchDocumentResponse.getTotalHits();
SearchShardStatistics shardStatistics = searchDocumentResponse.getSearchShardStatistics();
float maxScore = searchDocumentResponse.getMaxScore();
Duration executionDuration = searchDocumentResponse.getExecutionDuration();
String scrollId = searchDocumentResponse.getScrollId();
String pointInTimeId = searchDocumentResponse.getPointInTimeId();
@@ -107,8 +102,8 @@ public class SearchHitMapping<T> {
Suggest suggest = searchDocumentResponse.getSuggest();
mapHitsInCompletionSuggestion(suggest);
return new SearchHitsImpl<>(totalHits, totalHitsRelation, maxScore, executionDuration, scrollId, pointInTimeId,
searchHits, aggregations, suggest, shardStatistics);
return new SearchHitsImpl<>(totalHits, totalHitsRelation, maxScore, scrollId, pointInTimeId, searchHits,
aggregations, suggest);
}
@SuppressWarnings("unchecked")
@@ -170,7 +165,7 @@ public class SearchHitMapping<T> {
Map<String, SearchHits<?>> innerHits = new LinkedHashMap<>();
Map<String, SearchDocumentResponse> documentInnerHits = searchDocument.getInnerHits();
if (documentInnerHits != null && !documentInnerHits.isEmpty()) {
if (documentInnerHits != null && documentInnerHits.size() > 0) {
SearchHitMapping<SearchDocument> searchDocumentSearchHitMapping = SearchHitMapping
.mappingFor(SearchDocument.class, converter);
@@ -238,16 +233,14 @@ public class SearchHitMapping<T> {
scrollId = searchHitsImpl.getScrollId();
}
return new SearchHitsImpl<>(searchHits.getTotalHits(),
searchHits.getTotalHitsRelation(),
searchHits.getMaxScore(),
searchHits.getExecutionDuration(),
scrollId,
searchHits.getPointInTimeId(),
convertedSearchHits,
searchHits.getAggregations(),
searchHits.getSuggest(),
searchHits.getSearchShardStatistics());
return new SearchHitsImpl<>(searchHits.getTotalHits(), //
searchHits.getTotalHitsRelation(), //
searchHits.getMaxScore(), //
scrollId, //
searchHits.getPointInTimeId(), //
convertedSearchHits, //
searchHits.getAggregations(), //
searchHits.getSuggest());
}
} catch (Exception e) {
throw new UncategorizedElasticsearchException("Unable to convert inner hits.", e);
@@ -291,8 +284,8 @@ public class SearchHitMapping<T> {
}
private static class ElasticsearchPersistentEntityWithNestedMetaData {
@Nullable private final ElasticsearchPersistentEntity<?> entity;
private final NestedMetaData nestedMetaData;
@Nullable private ElasticsearchPersistentEntity<?> entity;
private NestedMetaData nestedMetaData;
public ElasticsearchPersistentEntityWithNestedMetaData(@Nullable ElasticsearchPersistentEntity<?> entity,
NestedMetaData nestedMetaData) {
@@ -23,8 +23,8 @@ import java.util.stream.Stream;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.repository.util.ReactiveWrappers;
import org.springframework.data.util.CloseableIterator;
import org.springframework.data.util.ReactiveWrappers;
import org.springframework.lang.Nullable;
/**
@@ -15,7 +15,6 @@
*/
package org.springframework.data.elasticsearch.core;
import java.time.Duration;
import java.util.Iterator;
import java.util.List;
@@ -28,8 +27,6 @@ import org.springframework.lang.Nullable;
*
* @param <T> the result data class.
* @author Sascha Woo
* @author Haibo Liu
* @author Mohamed El Harrougui
* @since 4.0
*/
public interface SearchHits<T> extends Streamable<SearchHit<T>> {
@@ -45,11 +42,6 @@ public interface SearchHits<T> extends Streamable<SearchHit<T>> {
*/
float getMaxScore();
/**
* @return the execution duration it took to complete the request
*/
Duration getExecutionDuration();
/**
* @param index position in List.
* @return the {@link SearchHit} at position {index}
@@ -116,10 +108,4 @@ public interface SearchHits<T> extends Streamable<SearchHit<T>> {
*/
@Nullable
String getPointInTimeId();
/**
* @return shard statistics for the search hit.
*/
@Nullable
SearchShardStatistics getSearchShardStatistics();
}
@@ -15,7 +15,6 @@
*/
package org.springframework.data.elasticsearch.core;
import java.time.Duration;
import java.util.Collections;
import java.util.List;
@@ -30,8 +29,6 @@ import org.springframework.util.Assert;
* @param <T> the result data class.
* @author Peter-Josef Meisch
* @author Sascha Woo
* @author Haibo Liu
* @author Mohamed El Harrougui
* @since 4.0
*/
public class SearchHitsImpl<T> implements SearchScrollHits<T> {
@@ -39,42 +36,36 @@ public class SearchHitsImpl<T> implements SearchScrollHits<T> {
private final long totalHits;
private final TotalHitsRelation totalHitsRelation;
private final float maxScore;
private final Duration executionDuration;
@Nullable private final String scrollId;
private final List<? extends SearchHit<T>> searchHits;
private final Lazy<List<SearchHit<T>>> unmodifiableSearchHits;
@Nullable private final AggregationsContainer<?> aggregations;
@Nullable private final Suggest suggest;
@Nullable private final String pointInTimeId;
@Nullable private final SearchShardStatistics searchShardStatistics;
@Nullable private String pointInTimeId;
/**
* @param totalHits the number of total hits for the search
* @param totalHitsRelation the relation {@see TotalHitsRelation}, must not be {@literal null}
* @param maxScore the maximum score
* @param executionDuration the execution duration it took to complete the request
* @param scrollId the scroll id if available
* @param searchHits must not be {@literal null}
* @param aggregations the aggregations if available
*/
public SearchHitsImpl(long totalHits, TotalHitsRelation totalHitsRelation, float maxScore, Duration executionDuration,
@Nullable String scrollId, @Nullable String pointInTimeId, List<? extends SearchHit<T>> searchHits,
@Nullable AggregationsContainer<?> aggregations, @Nullable Suggest suggest,
@Nullable SearchShardStatistics searchShardStatistics) {
public SearchHitsImpl(long totalHits, TotalHitsRelation totalHitsRelation, float maxScore, @Nullable String scrollId,
@Nullable String pointInTimeId, List<? extends SearchHit<T>> searchHits,
@Nullable AggregationsContainer<?> aggregations, @Nullable Suggest suggest) {
Assert.notNull(searchHits, "searchHits must not be null");
this.totalHits = totalHits;
this.totalHitsRelation = totalHitsRelation;
this.maxScore = maxScore;
this.executionDuration = executionDuration;
this.scrollId = scrollId;
this.pointInTimeId = pointInTimeId;
this.searchHits = searchHits;
this.aggregations = aggregations;
this.suggest = suggest;
this.unmodifiableSearchHits = Lazy.of(() -> Collections.unmodifiableList(searchHits));
this.searchShardStatistics = searchShardStatistics;
}
// region getter
@@ -93,11 +84,6 @@ public class SearchHitsImpl<T> implements SearchScrollHits<T> {
return maxScore;
}
@Override
public Duration getExecutionDuration() {
return executionDuration;
}
@Override
@Nullable
public String getScrollId() {
@@ -132,23 +118,16 @@ public class SearchHitsImpl<T> implements SearchScrollHits<T> {
return pointInTimeId;
}
@Override
public SearchShardStatistics getSearchShardStatistics() {
return searchShardStatistics;
}
@Override
public String toString() {
return "SearchHits{" + //
"totalHits=" + totalHits + //
", totalHitsRelation=" + totalHitsRelation + //
", maxScore=" + maxScore + //
", executionDuration=" + executionDuration + //
", scrollId='" + scrollId + '\'' + //
", pointInTimeId='" + pointInTimeId + '\'' + //
", searchHits={" + searchHits.size() + " elements}" + //
", aggregations=" + aggregations + //
", shardStatistics=" + searchShardStatistics + //
'}';
}
}
@@ -15,8 +15,6 @@
*/
package org.springframework.data.elasticsearch.core;
import java.time.Duration;
import org.springframework.data.util.CloseableIterator;
import org.springframework.lang.Nullable;
@@ -25,7 +23,6 @@ import org.springframework.lang.Nullable;
* {@link java.util.stream.Stream}.
*
* @author Sascha Woo
* @author Mohamed El Harrougui
* @param <T>
* @since 4.0
*/
@@ -42,11 +39,6 @@ public interface SearchHitsIterator<T> extends CloseableIterator<SearchHit<T>> {
*/
float getMaxScore();
/**
* @return the execution duration it took to complete the request
*/
Duration getExecutionDuration();
/**
* @return the number of total hits.
*/
@@ -1,130 +0,0 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.core;
import java.util.List;
import org.springframework.data.elasticsearch.ElasticsearchErrorCause;
import org.springframework.lang.Nullable;
/**
* @author Haibo Liu
* @since 5.3
*/
public class SearchShardStatistics {
private final Number failed;
private final Number successful;
private final Number total;
@Nullable private final Number skipped;
private final List<Failure> failures;
private SearchShardStatistics(Number failed, Number successful, Number total, @Nullable Number skipped,
List<Failure> failures) {
this.failed = failed;
this.successful = successful;
this.total = total;
this.skipped = skipped;
this.failures = failures;
}
public static SearchShardStatistics of(Number failed, Number successful, Number total, @Nullable Number skipped,
List<Failure> failures) {
return new SearchShardStatistics(failed, successful, total, skipped, failures);
}
public Number getFailed() {
return failed;
}
public Number getSuccessful() {
return successful;
}
public Number getTotal() {
return total;
}
@Nullable
public Number getSkipped() {
return skipped;
}
public boolean isFailed() {
return failed.intValue() > 0;
}
public List<Failure> getFailures() {
return failures;
}
public static class Failure {
@Nullable private final String index;
@Nullable private final String node;
@Nullable private final String status;
private final int shard;
@Nullable private final Exception exception;
@Nullable private final ElasticsearchErrorCause elasticsearchErrorCause;
private Failure(@Nullable String index, @Nullable String node, @Nullable String status, int shard,
@Nullable Exception exception, @Nullable ElasticsearchErrorCause elasticsearchErrorCause) {
this.index = index;
this.node = node;
this.status = status;
this.shard = shard;
this.exception = exception;
this.elasticsearchErrorCause = elasticsearchErrorCause;
}
public static SearchShardStatistics.Failure of(@Nullable String index, @Nullable String node,
@Nullable String status, int shard, @Nullable Exception exception,
@Nullable ElasticsearchErrorCause elasticsearchErrorCause) {
return new SearchShardStatistics.Failure(index, node, status, shard, exception, elasticsearchErrorCause);
}
@Nullable
public String getIndex() {
return index;
}
@Nullable
public String getNode() {
return node;
}
@Nullable
public String getStatus() {
return status;
}
@Nullable
public Exception getException() {
return exception;
}
public int getShard() {
return shard;
}
@Nullable
public ElasticsearchErrorCause getElasticsearchErrorCause() {
return elasticsearchErrorCause;
}
}
}
@@ -15,7 +15,6 @@
*/
package org.springframework.data.elasticsearch.core;
import java.time.Duration;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
@@ -32,7 +31,6 @@ import org.springframework.util.Assert;
*
* @author Mark Paluch
* @author Sascha Woo
* @author Mohamed El Harrougui
* @since 3.2
*/
abstract class StreamQueries {
@@ -58,16 +56,15 @@ abstract class StreamQueries {
AggregationsContainer<?> aggregations = searchHits.getAggregations();
float maxScore = searchHits.getMaxScore();
Duration executionDuration = searchHits.getExecutionDuration();
long totalHits = searchHits.getTotalHits();
TotalHitsRelation totalHitsRelation = searchHits.getTotalHitsRelation();
return new SearchHitsIterator<>() {
private final AtomicInteger currentCount = new AtomicInteger();
private volatile AtomicInteger currentCount = new AtomicInteger();
private volatile Iterator<SearchHit<T>> currentScrollHits = searchHits.iterator();
private volatile boolean continueScroll = currentScrollHits.hasNext();
private final ScrollState scrollState = new ScrollState(searchHits.getScrollId());
private volatile ScrollState scrollState = new ScrollState(searchHits.getScrollId());
private volatile boolean isClosed = false;
@Override
@@ -89,11 +86,6 @@ abstract class StreamQueries {
return maxScore;
}
@Override
public Duration getExecutionDuration() {
return executionDuration;
}
@Override
public long getTotalHits() {
return totalHits;
@@ -110,6 +110,6 @@ public interface ElasticsearchConverter
* @return a String wihere the property names are replaced with field names
* @since 5.2
*/
String updateFieldNames(String propertyPath, ElasticsearchPersistentEntity<?> persistentEntity);
public String updateFieldNames(String propertyPath, ElasticsearchPersistentEntity<?> persistentEntity);
// endregion
}
@@ -129,7 +129,7 @@ public class ElasticsearchCustomConversions extends CustomConversions {
@WritingConverter
enum ByteArrayToBase64Converter implements Converter<byte[], String> {
INSTANCE;
INSTANCE,;
@Override
public String convert(byte[] source) {
@@ -344,7 +344,7 @@ public class GeoConverters {
String type = GeoConverters.getGeoJsonType(source);
Assert.isTrue(type.equalsIgnoreCase(GeoJsonPolygon.TYPE), "does not contain a type 'Polygon'");
List<GeoJsonLineString> lines = geoJsonLineStringsFromMap(source);
Assert.isTrue(!lines.isEmpty(), "no linestrings defined in polygon");
Assert.isTrue(lines.size() > 0, "no linestrings defined in polygon");
GeoJsonPolygon geoJsonPolygon = GeoJsonPolygon.of(lines.get(0));
for (int i = 1; i < lines.size(); i++) {
geoJsonPolygon = geoJsonPolygon.withInnerRing(lines.get(i));
@@ -1,40 +0,0 @@
/*
* Copyright 2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.core.convert;
import org.springframework.lang.Nullable;
/**
* @since 5.3
* @author Peter-Josef Meisch
*/
public class MappingConversionException extends RuntimeException {
private final String documentId;
public MappingConversionException(@Nullable String documentId, Throwable cause) {
super(cause);
this.documentId = documentId != null ? documentId : "\"null\"";
}
public String getDocumentId() {
return documentId;
}
@Override
public String getMessage() {
return "Conversion exception when converting document id " + documentId;
}
}
@@ -34,9 +34,6 @@ import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.ConverterNotFoundException;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.core.env.Environment;
import org.springframework.core.env.EnvironmentCapable;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.data.convert.CustomConversions;
import org.springframework.data.elasticsearch.annotations.FieldType;
import org.springframework.data.elasticsearch.annotations.ScriptedField;
@@ -61,7 +58,6 @@ import org.springframework.data.mapping.SimplePropertyHandler;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.*;
import org.springframework.data.util.TypeInformation;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.format.datetime.DateFormatterRegistrar;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -84,11 +80,10 @@ import org.springframework.util.ObjectUtils;
* @author Marc Vanbrabant
* @author Anton Naydenov
* @author vdisk
* @author Junghoon Ban
* @since 3.2
*/
public class MappingElasticsearchConverter
implements ElasticsearchConverter, ApplicationContextAware, InitializingBean, EnvironmentCapable {
implements ElasticsearchConverter, ApplicationContextAware, InitializingBean {
private static final String INCOMPATIBLE_TYPES = "Cannot convert %1$s of type %2$s into an instance of %3$s! Implement a custom Converter<%2$s, %3$s> and register it with the CustomConversions.";
private static final String INVALID_TYPE_TO_READ = "Expected to read Document %s into type %s but didn't find a PersistentEntity for the latter!";
@@ -98,14 +93,7 @@ public class MappingElasticsearchConverter
private final MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext;
private final GenericConversionService conversionService;
private CustomConversions conversions = new ElasticsearchCustomConversions(Collections.emptyList());
protected @Nullable Environment environment;
private final SpELContext spELContext = new SpELContext(new MapAccessor());
private final SpelExpressionParser expressionParser = new SpelExpressionParser();
private final CachingValueExpressionEvaluatorFactory expressionEvaluatorFactory = new CachingValueExpressionEvaluatorFactory(
expressionParser, this, spELContext);
private final EntityInstantiators instantiators = new EntityInstantiators();
private final ElasticsearchTypeMapper typeMapper;
@@ -133,14 +121,6 @@ public class MappingElasticsearchConverter
}
}
@Override
public Environment getEnvironment() {
if (environment == null) {
environment = new StandardEnvironment();
}
return environment;
}
@Override
public MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> getMappingContext() {
return mappingContext;
@@ -179,8 +159,7 @@ public class MappingElasticsearchConverter
@Override
public <R> R read(Class<R> type, Document source) {
Reader reader = new Reader(mappingContext, conversionService, conversions, typeMapper, expressionEvaluatorFactory,
instantiators);
Reader reader = new Reader(mappingContext, conversionService, conversions, typeMapper, spELContext, instantiators);
return reader.read(type, source);
}
@@ -220,29 +199,29 @@ public class MappingElasticsearchConverter
*/
private static class Reader extends Base {
private final SpELContext spELContext;
private final EntityInstantiators instantiators;
private final CachingValueExpressionEvaluatorFactory expressionEvaluatorFactory;
public Reader(
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext,
GenericConversionService conversionService, CustomConversions conversions, ElasticsearchTypeMapper typeMapper,
CachingValueExpressionEvaluatorFactory expressionEvaluatorFactory, EntityInstantiators instantiators) {
SpELContext spELContext, EntityInstantiators instantiators) {
super(mappingContext, conversionService, conversions, typeMapper);
this.expressionEvaluatorFactory = expressionEvaluatorFactory;
this.spELContext = spELContext;
this.instantiators = instantiators;
}
@SuppressWarnings("unchecked")
/**
* Reads the given source into the given type.
*
* @param type the type to convert the given source to.
* @param type they type to convert the given source to.
* @param source the source to create an object of the given type from.
* @return the object that was read
*/
<R> R read(Class<R> type, Document source) {
// noinspection unchecked
TypeInformation<R> typeInformation = TypeInformation.of((Class<R>) ClassUtils.getUserClass(type));
R r = read(typeInformation, source);
@@ -334,7 +313,8 @@ public class MappingElasticsearchConverter
private <R> R readEntity(ElasticsearchPersistentEntity<?> entity, Map<String, Object> source) {
ElasticsearchPersistentEntity<?> targetEntity = computeClosestEntity(entity, source);
ValueExpressionEvaluator evaluator = expressionEvaluatorFactory.create(source);
SpELExpressionEvaluator evaluator = new DefaultSpELExpressionEvaluator(source, spELContext);
MapValueAccessor accessor = new MapValueAccessor(source);
InstanceCreatorMetadata<?> creatorMetadata = entity.getInstanceCreatorMetadata();
@@ -352,56 +332,50 @@ public class MappingElasticsearchConverter
return instance;
}
Document document = (source instanceof Document) ? (Document) source : null;
ElasticsearchPropertyValueProvider valueProvider = new ElasticsearchPropertyValueProvider(accessor, evaluator);
try {
R result = readProperties(targetEntity, instance, valueProvider);
R result = readProperties(targetEntity, instance, valueProvider);
if (document != null) {
if (document.hasId()) {
ElasticsearchPersistentProperty idProperty = targetEntity.getIdProperty();
PersistentPropertyAccessor<R> propertyAccessor = new ConvertingPropertyAccessor<>(
targetEntity.getPropertyAccessor(result), conversionService);
// Only deal with String because ES generated Ids are strings !
if (idProperty != null && idProperty.isReadable() && idProperty.getType().isAssignableFrom(String.class)) {
propertyAccessor.setProperty(idProperty, document.getId());
}
}
if (document.hasVersion()) {
long version = document.getVersion();
ElasticsearchPersistentProperty versionProperty = targetEntity.getVersionProperty();
// Only deal with Long because ES versions are longs !
if (versionProperty != null && versionProperty.getType().isAssignableFrom(Long.class)) {
// check that a version was actually returned in the response, -1 would indicate that
// a search didn't request the version ids in the response, which would be an issue
Assert.isTrue(version != -1, "Version in response is -1");
targetEntity.getPropertyAccessor(result).setProperty(versionProperty, version);
}
}
if (targetEntity.hasSeqNoPrimaryTermProperty() && document.hasSeqNo() && document.hasPrimaryTerm()) {
if (isAssignedSeqNo(document.getSeqNo()) && isAssignedPrimaryTerm(document.getPrimaryTerm())) {
SeqNoPrimaryTerm seqNoPrimaryTerm = new SeqNoPrimaryTerm(document.getSeqNo(), document.getPrimaryTerm());
ElasticsearchPersistentProperty property = targetEntity.getRequiredSeqNoPrimaryTermProperty();
targetEntity.getPropertyAccessor(result).setProperty(property, seqNoPrimaryTerm);
}
if (source instanceof Document document) {
if (document.hasId()) {
ElasticsearchPersistentProperty idProperty = targetEntity.getIdProperty();
PersistentPropertyAccessor<R> propertyAccessor = new ConvertingPropertyAccessor<>(
targetEntity.getPropertyAccessor(result), conversionService);
// Only deal with String because ES generated Ids are strings !
if (idProperty != null && idProperty.isReadable() && idProperty.getType().isAssignableFrom(String.class)) {
propertyAccessor.setProperty(idProperty, document.getId());
}
}
if (source instanceof SearchDocument searchDocument) {
populateScriptFields(targetEntity, result, searchDocument);
if (document.hasVersion()) {
long version = document.getVersion();
ElasticsearchPersistentProperty versionProperty = targetEntity.getVersionProperty();
// Only deal with Long because ES versions are longs !
if (versionProperty != null && versionProperty.getType().isAssignableFrom(Long.class)) {
// check that a version was actually returned in the response, -1 would indicate that
// a search didn't request the version ids in the response, which would be an issue
Assert.isTrue(version != -1, "Version in response is -1");
targetEntity.getPropertyAccessor(result).setProperty(versionProperty, version);
}
}
if (targetEntity.hasSeqNoPrimaryTermProperty() && document.hasSeqNo() && document.hasPrimaryTerm()) {
if (isAssignedSeqNo(document.getSeqNo()) && isAssignedPrimaryTerm(document.getPrimaryTerm())) {
SeqNoPrimaryTerm seqNoPrimaryTerm = new SeqNoPrimaryTerm(document.getSeqNo(), document.getPrimaryTerm());
ElasticsearchPersistentProperty property = targetEntity.getRequiredSeqNoPrimaryTermProperty();
targetEntity.getPropertyAccessor(result).setProperty(property, seqNoPrimaryTerm);
}
}
return result;
} catch (ConversionException e) {
String documentId = (document != null && document.hasId()) ? document.getId() : null;
throw new MappingConversionException(documentId, e);
}
if (source instanceof SearchDocument searchDocument) {
populateScriptFields(targetEntity, result, searchDocument);
}
return result;
}
private ParameterValueProvider<ElasticsearchPersistentProperty> getParameterProvider(
ElasticsearchPersistentEntity<?> entity, MapValueAccessor source, ValueExpressionEvaluator evaluator) {
ElasticsearchPersistentEntity<?> entity, MapValueAccessor source, SpELExpressionEvaluator evaluator) {
ElasticsearchPropertyValueProvider provider = new ElasticsearchPropertyValueProvider(source, evaluator);
@@ -410,7 +384,7 @@ public class MappingElasticsearchConverter
PersistentEntityParameterValueProvider<ElasticsearchPersistentProperty> parameterProvider = new PersistentEntityParameterValueProvider<>(
entity, provider, null);
return new ConverterAwareValueExpressionParameterValueProvider(evaluator, conversionService, parameterProvider);
return new ConverterAwareSpELExpressionParameterValueProvider(evaluator, conversionService, parameterProvider);
}
private boolean isAssignedSeqNo(long seqNo) {
@@ -492,7 +466,7 @@ public class MappingElasticsearchConverter
TypeInformation<?> collectionComponentType = getCollectionComponentType(type);
if (collectionComponentType != null) {
Object o = read(collectionComponentType, (Map<String, Object>) value);
return (o != null) ? getCollectionWithSingleElement(type, collectionComponentType, o) : null;
return getCollectionWithSingleElement(type, collectionComponentType, o);
}
return (T) read(type, (Map<String, Object>) value);
} else {
@@ -501,7 +475,7 @@ public class MappingElasticsearchConverter
if (collectionComponentType != null
&& collectionComponentType.isAssignableFrom(TypeInformation.of(value.getClass()))) {
Object o = getPotentiallyConvertedSimpleRead(value, collectionComponentType);
return (o != null) ? getCollectionWithSingleElement(type, collectionComponentType, o) : null;
return getCollectionWithSingleElement(type, collectionComponentType, o);
}
return (T) getPotentiallyConvertedSimpleRead(value, rawType);
@@ -519,7 +493,7 @@ public class MappingElasticsearchConverter
/**
* @param type the type to check
* @return the collection type if type is a collection, null otherwise,
* @return true if type is a collectoin, null otherwise,
*/
@Nullable
TypeInformation<?> getCollectionComponentType(TypeInformation<?> type) {
@@ -529,15 +503,17 @@ public class MappingElasticsearchConverter
private Object propertyConverterRead(ElasticsearchPersistentProperty property, Object source) {
PropertyValueConverter propertyValueConverter = Objects.requireNonNull(property.getPropertyValueConverter());
if (source instanceof String[] strings) {
if (source instanceof String[]) {
// convert to a List
source = Arrays.asList(strings);
source = Arrays.asList((String[]) source);
}
if (source instanceof List<?> list) {
source = list.stream().map(it -> convertOnRead(propertyValueConverter, it)).collect(Collectors.toList());
} else if (source instanceof Set<?> set) {
source = set.stream().map(it -> convertOnRead(propertyValueConverter, it)).collect(Collectors.toSet());
if (source instanceof List) {
source = ((List<?>) source).stream().map(it -> convertOnRead(propertyValueConverter, it))
.collect(Collectors.toList());
} else if (source instanceof Set) {
source = ((Set<?>) source).stream().map(it -> convertOnRead(propertyValueConverter, it))
.collect(Collectors.toSet());
} else {
source = convertOnRead(propertyValueConverter, source);
}
@@ -635,10 +611,9 @@ public class MappingElasticsearchConverter
* but will be removed from spring-data-commons, so we do it here
*/
@Nullable
private Object convertFromCollectionToObject(Object value, Class<?> target) {
private Object convertFromCollectionToObject(Object value, @Nullable Class<?> target) {
if (value.getClass().isArray()) {
// noinspection ArraysAsListWithZeroOrOneArgument
value = Arrays.asList(value);
}
@@ -688,9 +663,9 @@ public class MappingElasticsearchConverter
class ElasticsearchPropertyValueProvider implements PropertyValueProvider<ElasticsearchPersistentProperty> {
final MapValueAccessor accessor;
final ValueExpressionEvaluator evaluator;
final SpELExpressionEvaluator evaluator;
ElasticsearchPropertyValueProvider(MapValueAccessor accessor, ValueExpressionEvaluator evaluator) {
ElasticsearchPropertyValueProvider(MapValueAccessor accessor, SpELExpressionEvaluator evaluator) {
this.accessor = accessor;
this.evaluator = evaluator;
}
@@ -710,29 +685,33 @@ public class MappingElasticsearchConverter
}
/**
* Extension of {@link ValueExpressionParameterValueProvider} to recursively trigger value conversion on the raw
* Extension of {@link SpELExpressionParameterValueProvider} to recursively trigger value conversion on the raw
* resolved SpEL value.
*
* @author Mark Paluch
*/
private class ConverterAwareValueExpressionParameterValueProvider
extends ValueExpressionParameterValueProvider<ElasticsearchPersistentProperty> {
private class ConverterAwareSpELExpressionParameterValueProvider
extends SpELExpressionParameterValueProvider<ElasticsearchPersistentProperty> {
/**
* Creates a new {@link ConverterAwareValueExpressionParameterValueProvider}.
* Creates a new {@link ConverterAwareSpELExpressionParameterValueProvider}.
*
* @param evaluator must not be {@literal null}.
* @param conversionService must not be {@literal null}.
* @param delegate must not be {@literal null}.
*/
public ConverterAwareValueExpressionParameterValueProvider(ValueExpressionEvaluator evaluator,
public ConverterAwareSpELExpressionParameterValueProvider(SpELExpressionEvaluator evaluator,
ConversionService conversionService, ParameterValueProvider<ElasticsearchPersistentProperty> delegate) {
super(evaluator, conversionService, delegate);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.model.SpELExpressionParameterValueProvider#potentiallyConvertSpelValue(java.lang.Object, org.springframework.data.mapping.PreferredConstructor.Parameter)
*/
@Override
protected <T> T potentiallyConvertExpressionValue(Object object,
protected <T> T potentiallyConvertSpelValue(Object object,
Parameter<T, ElasticsearchPersistentProperty> parameter) {
return readValue(object, parameter.getType());
}
@@ -1009,8 +988,12 @@ public class MappingElasticsearchConverter
private static boolean hasEmptyValue(Object value) {
return value instanceof String s && s.isEmpty() || value instanceof Collection<?> c && c.isEmpty()
|| value instanceof Map<?, ?> m && m.isEmpty();
if (value instanceof String s && s.isEmpty() || value instanceof Collection<?> c && c.isEmpty()
|| value instanceof Map<?, ?> m && m.isEmpty()) {
return true;
}
return false;
}
@SuppressWarnings("unchecked")
@@ -1412,18 +1395,12 @@ public class MappingElasticsearchConverter
if (properties.length > 1) {
var persistentProperty = persistentEntity.getPersistentProperty(propertyName);
if (persistentProperty != null) {
ElasticsearchPersistentEntity<?> nestedPersistentEntity = mappingContext
.getPersistentEntity(persistentProperty);
if (nestedPersistentEntity != null) {
return fieldName + '.' + updateFieldNames(properties[1], nestedPersistentEntity);
} else {
return fieldName;
}
}
return (persistentProperty != null)
? fieldName + "." + updateFieldNames(properties[1], mappingContext.getPersistentEntity(persistentProperty))
: fieldName;
} else {
return fieldName;
}
return fieldName;
} else {
return propertyPath;
}
@@ -1432,7 +1409,6 @@ public class MappingElasticsearchConverter
// endregion
@SuppressWarnings("ClassCanBeRecord")
static class MapValueAccessor {
final Map<String, Object> target;
@@ -18,7 +18,11 @@ package org.springframework.data.elasticsearch.core.document;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.BooleanSupplier;
import java.util.function.Function;
import java.util.function.IntSupplier;
import java.util.function.LongSupplier;
import java.util.function.Supplier;
import org.springframework.data.elasticsearch.core.convert.ConversionException;
import org.springframework.data.elasticsearch.support.StringObjectMap;
@@ -26,12 +30,12 @@ import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* A representation of an Elasticsearch document as extended {@link StringObjectMap Map}. All iterators preserve
* original insertion order.
* A representation of a Elasticsearch document as extended {@link StringObjectMap Map}. All iterators preserve original
* insertion order.
* <p>
* Document does not allow {@code null} keys. It allows {@literal null} values.
* <p>
* Implementing classes can be either mutable or immutable. In case a subclass is immutable, its methods may throw
* Implementing classes can bei either mutable or immutable. In case a subclass is immutable, its methods may throw
* {@link UnsupportedOperationException} when calling modifying methods.
*
* @author Mark Paluch
@@ -56,7 +60,7 @@ public interface Document extends StringObjectMap<Document> {
* @param map source map containing key-value pairs and sub-documents. must not be {@literal null}.
* @return a new {@link Document}.
*/
static Document from(Map<String, ?> map) {
static Document from(Map<String, ? extends Object> map) {
Assert.notNull(map, "Map must not be null");
@@ -15,13 +15,11 @@
*/
package org.springframework.data.elasticsearch.core.document;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
import org.springframework.data.elasticsearch.core.AggregationsContainer;
import org.springframework.data.elasticsearch.core.SearchShardStatistics;
import org.springframework.data.elasticsearch.core.suggest.response.Suggest;
import org.springframework.lang.Nullable;
@@ -29,8 +27,6 @@ import org.springframework.lang.Nullable;
* This represents the complete search response from Elasticsearch, including the returned documents.
*
* @author Peter-Josef Meisch
* @author Haibo Liu
* @author Mohamed El Harrougui
* @since 4.0
*/
public class SearchDocumentResponse {
@@ -38,29 +34,24 @@ public class SearchDocumentResponse {
private final long totalHits;
private final String totalHitsRelation;
private final float maxScore;
private final Duration executionDuration;
@Nullable private final String scrollId;
private final List<SearchDocument> searchDocuments;
@Nullable private final AggregationsContainer<?> aggregations;
@Nullable private final Suggest suggest;
@Nullable String pointInTimeId;
@Nullable private final SearchShardStatistics searchShardStatistics;
public SearchDocumentResponse(long totalHits, String totalHitsRelation, float maxScore, Duration executionDuration,
@Nullable String scrollId, @Nullable String pointInTimeId, List<SearchDocument> searchDocuments,
@Nullable AggregationsContainer<?> aggregationsContainer, @Nullable Suggest suggest,
@Nullable SearchShardStatistics searchShardStatistics) {
public SearchDocumentResponse(long totalHits, String totalHitsRelation, float maxScore, @Nullable String scrollId,
@Nullable String pointInTimeId, List<SearchDocument> searchDocuments,
@Nullable AggregationsContainer<?> aggregationsContainer, @Nullable Suggest suggest) {
this.totalHits = totalHits;
this.totalHitsRelation = totalHitsRelation;
this.maxScore = maxScore;
this.executionDuration = executionDuration;
this.scrollId = scrollId;
this.pointInTimeId = pointInTimeId;
this.searchDocuments = searchDocuments;
this.aggregations = aggregationsContainer;
this.suggest = suggest;
this.searchShardStatistics = searchShardStatistics;
}
public long getTotalHits() {
@@ -75,10 +66,6 @@ public class SearchDocumentResponse {
return maxScore;
}
public Duration getExecutionDuration() {
return executionDuration;
}
@Nullable
public String getScrollId() {
return scrollId;
@@ -106,11 +93,6 @@ public class SearchDocumentResponse {
return pointInTimeId;
}
@Nullable
public SearchShardStatistics getSearchShardStatistics() {
return searchShardStatistics;
}
/**
* A function to convert a {@link SearchDocument} async into an entity. Asynchronous so that it can be used from the
* imperative and the reactive code.
@@ -24,8 +24,8 @@ import org.springframework.data.geo.Box;
*/
public class GeoBox {
private final GeoPoint topLeft;
private final GeoPoint bottomRight;
private GeoPoint topLeft;
private GeoPoint bottomRight;
public GeoBox(GeoPoint topLeft, GeoPoint bottomRight) {
this.topLeft = topLeft;
@@ -33,7 +33,7 @@ public class GeoJsonMultiPolygon implements GeoJson<Iterable<GeoJsonPolygon>> {
public static final String TYPE = "MultiPolygon";
private final List<GeoJsonPolygon> coordinates = new ArrayList<>();
private List<GeoJsonPolygon> coordinates = new ArrayList<>();
private GeoJsonMultiPolygon(List<GeoJsonPolygon> polygons) {
this.coordinates.addAll(polygons);
@@ -69,7 +69,6 @@ import com.fasterxml.jackson.databind.util.RawValue;
* @author Peter-Josef Meisch
* @author Xiao Yu
* @author Subhobrata Dey
* @author Andriy Redko
*/
public class MappingBuilder {
@@ -176,9 +175,7 @@ public class MappingBuilder {
.findAnnotation(org.springframework.data.elasticsearch.annotations.Document.class);
var dynamicMapping = docAnnotation != null ? docAnnotation.dynamic() : null;
final FieldType fieldType = FieldType.Auto;
mapEntity(objectNode, entity, true, "", false, fieldType, fieldType.getMappedName(), null, dynamicMapping,
runtimeFields);
mapEntity(objectNode, entity, true, "", false, FieldType.Auto, null, dynamicMapping, runtimeFields);
if (!excludeFromSource.isEmpty()) {
ObjectNode sourceNode = objectNode.putObject(SOURCE);
@@ -214,13 +211,11 @@ public class MappingBuilder {
private void mapEntity(ObjectNode objectNode, @Nullable ElasticsearchPersistentEntity<?> entity,
boolean isRootObject, String nestedObjectFieldName, boolean nestedOrObjectField, FieldType fieldType,
String fieldTypeMappedName,
@Nullable Field parentFieldAnnotation, @Nullable Dynamic dynamicMapping, @Nullable Document runtimeFields)
throws IOException {
var mappingAnnotation = entity != null ? entity.findAnnotation(Mapping.class) : null;
if (mappingAnnotation != null) {
if (entity != null && entity.isAnnotationPresent(Mapping.class)) {
Mapping mappingAnnotation = entity.getRequiredAnnotation(Mapping.class);
if (!mappingAnnotation.enabled()) {
objectNode.put(MAPPING_ENABLED, false);
@@ -248,7 +243,7 @@ public class MappingBuilder {
boolean writeNestedProperties = !isRootObject && (isAnyPropertyAnnotatedWithField(entity) || nestedOrObjectField);
if (writeNestedProperties) {
String type = nestedOrObjectField ? fieldTypeMappedName : FieldType.Object.getMappedName();
String type = nestedOrObjectField ? fieldType.getMappedName() : FieldType.Object.getMappedName();
ObjectNode nestedObjectNode = objectMapper.createObjectNode();
nestedObjectNode.put(FIELD_PARAM_TYPE, type);
@@ -294,16 +289,6 @@ public class MappingBuilder {
LOGGER.warn(String.format("error mapping property with name %s", property.getName()), e);
}
});
}
// write the alias entries after the properties
if (mappingAnnotation != null) {
for (MappingAlias mappingAlias : mappingAnnotation.aliases()) {
var aliasNode = propertiesNode.putObject(mappingAlias.name());
aliasNode.put(FIELD_PARAM_TYPE, FIELD_PARAM_TYPE_ALIAS);
aliasNode.put(FIELD_PARAM_PATH, mappingAlias.path());
}
}
}
@@ -376,7 +361,7 @@ public class MappingBuilder {
nestedPropertyPrefix = nestedPropertyPath;
mapEntity(propertiesNode, persistentEntity, false, property.getFieldName(), true, fieldAnnotation.type(),
getMappedTypeName(fieldAnnotation), fieldAnnotation, dynamicMapping, null);
fieldAnnotation, dynamicMapping, null);
nestedPropertyPrefix = currentNestedPropertyPrefix;
return;
@@ -447,7 +432,7 @@ public class MappingBuilder {
contextNode.put(FIELD_CONTEXT_NAME, context.name());
contextNode.put(FIELD_CONTEXT_TYPE, context.type().getMappedName());
if (!context.precision().isEmpty()) {
if (context.precision().length() > 0) {
contextNode.put(FIELD_CONTEXT_PRECISION, context.precision());
}
@@ -477,7 +462,7 @@ public class MappingBuilder {
}
propertiesNode.set(property.getFieldName(), objectMapper.createObjectNode() //
.put(FIELD_PARAM_TYPE, getMappedTypeName(field)) //
.put(FIELD_PARAM_TYPE, field.type().getMappedName()) //
.put(MAPPING_ENABLED, false) //
);
@@ -486,16 +471,6 @@ public class MappingBuilder {
}
}
/**
* Return the mapping type name to be used for the {@link Field}
*
* @param field field to return the mapping type name for
* @return the mapping type name
*/
private String getMappedTypeName(Field field) {
return StringUtils.hasText(field.mappedTypeName()) ? field.mappedTypeName() : field.type().getMappedName();
}
/**
* Add mapping for @Field annotation
*
@@ -23,7 +23,15 @@ import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.data.elasticsearch.annotations.*;
import org.springframework.data.elasticsearch.annotations.DateFormat;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import org.springframework.data.elasticsearch.annotations.IndexOptions;
import org.springframework.data.elasticsearch.annotations.IndexPrefixes;
import org.springframework.data.elasticsearch.annotations.InnerField;
import org.springframework.data.elasticsearch.annotations.NullValueType;
import org.springframework.data.elasticsearch.annotations.Similarity;
import org.springframework.data.elasticsearch.annotations.TermVector;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -41,7 +49,6 @@ import com.fasterxml.jackson.databind.node.TextNode;
* @author Brian Kimmig
* @author Morgan Lutz
* @author Sascha Woo
* @author Haibo Liu
* @since 4.0
*/
public final class MappingParameters {
@@ -71,18 +78,12 @@ public final class MappingParameters {
static final String FIELD_PARAM_ORIENTATION = "orientation";
static final String FIELD_PARAM_POSITIVE_SCORE_IMPACT = "positive_score_impact";
static final String FIELD_PARAM_DIMS = "dims";
static final String FIELD_PARAM_ELEMENT_TYPE = "element_type";
static final String FIELD_PARAM_M = "m";
static final String FIELD_PARAM_EF_CONSTRUCTION = "ef_construction";
static final String FIELD_PARAM_CONFIDENCE_INTERVAL = "confidence_interval";
static final String FIELD_PARAM_SCALING_FACTOR = "scaling_factor";
static final String FIELD_PARAM_SEARCH_ANALYZER = "search_analyzer";
static final String FIELD_PARAM_STORE = "store";
static final String FIELD_PARAM_SIMILARITY = "similarity";
static final String FIELD_PARAM_TERM_VECTOR = "term_vector";
static final String FIELD_PARAM_TYPE = "type";
static final String FIELD_PARAM_PATH = "path";
static final String FIELD_PARAM_TYPE_ALIAS = "alias";
private final String analyzer;
private final boolean coerce;
@@ -107,16 +108,12 @@ public final class MappingParameters {
private final Integer positionIncrementGap;
private final boolean positiveScoreImpact;
private final Integer dims;
private final String elementType;
private final KnnSimilarity knnSimilarity;
@Nullable private final KnnIndexOptions knnIndexOptions;
private final String searchAnalyzer;
private final double scalingFactor;
private final String similarity;
private final boolean store;
private final TermVector termVector;
private final FieldType type;
private final String mappedTypeName;
/**
* extracts the mapping parameters from the relevant annotations.
@@ -142,7 +139,6 @@ public final class MappingParameters {
store = field.store();
fielddata = field.fielddata();
type = field.type();
mappedTypeName = StringUtils.hasText(field.mappedTypeName()) ? field.mappedTypeName() : type.getMappedName();
dateFormats = field.format();
dateFormatPatterns = field.pattern();
analyzer = field.analyzer();
@@ -176,9 +172,6 @@ public final class MappingParameters {
Assert.isTrue(dims >= 1 && dims <= 4096,
"Invalid required parameter! Dense_Vector value \"dims\" must be between 1 and 4096.");
}
elementType = field.elementType();
knnSimilarity = field.knnSimilarity();
knnIndexOptions = field.knnIndexOptions().length > 0 ? field.knnIndexOptions()[0] : null;
Assert.isTrue(field.enabled() || type == FieldType.Object, "enabled false is only allowed for field type object");
enabled = field.enabled();
eagerGlobalOrdinals = field.eagerGlobalOrdinals();
@@ -189,7 +182,6 @@ public final class MappingParameters {
store = field.store();
fielddata = field.fielddata();
type = field.type();
mappedTypeName = StringUtils.hasText(field.mappedTypeName()) ? field.mappedTypeName() : type.getMappedName();
dateFormats = field.format();
dateFormatPatterns = field.pattern();
analyzer = field.analyzer();
@@ -223,9 +215,6 @@ public final class MappingParameters {
Assert.isTrue(dims >= 1 && dims <= 4096,
"Invalid required parameter! Dense_Vector value \"dims\" must be between 1 and 4096.");
}
elementType = field.elementType();
knnSimilarity = field.knnSimilarity();
knnIndexOptions = field.knnIndexOptions().length > 0 ? field.knnIndexOptions()[0] : null;
enabled = true;
eagerGlobalOrdinals = field.eagerGlobalOrdinals();
}
@@ -248,7 +237,7 @@ public final class MappingParameters {
}
if (type != FieldType.Auto) {
objectNode.put(FIELD_PARAM_TYPE, mappedTypeName);
objectNode.put(FIELD_PARAM_TYPE, type.getMappedName());
if (type == FieldType.Date || type == FieldType.Date_Nanos || type == FieldType.Date_Range) {
List<String> formats = new ArrayList<>();
@@ -365,48 +354,6 @@ public final class MappingParameters {
if (type == FieldType.Dense_Vector) {
objectNode.put(FIELD_PARAM_DIMS, dims);
if (!FieldElementType.DEFAULT.equals(elementType)) {
objectNode.put(FIELD_PARAM_ELEMENT_TYPE, elementType);
}
if (knnSimilarity != KnnSimilarity.DEFAULT) {
objectNode.put(FIELD_PARAM_SIMILARITY, knnSimilarity.getSimilarity());
}
if (knnSimilarity != KnnSimilarity.DEFAULT) {
Assert.isTrue(index, "knn similarity can only be specified when 'index' is true.");
objectNode.put(FIELD_PARAM_SIMILARITY, knnSimilarity.getSimilarity());
}
if (knnIndexOptions != null) {
Assert.isTrue(index, "knn index options can only be specified when 'index' is true.");
ObjectNode indexOptionsNode = objectNode.putObject(FIELD_PARAM_INDEX_OPTIONS);
KnnAlgorithmType algoType = knnIndexOptions.type();
if (algoType != KnnAlgorithmType.DEFAULT) {
if (algoType == KnnAlgorithmType.INT8_HNSW || algoType == KnnAlgorithmType.INT8_FLAT) {
Assert.isTrue(!FieldElementType.BYTE.equals(elementType),
"'element_type' can only be float when using vector quantization.");
}
indexOptionsNode.put(FIELD_PARAM_TYPE, algoType.getType());
}
if (knnIndexOptions.m() >= 0) {
Assert.isTrue(algoType == KnnAlgorithmType.HNSW || algoType == KnnAlgorithmType.INT8_HNSW,
"knn 'm' parameter can only be applicable to hnsw and int8_hnsw index types.");
indexOptionsNode.put(FIELD_PARAM_M, knnIndexOptions.m());
}
if (knnIndexOptions.efConstruction() >= 0) {
Assert.isTrue(algoType == KnnAlgorithmType.HNSW || algoType == KnnAlgorithmType.INT8_HNSW,
"knn 'ef_construction' can only be applicable to hnsw and int8_hnsw index types.");
indexOptionsNode.put(FIELD_PARAM_EF_CONSTRUCTION, knnIndexOptions.efConstruction());
}
if (knnIndexOptions.confidenceInterval() >= 0) {
Assert.isTrue(algoType == KnnAlgorithmType.INT8_HNSW
|| algoType == KnnAlgorithmType.INT8_FLAT,
"knn 'confidence_interval' can only be applicable to int8_hnsw and int8_flat index types.");
indexOptionsNode.put(FIELD_PARAM_CONFIDENCE_INTERVAL, knnIndexOptions.confidenceInterval());
}
}
}
if (!enabled) {
@@ -17,7 +17,7 @@ package org.springframework.data.elasticsearch.core.join;
import java.util.Objects;
import org.springframework.data.annotation.PersistenceCreator;
import org.springframework.data.annotation.PersistenceConstructor;
import org.springframework.lang.Nullable;
/**
@@ -39,7 +39,7 @@ public class JoinField<ID> {
this(name, null);
}
@PersistenceCreator
@PersistenceConstructor
public JoinField(String name, @Nullable ID parent) {
this.name = name;
this.parent = parent;
@@ -1,218 +0,0 @@
/*
* Copyright 2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.core.mapping;
import java.util.Objects;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Immutable Value object encapsulating index alias(es).
*
* @author Youssef Aouichaoui
* @since 5.4
*/
public class Alias {
/**
* Alias name for the index.
*/
private final String alias;
/**
* Query used to limit documents the alias can access.
*/
@Nullable private final Query filter;
/**
* Used to route indexing operations to a specific shard.
*/
@Nullable private final String indexRouting;
/**
* Used to route search operations to a specific shard.
*/
@Nullable private final String searchRouting;
/**
* Used to route indexing and search operations to a specific shard.
*/
@Nullable private final String routing;
/**
* The alias is hidden? By default, this is set to {@code false}.
*/
@Nullable private final Boolean isHidden;
/**
* The index is the 'write index' for the alias? By default, this is set to {@code false}.
*/
@Nullable private final Boolean isWriteIndex;
private Alias(Builder builder) {
this.alias = builder.alias;
this.filter = builder.filter;
this.indexRouting = builder.indexRouting;
this.searchRouting = builder.searchRouting;
this.routing = builder.routing;
this.isHidden = builder.isHidden;
this.isWriteIndex = builder.isWriteIndex;
}
public String getAlias() {
return alias;
}
@Nullable
public Query getFilter() {
return filter;
}
@Nullable
public String getIndexRouting() {
return indexRouting;
}
@Nullable
public String getSearchRouting() {
return searchRouting;
}
@Nullable
public String getRouting() {
return routing;
}
@Nullable
public Boolean getHidden() {
return isHidden;
}
@Nullable
public Boolean getWriteIndex() {
return isWriteIndex;
}
public static Builder builder(String alias) {
return new Builder(alias);
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (!(o instanceof Alias that))
return false;
return Objects.equals(alias, that.alias) && Objects.equals(filter, that.filter)
&& Objects.equals(indexRouting, that.indexRouting)
&& Objects.equals(searchRouting, that.searchRouting)
&& Objects.equals(routing, that.routing)
&& Objects.equals(isHidden, that.isHidden)
&& Objects.equals(isWriteIndex, that.isWriteIndex);
}
@Override
public int hashCode() {
return Objects.hash(alias, filter, indexRouting, searchRouting, routing, isHidden, isWriteIndex);
}
public static class Builder {
private final String alias;
@Nullable private Query filter;
@Nullable private String indexRouting;
@Nullable private String searchRouting;
@Nullable private String routing;
@Nullable private Boolean isHidden;
@Nullable private Boolean isWriteIndex;
public Builder(String alias) {
Assert.notNull(alias, "alias must not be null");
this.alias = alias;
}
/**
* Query used to limit documents the alias can access.
*/
public Builder withFilter(@Nullable Query filter) {
this.filter = filter;
return this;
}
/**
* Used to route indexing operations to a specific shard.
*/
public Builder withIndexRouting(@Nullable String indexRouting) {
if (indexRouting != null && !indexRouting.trim().isEmpty()) {
this.indexRouting = indexRouting;
}
return this;
}
/**
* Used to route search operations to a specific shard.
*/
public Builder withSearchRouting(@Nullable String searchRouting) {
if (searchRouting != null && !searchRouting.trim().isEmpty()) {
this.searchRouting = searchRouting;
}
return this;
}
/**
* Used to route indexing and search operations to a specific shard.
*/
public Builder withRouting(@Nullable String routing) {
if (routing != null && !routing.trim().isEmpty()) {
this.routing = routing;
}
return this;
}
/**
* The alias is hidden? By default, this is set to {@code false}.
*/
public Builder withHidden(@Nullable Boolean hidden) {
isHidden = hidden;
return this;
}
/**
* The index is the 'write index' for the alias? By default, this is set to {@code false}.
*/
public Builder withWriteIndex(@Nullable Boolean writeIndex) {
isWriteIndex = writeIndex;
return this;
}
public Alias build() {
return new Alias(this);
}
}
}
@@ -1,114 +0,0 @@
/*
* Copyright 2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.core.mapping;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.springframework.data.elasticsearch.core.document.Document;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Encapsulating index mapping fields, settings, and index alias(es).
*
* @author Youssef Aouichaoui
* @since 5.3
*/
public class CreateIndexSettings {
private final IndexCoordinates indexCoordinates;
private final Set<Alias> aliases;
@Nullable private final Map<String, Object> settings;
@Nullable private final Document mapping;
private CreateIndexSettings(Builder builder) {
this.indexCoordinates = builder.indexCoordinates;
this.aliases = builder.aliases;
this.settings = builder.settings;
this.mapping = builder.mapping;
}
public static Builder builder(IndexCoordinates indexCoordinates) {
return new Builder(indexCoordinates);
}
public IndexCoordinates getIndexCoordinates() {
return indexCoordinates;
}
public Alias[] getAliases() {
return aliases.toArray(Alias[]::new);
}
@Nullable
public Map<String, Object> getSettings() {
return settings;
}
@Nullable
public Document getMapping() {
return mapping;
}
public static class Builder {
private final IndexCoordinates indexCoordinates;
private final Set<Alias> aliases = new HashSet<>();
@Nullable private Map<String, Object> settings;
@Nullable private Document mapping;
public Builder(IndexCoordinates indexCoordinates) {
Assert.notNull(indexCoordinates, "indexCoordinates must not be null");
this.indexCoordinates = indexCoordinates;
}
public Builder withAlias(Alias alias) {
Assert.notNull(alias, "alias must not be null");
this.aliases.add(alias);
return this;
}
public Builder withAliases(Set<Alias> aliases) {
Assert.notNull(aliases, "aliases must not be null");
this.aliases.addAll(aliases);
return this;
}
public Builder withSettings(Map<String, Object> settings) {
Assert.notNull(settings, "settings must not be null");
this.settings = settings;
return this;
}
public Builder withMapping(@Nullable Document mapping) {
this.mapping = mapping;
return this;
}
public CreateIndexSettings build() {
return new CreateIndexSettings(this);
}
}
}
@@ -15,8 +15,6 @@
*/
package org.springframework.data.elasticsearch.core.mapping;
import java.util.Set;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Dynamic;
import org.springframework.data.elasticsearch.annotations.Field;
@@ -44,14 +42,6 @@ public interface ElasticsearchPersistentEntity<T> extends PersistentEntity<T, El
IndexCoordinates getIndexCoordinates();
/**
* Retrieves the aliases associated with the current entity.
*
* @return Returns a set of aliases of the {@link PersistentEntity}.
* @since 5.4
*/
Set<Alias> getAliases();
short getShards();
short getReplicas();
@@ -76,7 +66,7 @@ public interface ElasticsearchPersistentEntity<T> extends PersistentEntity<T, El
boolean isCreateIndexAndMapping();
/**
* returns the {@link ElasticsearchPersistentProperty} with the given fieldName (can be set by the {@link Field})
* returns the {@link ElasticsearchPersistentProperty} with the given fieldName (may be set by the {@link Field}
* annotation.
*
* @param fieldName to field name for the search, must not be {@literal null}
@@ -199,7 +189,7 @@ public interface ElasticsearchPersistentEntity<T> extends PersistentEntity<T, El
boolean storeVersionInSource();
/**
* @return if the mapping should be written to the index on repository bootstrap even if the index already exists.
* @return if the mapping should be written to the index on repositry bootstrap even if the index already exists.
* @since 5.2
*/
boolean isAlwaysWriteMapping();
@@ -34,12 +34,12 @@ public class IndexCoordinates {
private final String[] indexNames;
public static IndexCoordinates of(String... indexNames) {
Assert.notNull(indexNames, "indexNames must not be null");
return new IndexCoordinates(indexNames);
}
private IndexCoordinates(String... indexNames) {
Assert.notEmpty(indexNames, "indexNames may not be null or empty");
Assert.noNullElements(indexNames, "indexNames may not contain null elements");
this.indexNames = indexNames;
}
@@ -15,9 +15,7 @@
*/
package org.springframework.data.elasticsearch.core.mapping;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;
@@ -33,8 +31,6 @@ import org.springframework.data.elasticsearch.annotations.Routing;
import org.springframework.data.elasticsearch.annotations.Setting;
import org.springframework.data.elasticsearch.core.index.Settings;
import org.springframework.data.elasticsearch.core.join.JoinField;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.core.query.StringQuery;
import org.springframework.data.mapping.MappingException;
import org.springframework.data.mapping.PropertyHandler;
import org.springframework.data.mapping.model.BasicPersistentEntity;
@@ -77,14 +73,13 @@ public class SimpleElasticsearchPersistentEntity<T> extends BasicPersistentEntit
private @Nullable ElasticsearchPersistentProperty joinFieldProperty;
private @Nullable ElasticsearchPersistentProperty indexedIndexNameProperty;
private @Nullable Document.VersionType versionType;
private final boolean createIndexAndMapping;
private final boolean alwaysWriteMapping;
private boolean createIndexAndMapping;
private boolean alwaysWriteMapping;
private final Dynamic dynamic;
private final Map<String, ElasticsearchPersistentProperty> fieldNamePropertyCache = new ConcurrentHashMap<>();
private final ConcurrentHashMap<String, Expression> routingExpressions = new ConcurrentHashMap<>();
private @Nullable String routing;
private final ContextConfiguration contextConfiguration;
private final Set<Alias> aliases = new HashSet<>();
private final ConcurrentHashMap<String, Expression> indexNameExpressions = new ConcurrentHashMap<>();
private final Lazy<EvaluationContext> indexNameEvaluationContext = Lazy.of(this::getIndexNameEvaluationContext);
@@ -117,7 +112,6 @@ public class SimpleElasticsearchPersistentEntity<T> extends BasicPersistentEntit
this.dynamic = document.dynamic();
this.storeIdInSource = document.storeIdInSource();
this.storeVersionInSource = document.storeVersionInSource();
buildAliases();
} else {
this.dynamic = Dynamic.INHERIT;
this.storeIdInSource = true;
@@ -144,11 +138,6 @@ public class SimpleElasticsearchPersistentEntity<T> extends BasicPersistentEntit
return resolve(IndexCoordinates.of(getIndexName()));
}
@Override
public Set<Alias> getAliases() {
return aliases;
}
@Nullable
@Override
public String getIndexStoreType() {
@@ -258,12 +247,12 @@ public class SimpleElasticsearchPersistentEntity<T> extends BasicPersistentEntit
if (property.isIndexedIndexNameProperty()) {
if (!property.getActualType().isAssignableFrom(String.class)) {
throw new MappingException("@IndexedIndexName annotation must be put on String property");
throw new MappingException(String.format("@IndexedIndexName annotation must be put on String property"));
}
if (indexedIndexNameProperty != null) {
throw new MappingException(
"@IndexedIndexName annotation can only be put on one property in an entity");
String.format("@IndexedIndexName annotation can only be put on one property in an entity"));
}
this.indexedIndexNameProperty = property;
@@ -626,35 +615,4 @@ public class SimpleElasticsearchPersistentEntity<T> extends BasicPersistentEntit
public Dynamic dynamic() {
return dynamic;
}
/**
* Building once the aliases for the current document.
*/
private void buildAliases() {
// Clear the existing aliases.
aliases.clear();
if (document != null) {
for (org.springframework.data.elasticsearch.annotations.Alias alias : document.aliases()) {
if (alias.value().isEmpty()) {
continue;
}
Query query = null;
if (!alias.filter().value().isEmpty()) {
query = new StringQuery(alias.filter().value());
}
aliases.add(
Alias.builder(alias.value())
.withFilter(query)
.withIndexRouting(alias.indexRouting())
.withSearchRouting(alias.searchRouting())
.withRouting(alias.routing())
.withHidden(alias.isHidden())
.withWriteIndex(alias.isWriteIndex())
.build());
}
}
}
}
@@ -98,7 +98,6 @@ public class SimpleElasticsearchPersistentProperty extends
this.isSeqNoPrimaryTerm = SeqNoPrimaryTerm.class.isAssignableFrom(getRawType());
boolean isField = isAnnotationPresent(Field.class);
boolean isMultiField = isAnnotationPresent(MultiField.class);
if (isVersionProperty() && !getType().equals(Long.class)) {
throw new MappingException(String.format("Version property %s must be of type Long!", property.getName()));
@@ -110,10 +109,8 @@ public class SimpleElasticsearchPersistentProperty extends
initPropertyValueConverter();
storeNullValue = isField ? getRequiredAnnotation(Field.class).storeNullValue()
: isMultiField && getRequiredAnnotation(MultiField.class).mainField().storeNullValue();
storeEmptyValue = isField ? getRequiredAnnotation(Field.class).storeEmptyValue()
: !isMultiField || getRequiredAnnotation(MultiField.class).mainField().storeEmptyValue();
storeNullValue = isField && getRequiredAnnotation(Field.class).storeNullValue();
storeEmptyValue = isField ? getRequiredAnnotation(Field.class).storeEmptyValue() : true;
}
@Override
@@ -230,17 +230,11 @@ public class ByQueryResponse {
return aborted;
}
@Nullable
public ElasticsearchErrorCause getElasticsearchErrorCause() {
return elasticsearchErrorCause;
}
/**
* Create a new {@link FailureBuilder} to build {@link Failure}
*
* @return a new {@link FailureBuilder} to build {@link Failure}
*/
public static FailureBuilder builder() {
return new FailureBuilder();
}
@@ -316,9 +316,6 @@ public class Criteria {
Criteria orCriteria = new OrCriteria(this.criteriaChain, criteria.getField());
orCriteria.queryCriteriaEntries.addAll(criteria.queryCriteriaEntries);
orCriteria.filterCriteriaEntries.addAll(criteria.filterCriteriaEntries);
orCriteria.subCriteria.addAll(criteria.subCriteria);
orCriteria.boost = criteria.boost;
orCriteria.negating = criteria.isNegating();
return orCriteria;
}
@@ -673,8 +670,8 @@ public class Criteria {
*/
public Criteria boundedBy(String topLeftGeohash, String bottomRightGeohash) {
Assert.isTrue(StringUtils.hasLength(topLeftGeohash), "topLeftGeohash must not be empty");
Assert.isTrue(StringUtils.hasLength(bottomRightGeohash), "bottomRightGeohash must not be empty");
Assert.isTrue(!StringUtils.isEmpty(topLeftGeohash), "topLeftGeohash must not be empty");
Assert.isTrue(!StringUtils.isEmpty(bottomRightGeohash), "bottomRightGeohash must not be empty");
filterCriteriaEntries
.add(new CriteriaEntry(OperationKey.BBOX, new Object[] { topLeftGeohash, bottomRightGeohash }));
@@ -757,7 +754,7 @@ public class Criteria {
*/
public Criteria within(String geoLocation, String distance) {
Assert.isTrue(StringUtils.hasLength(geoLocation), "geoLocation value must not be null");
Assert.isTrue(!StringUtils.isEmpty(geoLocation), "geoLocation value must not be null");
filterCriteriaEntries.add(new CriteriaEntry(OperationKey.WITHIN, new Object[] { geoLocation, distance }));
return this;
@@ -816,32 +813,6 @@ public class Criteria {
filterCriteriaEntries.add(new CriteriaEntry(OperationKey.GEO_CONTAINS, geoShape));
return this;
}
/**
* Adds a new filter CriteriaEntry for HAS_CHILD.
*
* @param query the has_child query.
* @return the current Criteria.
*/
public Criteria hasChild(HasChildQuery query) {
Assert.notNull(query, "has_child query must not be null.");
queryCriteriaEntries.add(new CriteriaEntry(OperationKey.HAS_CHILD, query));
return this;
}
/**
* Adds a new filter CriteriaEntry for HAS_PARENT.
*
* @param query the has_parent query.
* @return the current Criteria.
*/
public Criteria hasParent(HasParentQuery query) {
Assert.notNull(query, "has_parent query must not be null.");
queryCriteriaEntries.add(new CriteriaEntry(OperationKey.HAS_PARENT, query));
return this;
}
// endregion
// region helper functions
@@ -1003,11 +974,7 @@ public class Criteria {
/**
* @since 5.1
*/
REGEXP,
/**
* @since 5.3
*/
HAS_CHILD, HAS_PARENT;
REGEXP;
/**
* @return true if this key does not have an associated value
@@ -1,676 +0,0 @@
/*
* Copyright 2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.core.query;
import java.time.Duration;
import java.util.EnumSet;
import java.util.List;
import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.core.query.Query.SearchType;
import org.springframework.data.elasticsearch.core.query.types.ConflictsType;
import org.springframework.data.elasticsearch.core.query.types.OperatorType;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Defines a delete request.
*
* @author Aouichaoui Youssef
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-delete-by-query.html">docs</a>
* @since 5.3
*/
public class DeleteQuery {
// For Lucene query
/**
* Query in the Lucene query string syntax.
*/
@Nullable private final String q;
/**
* If true, wildcard and prefix queries are analyzed. Defaults to false. This parameter can only be used when the
* lucene query {@code q} parameter is specified.
*/
@Nullable private final Boolean analyzeWildcard;
/**
* Analyzer to use for the query string. This parameter can only be used when the lucene query {@code q} parameter is
* specified.
*/
@Nullable private final String analyzer;
/**
* The default operator for a query string query: {@literal AND} or {@literal OR}. Defaults to {@literal OR}. This
* parameter can only be used when the lucene query {@code q} parameter is specified.
*/
@Nullable private final OperatorType defaultOperator;
/**
* Field to be used as the default when no field prefix is specified in the query string. This parameter can only be
* used when the lucene query {@code q} parameter is specified.
* <p>
* e.g: {@code {"query":{"prefix":{"user.name":{"value":"es"}}}} }
*/
@Nullable private final String df;
/**
* If a query contains errors related to the format of the data being entered, they will be disregarded unless
* specified otherwise. By default, this feature is turned off.
*/
@Nullable private final Boolean lenient;
// For ES query
/**
* An error will occur if the condition is {@code false} and any of the following are true: a wildcard expression, an
* index alias, or the {@literal _all value} only targets missing or closed indices. By default, this is set to
* {@code true}.
*/
@Nullable private final Boolean allowNoIndices;
/**
* Define the types of conflicts that occur when a query encounters version conflicts: abort or proceed. Defaults to
* abort.
*/
@Nullable private final ConflictsType conflicts;
/**
* Type of index that wildcard patterns can match. Defaults to {@literal open}.
*/
@Nullable private final EnumSet<IndicesOptions.WildcardStates> expandWildcards;
/**
* An error occurs if it is directed at an index that is missing or closed when it is {@code false}. By default, this
* is set to {@code false}.
*/
@Nullable private final Boolean ignoreUnavailable;
/**
* Maximum number of documents to process. Defaults to all documents.
*/
@Nullable private final Long maxDocs;
/**
* Specifies the node or shard the operation should be performed on.
*/
@Nullable private final String preference;
/**
* Use the request cache when it is {@code true}. By default, use the index-level setting.
*/
@Nullable private final Boolean requestCache;
/**
* Refreshes all shards involved in the deleting by query after the request completes when it is {@code true}. By
* default, this is set to {@code false}.
*/
@Nullable private final Boolean refresh;
/**
* Limited this request to a certain number of sub-requests per second. By default, this is set to {@code -1} (no
* throttle).
*/
@Nullable private final Float requestsPerSecond;
/**
* Custom value used to route operations to a specific shard.
*/
@Nullable private final String routing;
/**
* Period to retain the search context for scrolling.
*/
@Nullable private final Duration scroll;
/**
* Size of the scroll request that powers the operation. By default, this is set to {@code 1000}.
*/
@Nullable private final Long scrollSize;
/**
* The type of the search operation.
*/
@Nullable private final SearchType searchType;
/**
* Explicit timeout for each search request. By default, this is set to no timeout.
*/
@Nullable private final Duration searchTimeout;
/**
* The number of slices this task should be divided into. By default, this is set to {@code 1} meaning the task isnt
* sliced into subtasks.
*/
@Nullable private final Integer slices;
/**
* Sort search results in a specific order.
*/
@Nullable private final Sort sort;
/**
* Specific {@code tag} of the request for logging and statistical purposes.
*/
@Nullable private final List<String> stats;
/**
* The Maximum number of documents that can be collected for each shard. If a query exceeds this limit, Elasticsearch
* will stop the query.
*/
@Nullable private final Long terminateAfter;
/**
* Period each deletion request waits for active shards. By default, this is set to {@code 1m} (one minute).
*/
@Nullable private final Duration timeout;
/**
* Returns the document version as part of a hit.
*/
@Nullable private final Boolean version;
// Body
/**
* Query that specifies the documents to delete.
*/
private final Query query;
public static Builder builder(Query query) {
return new Builder(query);
}
private DeleteQuery(Builder builder) {
this.q = builder.luceneQuery;
this.analyzeWildcard = builder.analyzeWildcard;
this.analyzer = builder.analyzer;
this.defaultOperator = builder.defaultOperator;
this.df = builder.defaultField;
this.lenient = builder.lenient;
this.allowNoIndices = builder.allowNoIndices;
this.conflicts = builder.conflicts;
this.expandWildcards = builder.expandWildcards;
this.ignoreUnavailable = builder.ignoreUnavailable;
this.maxDocs = builder.maxDocs;
this.preference = builder.preference;
this.requestCache = builder.requestCache;
this.refresh = builder.refresh;
this.requestsPerSecond = builder.requestsPerSecond;
this.routing = builder.routing;
this.scroll = builder.scrollTime;
this.scrollSize = builder.scrollSize;
this.searchType = builder.searchType;
this.searchTimeout = builder.searchTimeout;
this.slices = builder.slices;
this.sort = builder.sort;
this.stats = builder.stats;
this.terminateAfter = builder.terminateAfter;
this.timeout = builder.timeout;
this.version = builder.version;
this.query = builder.query;
}
@Nullable
public String getQ() {
return q;
}
@Nullable
public Boolean getAnalyzeWildcard() {
return analyzeWildcard;
}
@Nullable
public String getAnalyzer() {
return analyzer;
}
@Nullable
public OperatorType getDefaultOperator() {
return defaultOperator;
}
@Nullable
public String getDf() {
return df;
}
@Nullable
public Boolean getLenient() {
return lenient;
}
@Nullable
public Boolean getAllowNoIndices() {
return allowNoIndices;
}
@Nullable
public ConflictsType getConflicts() {
return conflicts;
}
@Nullable
public EnumSet<IndicesOptions.WildcardStates> getExpandWildcards() {
return expandWildcards;
}
@Nullable
public Boolean getIgnoreUnavailable() {
return ignoreUnavailable;
}
@Nullable
public Long getMaxDocs() {
return maxDocs;
}
@Nullable
public String getPreference() {
return preference;
}
@Nullable
public Boolean getRequestCache() {
return requestCache;
}
@Nullable
public Boolean getRefresh() {
return refresh;
}
@Nullable
public Float getRequestsPerSecond() {
return requestsPerSecond;
}
@Nullable
public String getRouting() {
return routing;
}
@Nullable
public Duration getScroll() {
return scroll;
}
@Nullable
public Long getScrollSize() {
return scrollSize;
}
@Nullable
public SearchType getSearchType() {
return searchType;
}
@Nullable
public Duration getSearchTimeout() {
return searchTimeout;
}
@Nullable
public Integer getSlices() {
return slices;
}
@Nullable
public Sort getSort() {
return sort;
}
@Nullable
public List<String> getStats() {
return stats;
}
@Nullable
public Long getTerminateAfter() {
return terminateAfter;
}
@Nullable
public Duration getTimeout() {
return timeout;
}
@Nullable
public Boolean getVersion() {
return version;
}
@Nullable
public Query getQuery() {
return query;
}
public static final class Builder {
// For Lucene query
@Nullable private String luceneQuery;
@Nullable private Boolean analyzeWildcard;
@Nullable private String analyzer;
@Nullable private OperatorType defaultOperator;
@Nullable private String defaultField;
@Nullable private Boolean lenient;
// For ES query
@Nullable private Boolean allowNoIndices;
@Nullable private ConflictsType conflicts;
@Nullable private EnumSet<IndicesOptions.WildcardStates> expandWildcards;
@Nullable private Boolean ignoreUnavailable;
@Nullable private Long maxDocs;
@Nullable private String preference;
@Nullable private Boolean requestCache;
@Nullable private Boolean refresh;
@Nullable private Float requestsPerSecond;
@Nullable private String routing;
@Nullable private Duration scrollTime;
@Nullable private Long scrollSize;
@Nullable private SearchType searchType;
@Nullable private Duration searchTimeout;
@Nullable private Integer slices;
@Nullable private Sort sort;
@Nullable private List<String> stats;
@Nullable private Long terminateAfter;
@Nullable private Duration timeout;
@Nullable private Boolean version;
// Body
private final Query query;
private Builder(Query query) {
Assert.notNull(query, "query must not be null");
this.query = query;
}
/**
* Query in the Lucene query string syntax.
*/
public Builder withLuceneQuery(@Nullable String luceneQuery) {
this.luceneQuery = luceneQuery;
return this;
}
/**
* If true, wildcard and prefix queries are analyzed. Defaults to false. This parameter can only be used when the
* lucene query {@code q} parameter is specified.
*/
public Builder withAnalyzeWildcard(@Nullable Boolean analyzeWildcard) {
this.analyzeWildcard = analyzeWildcard;
return this;
}
/**
* Analyzer to use for the query string. This parameter can only be used when the lucene query {@code q} parameter
* is specified.
*/
public Builder withAnalyzer(@Nullable String analyzer) {
this.analyzer = analyzer;
return this;
}
/**
* The default operator for a query string query: {@literal AND} or {@literal OR}. Defaults to {@literal OR}. This
* parameter can only be used when the lucene query {@code q} parameter is specified.
*/
public Builder withDefaultOperator(@Nullable OperatorType defaultOperator) {
this.defaultOperator = defaultOperator;
return this;
}
/**
* Field to be used as the default when no field prefix is specified in the query string. This parameter can only be
* used when the lucene query {@code q} parameter is specified.
* <p>
* e.g: {@code {"query":{"prefix":{"user.name":{"value":"es"}}}} }
*/
public Builder withDefaultField(@Nullable String defaultField) {
this.defaultField = defaultField;
return this;
}
/**
* If a query contains errors related to the format of the data being entered, they will be disregarded unless
* specified otherwise. By default, this feature is turned off.
*/
public Builder withLenient(@Nullable Boolean lenient) {
this.lenient = lenient;
return this;
}
/**
* An error will occur if the condition is {@code false} and any of the following are true: a wildcard expression,
* an index alias, or the {@literal _all value} only targets missing or closed indices. By default, this is set to
* {@code true}.
*/
public Builder withAllowNoIndices(@Nullable Boolean allowNoIndices) {
this.allowNoIndices = allowNoIndices;
return this;
}
/**
* Define the types of conflicts that occur when a query encounters version conflicts: abort or proceed. Defaults to
* abort.
*/
public Builder withConflicts(@Nullable ConflictsType conflicts) {
this.conflicts = conflicts;
return this;
}
/**
* Type of index that wildcard patterns can match. Defaults to {@literal open}.
*/
public Builder setExpandWildcards(@Nullable EnumSet<IndicesOptions.WildcardStates> expandWildcards) {
this.expandWildcards = expandWildcards;
return this;
}
/**
* An error occurs if it is directed at an index that is missing or closed when it is {@code false}. By default,
* this is set to {@code false}.
*/
public Builder withIgnoreUnavailable(@Nullable Boolean ignoreUnavailable) {
this.ignoreUnavailable = ignoreUnavailable;
return this;
}
/**
* Maximum number of documents to process. Defaults to all documents.
*/
public Builder withMaxDocs(@Nullable Long maxDocs) {
this.maxDocs = maxDocs;
return this;
}
/**
* Specifies the node or shard the operation should be performed on.
*/
public Builder withPreference(@Nullable String preference) {
this.preference = preference;
return this;
}
/**
* Use the request cache when it is {@code true}. By default, use the index-level setting.
*/
public Builder withRequestCache(@Nullable Boolean requestCache) {
this.requestCache = requestCache;
return this;
}
/**
* Refreshes all shards involved in the deleting by query after the request completes when it is {@code true}. By
* default, this is set to {@code false}.
*/
public Builder withRefresh(@Nullable Boolean refresh) {
this.refresh = refresh;
return this;
}
/**
* Limited this request to a certain number of sub-requests per second. By default, this is set to {@code -1} (no
* throttle).
*/
public Builder withRequestsPerSecond(@Nullable Float requestsPerSecond) {
this.requestsPerSecond = requestsPerSecond;
return this;
}
/**
* Custom value used to route operations to a specific shard.
*/
public Builder withRouting(@Nullable String routing) {
this.routing = routing;
return this;
}
/**
* Period to retain the search context for scrolling.
*/
public Builder withScrollTime(@Nullable Duration scrollTime) {
this.scrollTime = scrollTime;
return this;
}
/**
* Size of the scroll request that powers the operation. By default, this is set to {@code 1000}.
*/
public Builder withScrollSize(@Nullable Long scrollSize) {
this.scrollSize = scrollSize;
return this;
}
/**
* The type of the search operation.
*/
public Builder withSearchType(@Nullable SearchType searchType) {
this.searchType = searchType;
return this;
}
/**
* Explicit timeout for each search request. By default, this is set to no timeout.
*/
public Builder withSearchTimeout(@Nullable Duration searchTimeout) {
this.searchTimeout = searchTimeout;
return this;
}
/**
* The number of slices this task should be divided into. By default, this is set to {@code 1} meaning the task
* isnt sliced into subtasks.
*/
public Builder withSlices(@Nullable Integer slices) {
this.slices = slices;
return this;
}
/**
* Sort search results in a specific order.
*/
public Builder withSort(@Nullable Sort sort) {
this.sort = sort;
return this;
}
/**
* Specific {@code tag} of the request for logging and statistical purposes.
*/
public Builder withStats(@Nullable List<String> stats) {
this.stats = stats;
return this;
}
/**
* The Maximum number of documents that can be collected for each shard. If a query exceeds this limit,
* Elasticsearch will stop the query.
*/
public Builder withTerminateAfter(@Nullable Long terminateAfter) {
this.terminateAfter = terminateAfter;
return this;
}
/**
* Period each deletion request waits for active shards. By default, this is set to {@code 1m} (one minute).
*/
public Builder withTimeout(@Nullable Duration timeout) {
this.timeout = timeout;
return this;
}
/**
* Returns the document version as part of a hit.
*/
public Builder withVersion(@Nullable Boolean version) {
this.version = version;
return this;
}
public DeleteQuery build() {
if (luceneQuery == null) {
if (defaultField != null) {
throw new IllegalArgumentException("When defining the df parameter, you must include the Lucene query.");
}
if (analyzer != null) {
throw new IllegalArgumentException(
"When defining the analyzer parameter, you must include the Lucene query.");
}
if (analyzeWildcard != null) {
throw new IllegalArgumentException(
"When defining the analyzeWildcard parameter, you must include the Lucene query.");
}
if (defaultOperator != null) {
throw new IllegalArgumentException(
"When defining the defaultOperator parameter, you must include the Lucene query.");
}
if (lenient != null) {
throw new IllegalArgumentException("When defining the lenient parameter, you must include the Lucene query.");
}
}
return new DeleteQuery(this);
}
}
}
@@ -1,204 +0,0 @@
/*
* Copyright 2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.core.query;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Defines a has_child request.
*
* @author Aouichaoui Youssef
* @see <a href=
* "https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-has-child-query.html">docs</a>
* @since 5.3
*/
public class HasChildQuery {
/**
* Name of the child relationship mapped for the join field.
*/
private final String type;
/**
* Query that specifies the documents to run on child documents of the {@link #type} field.
*/
private final Query query;
/**
* Indicates whether to ignore an unmapped {@link #type} and not return any documents instead of an error. Default,
* this is set to {@code false}.
*/
@Nullable private final Boolean ignoreUnmapped;
/**
* The Maximum number of child documents that match the {@link #query} allowed for a returned parent document. If the
* parent document exceeds this limit, it is excluded from the search results.
*/
@Nullable private final Integer maxChildren;
/**
* Minimum number of child documents that match the query required to match the {@link #query} for a returned parent
* document. If the parent document does not meet this limit, it is excluded from the search results.
*/
@Nullable private final Integer minChildren;
/**
* Indicates how scores for matching child documents affect the root parent documents relevance score.
*/
@Nullable private final ScoreMode scoreMode;
/**
* Obtaining nested objects and documents that have a parent-child relationship.
*/
@Nullable private final InnerHitsQuery innerHitsQuery;
public static Builder builder(String type) {
return new Builder(type);
}
private HasChildQuery(Builder builder) {
this.type = builder.type;
this.query = builder.query;
this.innerHitsQuery = builder.innerHitsQuery;
this.ignoreUnmapped = builder.ignoreUnmapped;
this.maxChildren = builder.maxChildren;
this.minChildren = builder.minChildren;
this.scoreMode = builder.scoreMode;
}
public String getType() {
return type;
}
public Query getQuery() {
return query;
}
@Nullable
public Boolean getIgnoreUnmapped() {
return ignoreUnmapped;
}
@Nullable
public Integer getMaxChildren() {
return maxChildren;
}
@Nullable
public Integer getMinChildren() {
return minChildren;
}
@Nullable
public ScoreMode getScoreMode() {
return scoreMode;
}
@Nullable
public InnerHitsQuery getInnerHitsQuery() {
return innerHitsQuery;
}
public enum ScoreMode {
Default, Avg, Max, Min, Sum
}
public static final class Builder {
private final String type;
private Query query;
@Nullable private Boolean ignoreUnmapped;
@Nullable private Integer maxChildren;
@Nullable private Integer minChildren;
@Nullable private ScoreMode scoreMode;
@Nullable private InnerHitsQuery innerHitsQuery;
private Builder(String type) {
Assert.notNull(type, "type must not be null");
this.type = type;
}
/**
* Query that specifies the documents to run on child documents of the {@link #type} field.
*/
public Builder withQuery(Query query) {
this.query = query;
return this;
}
/**
* Indicates whether to ignore an unmapped {@link #type} and not return any documents instead of an error. Default,
* this is set to {@code false}.
*/
public Builder withIgnoreUnmapped(@Nullable Boolean ignoreUnmapped) {
this.ignoreUnmapped = ignoreUnmapped;
return this;
}
/**
* The Maximum number of child documents that match the {@link #query} allowed for a returned parent document. If
* the parent document exceeds this limit, it is excluded from the search results.
*/
public Builder withMaxChildren(@Nullable Integer maxChildren) {
this.maxChildren = maxChildren;
return this;
}
/**
* Minimum number of child documents that match the query required to match the {@link #query} for a returned parent
* document. If the parent document does not meet this limit, it is excluded from the search results.
*/
public Builder withMinChildren(@Nullable Integer minChildren) {
this.minChildren = minChildren;
return this;
}
/**
* Indicates how scores for matching child documents affect the root parent documents relevance score.
*/
public Builder withScoreMode(@Nullable ScoreMode scoreMode) {
this.scoreMode = scoreMode;
return this;
}
/**
* Obtaining nested objects and documents that have a parent-child relationship.
*/
public Builder withInnerHitsQuery(@Nullable InnerHitsQuery innerHitsQuery) {
this.innerHitsQuery = innerHitsQuery;
return this;
}
public HasChildQuery build() {
Assert.notNull(query, "query must not be null.");
return new HasChildQuery(this);
}
}
}
@@ -1,141 +0,0 @@
/*
* Copyright 2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.core.query;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Defines a has_parent request.
*
* @author Aouichaoui Youssef
* @see <a href=
* "https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-has-parent-query.html">docs</a>
* @since 5.3
*/
public class HasParentQuery {
/**
* Name of the parent relationship mapped for the join field.
*/
private final String parentType;
/**
* Query that specifies the documents to run on parent documents of the {@link #parentType} field.
*/
private final Query query;
/**
* Indicates whether the relevance score of a matching parent document is aggregated into its child documents.
* Default, this is set to {@code false}.
*/
@Nullable private final Boolean score;
/**
* Indicates whether to ignore an unmapped {@link #parentType} and not return any documents instead of an error.
* Default, this is set to {@code false}.
*/
@Nullable private final Boolean ignoreUnmapped;
/**
* Obtaining nested objects and documents that have a parent-child relationship.
*/
@Nullable private final InnerHitsQuery innerHitsQuery;
public static Builder builder(String parentType) {
return new Builder(parentType);
}
private HasParentQuery(Builder builder) {
this.parentType = builder.parentType;
this.query = builder.query;
this.innerHitsQuery = builder.innerHitsQuery;
this.score = builder.score;
this.ignoreUnmapped = builder.ignoreUnmapped;
}
public String getParentType() {
return parentType;
}
public Query getQuery() {
return query;
}
@Nullable
public Boolean getScore() {
return score;
}
@Nullable
public Boolean getIgnoreUnmapped() {
return ignoreUnmapped;
}
@Nullable
public InnerHitsQuery getInnerHitsQuery() {
return innerHitsQuery;
}
public static class Builder {
private final String parentType;
private Query query;
@Nullable private Boolean score;
@Nullable private Boolean ignoreUnmapped;
@Nullable private InnerHitsQuery innerHitsQuery;
private Builder(String parentType) {
Assert.notNull(parentType, "parent_type must not be null.");
this.parentType = parentType;
}
public Builder withQuery(Query query) {
this.query = query;
return this;
}
public Builder withScore(@Nullable Boolean score) {
this.score = score;
return this;
}
public Builder withIgnoreUnmapped(@Nullable Boolean ignoreUnmapped) {
this.ignoreUnmapped = ignoreUnmapped;
return this;
}
/**
* Obtaining nested objects and documents that have a parent-child relationship.
*/
public Builder withInnerHitsQuery(@Nullable InnerHitsQuery innerHitsQuery) {
this.innerHitsQuery = innerHitsQuery;
return this;
}
public HasParentQuery build() {
Assert.notNull(query, "query must not be null.");
return new HasParentQuery(this);
}
}
}
@@ -23,8 +23,8 @@ package org.springframework.data.elasticsearch.core.query;
*/
public class IndexBoost {
private final String indexName;
private final float boost;
private String indexName;
private float boost;
public IndexBoost(String indexName, float boost) {
this.indexName = indexName;
@@ -25,8 +25,8 @@ import java.util.EnumSet;
*/
public class IndicesOptions {
private final EnumSet<Option> options;
private final EnumSet<WildcardStates> expandWildcards;
private EnumSet<Option> options;
private EnumSet<WildcardStates> expandWildcards;
public static final IndicesOptions STRICT_EXPAND_OPEN = new IndicesOptions(
EnumSet.of(IndicesOptions.Option.ALLOW_NO_INDICES), EnumSet.of(IndicesOptions.WildcardStates.OPEN));
@@ -101,10 +101,10 @@ public class IndicesOptions {
}
public enum WildcardStates {
OPEN, CLOSED, HIDDEN, ALL, NONE
OPEN, CLOSED, HIDDEN, ALL, NONE;
}
public enum Option {
IGNORE_UNAVAILABLE, IGNORE_ALIASES, ALLOW_NO_INDICES, FORBID_ALIASES_TO_MULTIPLE_INDICES, FORBID_CLOSED_INDICES, IGNORE_THROTTLED
IGNORE_UNAVAILABLE, IGNORE_ALIASES, ALLOW_NO_INDICES, FORBID_ALIASES_TO_MULTIPLE_INDICES, FORBID_CLOSED_INDICES, IGNORE_THROTTLED;
}
}
@@ -1,107 +0,0 @@
/*
* Copyright 2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.core.query;
import org.springframework.lang.Nullable;
/**
* Defines an inner_hits request.
*
* @author Aouichaoui Youssef
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/inner-hits.html">docs</a>
* @since 5.3
*/
public class InnerHitsQuery {
/**
* The name to be used for the particular inner hit definition in the response.
*/
@Nullable private final String name;
/**
* The maximum number of hits to return.
*/
@Nullable private final Integer size;
/**
* The offset from where the first hit to fetch.
*/
@Nullable private final Integer from;
public static Builder builder() {
return new Builder();
}
private InnerHitsQuery(Builder builder) {
this.name = builder.name;
this.from = builder.from;
this.size = builder.size;
}
@Nullable
public String getName() {
return name;
}
@Nullable
public Integer getSize() {
return size;
}
@Nullable
public Integer getFrom() {
return from;
}
public static final class Builder {
@Nullable private String name;
@Nullable private Integer size;
@Nullable private Integer from;
private Builder() {}
/**
* The name to be used for the particular inner hit definition in the response.
*/
public Builder withName(@Nullable String name) {
this.name = name;
return this;
}
/**
* The maximum number of hits to return.
*/
public Builder withSize(@Nullable Integer size) {
this.size = size;
return this;
}
/**
* The offset from where the first hit to fetch.
*/
public Builder withFrom(@Nullable Integer from) {
this.from = from;
return this;
}
public InnerHitsQuery build() {
return new InnerHitsQuery(this);
}
}
}
@@ -15,12 +15,13 @@
*/
package org.springframework.data.elasticsearch.core.query;
import java.util.function.Function;
import org.springframework.data.domain.Sort;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import java.util.function.BiFunction;
import java.util.function.Function;
/**
* Extends the {@link Sort.Order} with properties that can be set on Elasticsearch order options.
*
@@ -142,19 +143,18 @@ public class Order extends Sort.Order {
}
public static class Nested {
private final String path;
@Nullable private final Query filter;
private String path;
@Nullable private Query filter;
@Nullable private Integer maxChildren = null;
@Nullable private final Nested nested;
@Nullable private Nested nested;
public static Nested of(String path, Function<Nested.Builder, Nested.Builder> builderFunction) {
Assert.notNull(path, "path must not be null");
Assert.notNull(builderFunction, "builderFunction must not be null");
Assert.notNull(path, "path must not be null");
Assert.notNull(builderFunction, "builderFunction must not be null");
return builderFunction.apply(builder(path)).build();
return builderFunction.apply(builder(path)).build();
}
public Nested(String path, @Nullable Query filter, @Nullable Integer maxChildren, @Nullable Nested nested) {
Assert.notNull(path, "path must not be null");
@@ -189,7 +189,7 @@ public class Order extends Sort.Order {
}
public static class Builder {
private final String path;
private String path;
@Nullable private Query filter = null;
@Nullable private Integer maxChildren = null;
@Nullable private Nested nested = null;
@@ -203,9 +203,8 @@ public class Order extends Sort.Order {
/**
* Sets the filter query for a nested sort.<br/>
* Note: This cannot be a {@link CriteriaQuery}, as that would be sent as a nested query within the filter, use a
* {@link org.springframework.data.elasticsearch.client.elc.NativeQuery} or {@link StringQuery} instead.
*
* Note: This cannot be a {@link CriteriaQuery}, as that would be sent as a nested query within the filter,
* use a {@link org.springframework.data.elasticsearch.client.elc.NativeQuery} or {@link StringQuery} instead.
* @param filter the filter to set
* @return this builder
* @throws IllegalArgumentException when a {@link CriteriaQuery} is passed.

Some files were not shown because too many files have changed in this diff Show More