Compare commits
97 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 44b1c9e848 | |||
| 1770f98a74 | |||
| bad0a80313 | |||
| 92dd6e8599 | |||
| c793be8ab4 | |||
| 07ae79f9ce | |||
| 47c84b84af | |||
| 5ba1e5dc77 | |||
| 5ddcd55942 | |||
| be4a77ad21 | |||
| 7fa3cb74a1 | |||
| ba9edf8ec8 | |||
| e4a39ae285 | |||
| 7392222793 | |||
| 2b76762007 | |||
| baec1f1419 | |||
| d693c4f81b | |||
| 94a40a7a75 | |||
| dc5bf5a606 | |||
| 1d89054d12 | |||
| 106b513d11 | |||
| a16782ec73 | |||
| 2d5f8e8219 | |||
| ad66510e9e | |||
| e1537087bf | |||
| 8eecbe6a32 | |||
| c79fe303db | |||
| b507abe327 | |||
| bec3beb1eb | |||
| 1d709f6c55 | |||
| 0beca99912 | |||
| 6d51e67948 | |||
| 0a51dbab01 | |||
| c96423d5ba | |||
| 496b8d62a4 | |||
| d2b3ba94f6 | |||
| 33973ec839 | |||
| 7f178238db | |||
| aa27bbec27 | |||
| bd6b6e92f4 | |||
| 87eb36a995 | |||
| 41cab97f78 | |||
| f4d2ff7a99 | |||
| 9472161808 | |||
| debf04b499 | |||
| 205d74b6db | |||
| 7a8a9a15f1 | |||
| c965862e82 | |||
| 6af099ea34 | |||
| 96185f94ef | |||
| ca85729ea4 | |||
| f9d01df6f7 | |||
| d16951eace | |||
| e1730ea7cc | |||
| 0f5497338a | |||
| e9ecebd9ef | |||
| 9a3f5dc4f5 | |||
| 6390aaa739 | |||
| b391a4e844 | |||
| 0a1e20579e | |||
| 1f75016977 | |||
| 3878540394 | |||
| 957fe0531f | |||
| 460b4ac0f5 | |||
| e1a2412651 | |||
| c6041fb659 | |||
| 8f745b19d1 | |||
| c16024d779 | |||
| af1d2dd641 | |||
| 06ede8d7ae | |||
| 1554c3c94f | |||
| 260dadd4d6 | |||
| b78588eec5 | |||
| b0c97ccf27 | |||
| 433d52981e | |||
| 6350514e7e | |||
| 02bd3e60f8 | |||
| 21a1fbca0f | |||
| 434de11f3d | |||
| 96b38652ab | |||
| d0ed80dfde | |||
| 362126e72d | |||
| 0e419133a2 | |||
| 8a3df63493 | |||
| fb9ccf7b44 | |||
| 1d6a1b0f2f | |||
| 72e8f41de5 | |||
| 4edf9bee41 | |||
| 8613eb26e0 | |||
| 415d5e0385 | |||
| 3833975a1a | |||
| 05ca90ecc1 | |||
| 7af76338fc | |||
| 1f4479092a | |||
| ddd795a3d3 | |||
| 612cc50b88 | |||
| d05b9f878a |
+1
-1
@@ -1,3 +1,3 @@
|
||||
#Thu Dec 14 08:34:15 CET 2023
|
||||
#Thu Dec 14 08:40:44 CET 2023
|
||||
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.6/apache-maven-3.9.6-bin.zip
|
||||
|
||||
Vendored
+26
-20
@@ -9,7 +9,7 @@ pipeline {
|
||||
|
||||
triggers {
|
||||
pollSCM 'H/10 * * * *'
|
||||
upstream(upstreamProjects: "spring-data-commons/3.2.x", threshold: hudson.model.Result.SUCCESS)
|
||||
upstream(upstreamProjects: "spring-data-commons/3.3.x", threshold: hudson.model.Result.SUCCESS)
|
||||
}
|
||||
|
||||
options {
|
||||
@@ -39,9 +39,11 @@ pipeline {
|
||||
|
||||
steps {
|
||||
script {
|
||||
docker.image(p['docker.java.main.image']).inside(p['docker.java.inside.docker']) {
|
||||
sh "PROFILE=none JENKINS_USER_NAME=${p['jenkins.user.name']} ci/verify.sh"
|
||||
sh "JENKINS_USER_NAME=${p['jenkins.user.name']} ci/clean.sh"
|
||||
docker.withRegistry(p['docker.proxy.registry'], p['docker.proxy.credentials']) {
|
||||
docker.image(p['docker.java.main.image']).inside(p['docker.java.inside.docker']) {
|
||||
sh "PROFILE=none JENKINS_USER_NAME=${p['jenkins.user.name']} ci/verify.sh"
|
||||
sh "JENKINS_USER_NAME=${p['jenkins.user.name']} ci/clean.sh"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -68,9 +70,11 @@ pipeline {
|
||||
}
|
||||
steps {
|
||||
script {
|
||||
docker.image(p['docker.java.next.image']).inside(p['docker.java.inside.docker']) {
|
||||
sh "PROFILE=none JENKINS_USER_NAME=${p['jenkins.user.name']} ci/verify.sh"
|
||||
sh "JENKINS_USER_NAME=${p['jenkins.user.name']} ci/clean.sh"
|
||||
docker.withRegistry(p['docker.proxy.registry'], p['docker.proxy.credentials']) {
|
||||
docker.image(p['docker.java.next.image']).inside(p['docker.java.inside.docker']) {
|
||||
sh "PROFILE=none JENKINS_USER_NAME=${p['jenkins.user.name']} ci/verify.sh"
|
||||
sh "JENKINS_USER_NAME=${p['jenkins.user.name']} ci/clean.sh"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -97,19 +101,21 @@ pipeline {
|
||||
}
|
||||
steps {
|
||||
script {
|
||||
docker.image(p['docker.java.main.image']).inside(p['docker.java.inside.basic']) {
|
||||
sh 'MAVEN_OPTS="-Duser.name=' + "${p['jenkins.user.name']}" + ' -Duser.home=/tmp/jenkins-home" ' +
|
||||
"DEVELOCITY_CACHE_USERNAME=${DEVELOCITY_CACHE_USR} " +
|
||||
"DEVELOCITY_CACHE_PASSWORD=${DEVELOCITY_CACHE_PSW} " +
|
||||
"GRADLE_ENTERPRISE_ACCESS_KEY=${DEVELOCITY_ACCESS_KEY} " +
|
||||
"./mvnw -s settings.xml -Pci,artifactory -Dmaven.repo.local=/tmp/jenkins-home/.m2/spring-data-elasticsearch-non-root " +
|
||||
"-Dartifactory.server=${p['artifactory.url']} " +
|
||||
"-Dartifactory.username=${ARTIFACTORY_USR} " +
|
||||
"-Dartifactory.password=${ARTIFACTORY_PSW} " +
|
||||
"-Dartifactory.staging-repository=${p['artifactory.repository.snapshot']} " +
|
||||
"-Dartifactory.build-name=spring-data-elasticsearch " +
|
||||
"-Dartifactory.build-number=${BUILD_NUMBER} " +
|
||||
"-Dmaven.test.skip=true clean deploy -U -B"
|
||||
docker.withRegistry(p['docker.proxy.registry'], p['docker.proxy.credentials']) {
|
||||
docker.image(p['docker.java.main.image']).inside(p['docker.java.inside.basic']) {
|
||||
sh 'MAVEN_OPTS="-Duser.name=' + "${p['jenkins.user.name']}" + ' -Duser.home=/tmp/jenkins-home" ' +
|
||||
"DEVELOCITY_CACHE_USERNAME=${DEVELOCITY_CACHE_USR} " +
|
||||
"DEVELOCITY_CACHE_PASSWORD=${DEVELOCITY_CACHE_PSW} " +
|
||||
"GRADLE_ENTERPRISE_ACCESS_KEY=${DEVELOCITY_ACCESS_KEY} " +
|
||||
"./mvnw -s settings.xml -Pci,artifactory -Dmaven.repo.local=/tmp/jenkins-home/.m2/spring-data-elasticsearch-non-root " +
|
||||
"-Dartifactory.server=${p['artifactory.url']} " +
|
||||
"-Dartifactory.username=${ARTIFACTORY_USR} " +
|
||||
"-Dartifactory.password=${ARTIFACTORY_PSW} " +
|
||||
"-Dartifactory.staging-repository=${p['artifactory.repository.snapshot']} " +
|
||||
"-Dartifactory.build-name=spring-data-elasticsearch " +
|
||||
"-Dartifactory.build-number=spring-data-elasticsearch-${BRANCH_NAME}-build-${BUILD_NUMBER} " +
|
||||
"-Dmaven.test.skip=true clean deploy -U -B"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-5
@@ -1,6 +1,4 @@
|
||||
image:https://spring.io/badges/spring-data-elasticsearch/ga.svg[Spring Data Elasticsearch,link=https://projects.spring.io/spring-data-elasticsearch#quick-start] image:https://spring.io/badges/spring-data-elasticsearch/snapshot.svg[Spring Data Elasticsearch,link=https://projects.spring.io/spring-data-elasticsearch#quick-start]
|
||||
|
||||
= Spring Data for Elasticsearch image:https://jenkins.spring.io/buildStatus/icon?job=spring-data-elasticsearch%2Fmain&subject=Build[link=https://jenkins.spring.io/view/SpringData/job/spring-data-elasticsearch/] https://gitter.im/spring-projects/spring-data[image:https://badges.gitter.im/spring-projects/spring-data.svg[Gitter]] 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"]
|
||||
= 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"]
|
||||
|
||||
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.
|
||||
|
||||
@@ -126,8 +124,7 @@ We’d 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 - 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].
|
||||
* Ask a question or chat with the community on https://app.gitter.im/#/room/#spring-projects_spring-data:gitter.im[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
|
||||
|
||||
@@ -3,8 +3,8 @@ java.main.tag=17.0.9_9-jdk-focal
|
||||
java.next.tag=21.0.1_12-jdk-jammy
|
||||
|
||||
# Docker container images - standard
|
||||
docker.java.main.image=harbor-repo.vmware.com/dockerhub-proxy-cache/library/eclipse-temurin:${java.main.tag}
|
||||
docker.java.next.image=harbor-repo.vmware.com/dockerhub-proxy-cache/library/eclipse-temurin:${java.next.tag}
|
||||
docker.java.main.image=library/eclipse-temurin:${java.main.tag}
|
||||
docker.java.next.image=library/eclipse-temurin:${java.next.tag}
|
||||
|
||||
# Supported versions of MongoDB
|
||||
docker.mongodb.4.4.version=4.4.25
|
||||
@@ -14,6 +14,7 @@ docker.mongodb.7.0.version=7.0.2
|
||||
|
||||
# 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
|
||||
@@ -25,6 +26,8 @@ docker.java.inside.docker=-u root -v /var/run/docker.sock:/var/run/docker.sock -
|
||||
# Credentials
|
||||
docker.registry=
|
||||
docker.credentials=hub.docker.com-springbuildmaster
|
||||
docker.proxy.registry=https://docker-hub.usw1.packages.broadcom.com
|
||||
docker.proxy.credentials=usw1_packages_broadcom_com-jenkins-token
|
||||
artifactory.credentials=02bd1690-b54f-4c9f-819d-a77cb7a9822c
|
||||
artifactory.url=https://repo.spring.io
|
||||
artifactory.repository.snapshot=libs-snapshot-local
|
||||
|
||||
@@ -5,12 +5,12 @@
|
||||
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-elasticsearch</artifactId>
|
||||
<version>5.2.2</version>
|
||||
<version>5.3.2</version>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.data.build</groupId>
|
||||
<artifactId>spring-data-parent</artifactId>
|
||||
<version>3.2.2</version>
|
||||
<version>3.3.2</version>
|
||||
</parent>
|
||||
|
||||
<name>Spring Data Elasticsearch</name>
|
||||
@@ -18,10 +18,10 @@
|
||||
<url>https://github.com/spring-projects/spring-data-elasticsearch</url>
|
||||
|
||||
<properties>
|
||||
<springdata.commons>3.2.2</springdata.commons>
|
||||
<springdata.commons>3.3.2</springdata.commons>
|
||||
|
||||
<!-- version of the ElasticsearchClient -->
|
||||
<elasticsearch-java>8.11.3</elasticsearch-java>
|
||||
<elasticsearch-java>8.13.4</elasticsearch-java>
|
||||
|
||||
<blockhound-junit>1.0.8.RELEASE</blockhound-junit>
|
||||
<hoverfly>0.14.4</hoverfly>
|
||||
@@ -324,6 +324,13 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.tngtech.archunit</groupId>
|
||||
<artifactId>archunit-junit5</artifactId>
|
||||
<version>${archunit}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
*** 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:elasticsearch.adoc[]
|
||||
** xref:elasticsearch/clients.adoc[]
|
||||
|
||||
@@ -150,7 +150,7 @@ ClientConfiguration clientConfiguration = ClientConfiguration.builder()
|
||||
return headers;
|
||||
})
|
||||
.withClientConfigurer( <.>
|
||||
ElasticsearchClientConfigurationCallback.from(clientBuilder -> {
|
||||
ElasticsearchHttpClientConfigurationCallback.from(clientBuilder -> {
|
||||
// ...
|
||||
return clientBuilder;
|
||||
}))
|
||||
@@ -192,6 +192,7 @@ 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;
|
||||
@@ -210,6 +211,7 @@ used by the `RestClient`.
|
||||
[source,java]
|
||||
----
|
||||
ClientConfiguration.builder()
|
||||
.connectedTo("localhost:9200", "localhost:9291")
|
||||
.withClientConfigurer(ElasticsearchClients.ElasticsearchHttpClientConfigurationCallback.from(httpAsyncClientBuilder -> {
|
||||
// configure the HttpAsyncClient
|
||||
return httpAsyncClientBuilder;
|
||||
|
||||
@@ -1,9 +1,21 @@
|
||||
[[new-features]]
|
||||
= What's new
|
||||
|
||||
[[new-features.5-2-2]]
|
||||
== New in Spring Data Elasticsearch 5.2.2
|
||||
* Upgrade to Elasticsearch 8.11.3
|
||||
[[new-features.5-3-1]]
|
||||
== New in Spring Data Elasticsearch 5.3.1
|
||||
|
||||
* Upgrade to Elasticsearch 8.13.4.
|
||||
|
||||
[[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-0]]
|
||||
== New in Spring Data Elasticsearch 5.2
|
||||
|
||||
@@ -52,7 +52,7 @@ public class Statement {
|
||||
return routing;
|
||||
}
|
||||
|
||||
public void setRouting(Routing routing) {
|
||||
public void setRouting(String 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,6 +226,7 @@ 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,8 +192,7 @@ 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!
|
||||
@@ -423,7 +422,6 @@ 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() //
|
||||
|
||||
+2
-2
@@ -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)
|
||||
|
||||
+200
-1
@@ -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,3 +361,202 @@ 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['birth-date'].value.toInstant();
|
||||
Instant startDate = doc['birthDate'].value.toInstant();
|
||||
emit (ChronoUnit.DAYS.between(startDate, currentDate) / 365);
|
||||
""");
|
||||
|
||||
|
||||
@@ -6,7 +6,8 @@ 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
|
||||
| 2023.1 (Vaughan) | 5.2.x | 8.11.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.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 comign from the new Elasticsearch client libraries.
|
||||
`Query` instance coming 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]]
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
[[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.
|
||||
@@ -39,41 +39,173 @@ 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"), //
|
||||
year_month_day("uuuu-MM-dd"); //
|
||||
/**
|
||||
* @since 5.3
|
||||
*/
|
||||
strict_year_month("uuuu-MM"), //
|
||||
year_month_day("uuuu-MM-dd"), //
|
||||
/**
|
||||
* @since 5.3
|
||||
*/
|
||||
strict_year_month_day("uuuu-MM-dd"); //
|
||||
|
||||
private final String pattern;
|
||||
|
||||
|
||||
+3
@@ -21,6 +21,7 @@ import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Haibo Liu
|
||||
* @since 4.0
|
||||
*/
|
||||
@Documented
|
||||
@@ -59,6 +60,8 @@ public @interface HighlightParameters {
|
||||
|
||||
int numberOfFragments() default -1;
|
||||
|
||||
Query highlightQuery() default @Query;
|
||||
|
||||
String order() default "";
|
||||
|
||||
int phraseLimit() default -1;
|
||||
|
||||
+4
-7
@@ -15,9 +15,6 @@
|
||||
*/
|
||||
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;
|
||||
@@ -25,10 +22,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.
|
||||
*
|
||||
* 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>
|
||||
* This can not be used to specify the index where an entity should be written to.
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
|
||||
@@ -74,7 +74,14 @@ 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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
+2
-2
@@ -25,11 +25,11 @@ import org.springframework.data.util.ReactiveWrappers;
|
||||
*/
|
||||
public class ElasticsearchAotPredicates {
|
||||
|
||||
public static final Predicate<ReactiveWrappers.ReactiveLibrary> IS_REACTIVE_LIBARARY_AVAILABLE = (
|
||||
public static final Predicate<ReactiveWrappers.ReactiveLibrary> IS_REACTIVE_LIBRARY_AVAILABLE = (
|
||||
lib) -> ReactiveWrappers.isAvailable(lib);
|
||||
|
||||
public static boolean isReactorPresent() {
|
||||
return IS_REACTIVE_LIBARARY_AVAILABLE.test(ReactiveWrappers.ReactiveLibrary.PROJECT_REACTOR);
|
||||
return IS_REACTIVE_LIBRARY_AVAILABLE.test(ReactiveWrappers.ReactiveLibrary.PROJECT_REACTOR);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -233,6 +233,15 @@ 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
|
||||
|
||||
+7
-1
@@ -25,7 +25,6 @@ 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;
|
||||
@@ -106,6 +105,13 @@ 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)
|
||||
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
-6
@@ -16,7 +16,6 @@
|
||||
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;
|
||||
@@ -40,9 +39,4 @@ public class AutoCloseableElasticsearchClient extends ElasticsearchClient implem
|
||||
public void close() throws Exception {
|
||||
transport.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ElasticsearchClusterClient cluster() {
|
||||
return super.cluster();
|
||||
}
|
||||
}
|
||||
|
||||
+26
-12
@@ -39,6 +39,7 @@ 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;
|
||||
@@ -50,6 +51,7 @@ import org.springframework.util.Assert;
|
||||
* filter.
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Junghoon Ban
|
||||
* @since 4.4
|
||||
*/
|
||||
class CriteriaFilterProcessor {
|
||||
@@ -68,10 +70,17 @@ class CriteriaFilterProcessor {
|
||||
for (Criteria chainedCriteria : criteria.getCriteriaChain()) {
|
||||
|
||||
if (chainedCriteria.isOr()) {
|
||||
BoolQuery.Builder boolQueryBuilder = QueryBuilders.bool();
|
||||
queriesForEntries(chainedCriteria).forEach(boolQueryBuilder::should);
|
||||
filterQueries.add(new Query(boolQueryBuilder.build()));
|
||||
Collection<? extends Query> queriesForEntries = queriesForEntries(chainedCriteria);
|
||||
|
||||
if (!queriesForEntries.isEmpty()) {
|
||||
BoolQuery.Builder boolQueryBuilder = QueryBuilders.bool();
|
||||
queriesForEntries.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);
|
||||
@@ -115,6 +124,7 @@ 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");
|
||||
|
||||
@@ -169,17 +179,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) ? extractDistanceString((Distance) values[1]) : (String) values[1];
|
||||
String dist = (values[1] instanceof Distance distance) ? extractDistanceString(distance) : (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) {
|
||||
GeoPoint loc = GeoPoint.fromPoint((Point) values[0]);
|
||||
} else if (values[0] instanceof Point point) {
|
||||
GeoPoint loc = GeoPoint.fromPoint(point);
|
||||
location.latlon(latlon -> latlon.lat(loc.getLat()).lon(loc.getLon()));
|
||||
} else {
|
||||
String loc = (String) values[0];
|
||||
@@ -220,8 +230,8 @@ class CriteriaFilterProcessor {
|
||||
"single-element of boundedBy filter must be type of GeoBox or Box");
|
||||
|
||||
GeoBox geoBBox;
|
||||
if (value instanceof Box) {
|
||||
geoBBox = GeoBox.fromBox((Box) value);
|
||||
if (value instanceof Box box) {
|
||||
geoBBox = GeoBox.fromBox(box);
|
||||
} else {
|
||||
geoBBox = (GeoBox) value;
|
||||
}
|
||||
@@ -244,7 +254,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 //
|
||||
@@ -266,7 +276,10 @@ class CriteriaFilterProcessor {
|
||||
.tlbr(tlbr -> tlbr //
|
||||
.topLeft(glb -> {
|
||||
if (isGeoHash) {
|
||||
glb.geohash(gh -> gh.geohash(topLeft));
|
||||
// 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));
|
||||
} else {
|
||||
glb.text(topLeft);
|
||||
}
|
||||
@@ -274,7 +287,8 @@ class CriteriaFilterProcessor {
|
||||
}) //
|
||||
.bottomRight(glb -> {
|
||||
if (isGeoHash) {
|
||||
glb.geohash(gh -> gh.geohash(bottomRight));
|
||||
glb.text(Geohash.toLatLon(bottomRight));
|
||||
// glb.geohash(gh -> gh.geohash(bottomRight));
|
||||
} else {
|
||||
glb.text(bottomRight);
|
||||
}
|
||||
|
||||
+71
-10
@@ -16,21 +16,27 @@
|
||||
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;
|
||||
|
||||
@@ -42,7 +48,7 @@ import org.springframework.util.Assert;
|
||||
* @author Ezequiel Antúnez Camacho
|
||||
* @since 4.4
|
||||
*/
|
||||
class CriteriaQueryProcessor {
|
||||
class CriteriaQueryProcessor extends AbstractQueryProcessor {
|
||||
|
||||
/**
|
||||
* creates a query from the criteria
|
||||
@@ -110,11 +116,18 @@ class CriteriaQueryProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
var filterQuery = CriteriaFilterProcessor.createQuery(criteria);
|
||||
if (shouldQueries.isEmpty() && mustNotQueries.isEmpty() && mustQueries.isEmpty()) {
|
||||
return null;
|
||||
|
||||
if (filterQuery.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// we need something to add the filter to
|
||||
mustQueries.add(Query.of(qb -> qb.matchAll(m -> m)));
|
||||
}
|
||||
|
||||
Query query = new Query.Builder().bool(boolQueryBuilder -> {
|
||||
return new Query.Builder().bool(boolQueryBuilder -> {
|
||||
|
||||
if (!shouldQueries.isEmpty()) {
|
||||
boolQueryBuilder.should(shouldQueries);
|
||||
@@ -128,10 +141,10 @@ class CriteriaQueryProcessor {
|
||||
boolQueryBuilder.must(mustQueries);
|
||||
}
|
||||
|
||||
filterQuery.ifPresent(boolQueryBuilder::filter);
|
||||
|
||||
return boolQueryBuilder;
|
||||
}).build();
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -174,6 +187,12 @@ class CriteriaQueryProcessor {
|
||||
.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();
|
||||
}
|
||||
|
||||
@@ -227,7 +246,7 @@ class CriteriaQueryProcessor {
|
||||
queryBuilder.queryString(queryStringQuery(fieldName, '*' + searchText, true, boost));
|
||||
break;
|
||||
case EXPRESSION:
|
||||
queryBuilder.queryString(queryStringQuery(fieldName, value.toString(), boost));
|
||||
queryBuilder.queryString(queryStringQuery(fieldName, Objects.requireNonNull(value).toString(), boost));
|
||||
break;
|
||||
case LESS:
|
||||
queryBuilder //
|
||||
@@ -259,6 +278,7 @@ class CriteriaQueryProcessor {
|
||||
break;
|
||||
case BETWEEN:
|
||||
Object[] ranges = (Object[]) value;
|
||||
Assert.notNull(value, "value for a between condition must not be null");
|
||||
queryBuilder //
|
||||
.range(rb -> {
|
||||
rb.field(fieldName);
|
||||
@@ -282,10 +302,10 @@ class CriteriaQueryProcessor {
|
||||
.boost(boost)); //
|
||||
break;
|
||||
case MATCHES:
|
||||
queryBuilder.match(matchQuery(fieldName, value.toString(), Operator.Or, boost));
|
||||
queryBuilder.match(matchQuery(fieldName, Objects.requireNonNull(value).toString(), Operator.Or, boost));
|
||||
break;
|
||||
case MATCHES_ALL:
|
||||
queryBuilder.match(matchQuery(fieldName, value.toString(), Operator.And, boost));
|
||||
queryBuilder.match(matchQuery(fieldName, Objects.requireNonNull(value).toString(), Operator.And, boost));
|
||||
|
||||
break;
|
||||
case IN:
|
||||
@@ -334,9 +354,35 @@ class CriteriaQueryProcessor {
|
||||
queryBuilder //
|
||||
.regexp(rb -> rb //
|
||||
.field(fieldName) //
|
||||
.value(value.toString()) //
|
||||
.value(Objects.requireNonNull(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);
|
||||
}
|
||||
@@ -359,7 +405,7 @@ class CriteriaQueryProcessor {
|
||||
|
||||
if (item != null) {
|
||||
|
||||
if (sb.length() > 0) {
|
||||
if (!sb.isEmpty()) {
|
||||
sb.append(' ');
|
||||
}
|
||||
sb.append('"');
|
||||
@@ -391,4 +437,19 @@ class CriteriaQueryProcessor {
|
||||
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()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-1
@@ -49,6 +49,7 @@ import org.springframework.util.Assert;
|
||||
* {@link org.springframework.data.elasticsearch.core.document.Document}
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Haibo Liu
|
||||
* @since 4.4
|
||||
*/
|
||||
final class DocumentAdapters {
|
||||
@@ -73,7 +74,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, null,
|
||||
innerHits.put(name, SearchDocumentResponseBuilder.from(innerHitsResult.hits(), null, null, null, null, null,
|
||||
searchDocument -> null, jsonpMapper));
|
||||
});
|
||||
|
||||
|
||||
+11
-1
@@ -31,6 +31,10 @@ import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
|
||||
/**
|
||||
* Base class for a @{@link org.springframework.context.annotation.Configuration} class to set up the Elasticsearch
|
||||
* connection using the Elasticsearch Client. This class exposes different parts of the setup as Spring beans. Deriving
|
||||
@@ -118,7 +122,13 @@ public abstract class ElasticsearchConfiguration extends ElasticsearchConfigurat
|
||||
*/
|
||||
@Bean
|
||||
public JsonpMapper jsonpMapper() {
|
||||
return new JacksonJsonpMapper();
|
||||
// we need to create our own objectMapper that keeps null values in order to provide the storeNullValue
|
||||
// functionality. The one Elasticsearch would provide removes the nulls. We remove unwanted nulls before they get
|
||||
// into this mapper, so we can safely keep them here.
|
||||
var objectMapper = (new ObjectMapper())
|
||||
.configure(SerializationFeature.INDENT_OUTPUT, false)
|
||||
.setSerializationInclusion(JsonInclude.Include.ALWAYS);
|
||||
return new JacksonJsonpMapper(objectMapper);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+2
-1
@@ -40,6 +40,7 @@ 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 {
|
||||
@@ -59,7 +60,7 @@ public class ElasticsearchExceptionTranslator implements PersistenceExceptionTra
|
||||
*/
|
||||
public RuntimeException translateException(Throwable throwable) {
|
||||
|
||||
RuntimeException runtimeException = throwable instanceof RuntimeException ? (RuntimeException) throwable
|
||||
RuntimeException runtimeException = throwable instanceof RuntimeException ex ? ex
|
||||
: new RuntimeException(throwable.getMessage(), throwable);
|
||||
RuntimeException potentiallyTranslatedException = translateExceptionIfPossible(runtimeException);
|
||||
|
||||
|
||||
+91
-41
@@ -29,6 +29,7 @@ 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;
|
||||
@@ -50,14 +51,7 @@ 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.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.*;
|
||||
import org.springframework.data.elasticsearch.core.query.UpdateResponse;
|
||||
import org.springframework.data.elasticsearch.core.reindex.ReindexRequest;
|
||||
import org.springframework.data.elasticsearch.core.reindex.ReindexResponse;
|
||||
@@ -72,6 +66,7 @@ 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 {
|
||||
@@ -175,6 +170,11 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
|
||||
doBulkOperation(queries, bulkOptions, index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByQueryResponse delete(DeleteQuery query, Class<?> clazz) {
|
||||
return delete(query, clazz, getIndexCoordinatesFor(clazz));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByQueryResponse delete(Query query, Class<?> clazz, IndexCoordinates index) {
|
||||
|
||||
@@ -188,6 +188,18 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
|
||||
return responseConverter.byQueryResponse(response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByQueryResponse delete(DeleteQuery query, Class<?> clazz, IndexCoordinates index) {
|
||||
Assert.notNull(query, "query must not be null");
|
||||
|
||||
DeleteByQueryRequest request = requestConverter.documentDeleteByQueryRequest(query, routingResolver.getRouting(),
|
||||
clazz, index, getRefreshPolicy());
|
||||
|
||||
DeleteByQueryResponse response = execute(client -> client.deleteByQuery(request));
|
||||
|
||||
return responseConverter.byQueryResponse(response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UpdateResponse update(UpdateQuery updateQuery, IndexCoordinates index) {
|
||||
|
||||
@@ -437,13 +449,10 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
|
||||
Assert.notNull(queries, "queries must not be null");
|
||||
Assert.notNull(clazz, "clazz must not be null");
|
||||
|
||||
List<MultiSearchQueryParameter> multiSearchQueryParameters = new ArrayList<>(queries.size());
|
||||
for (Query query : queries) {
|
||||
multiSearchQueryParameters.add(new MultiSearchQueryParameter(query, clazz, getIndexCoordinatesFor(clazz)));
|
||||
}
|
||||
|
||||
int size = queries.size();
|
||||
// noinspection unchecked
|
||||
return doMultiSearch(multiSearchQueryParameters).stream().map(searchHits -> (SearchHits<T>) searchHits)
|
||||
return multiSearch(queries, Collections.nCopies(size, clazz), Collections.nCopies(size, index))
|
||||
.stream().map(searchHits -> (SearchHits<T>) searchHits)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@@ -454,14 +463,7 @@ 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");
|
||||
|
||||
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);
|
||||
return multiSearch(queries, classes, classes.stream().map(this::getIndexCoordinatesFor).toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -473,14 +475,7 @@ 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");
|
||||
|
||||
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);
|
||||
return multiSearch(queries, classes, Collections.nCopies(queries.size(), index));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -497,16 +492,50 @@ 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 doMultiSearch(multiSearchQueryParameters);
|
||||
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);
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
private List<SearchHits<?>> doMultiSearch(List<MultiSearchQueryParameter> multiSearchQueryParameters) {
|
||||
|
||||
MsearchRequest request = requestConverter.searchMsearchRequest(multiSearchQueryParameters,
|
||||
@@ -518,22 +547,37 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
|
||||
Assert.isTrue(multiSearchQueryParameters.size() == responseItems.size(),
|
||||
"number of response items does not match number of requests");
|
||||
|
||||
List<SearchHits<?>> searchHitsList = new ArrayList<>(multiSearchQueryParameters.size());
|
||||
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());
|
||||
|
||||
Iterator<MultiSearchQueryParameter> queryIterator = multiSearchQueryParameters.iterator();
|
||||
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<MultiSearchResponseItem<EntityAsMap>> responseIterator = responseItems.iterator();
|
||||
|
||||
while (queryIterator.hasNext()) {
|
||||
MultiSearchQueryParameter queryParameter = queryIterator.next();
|
||||
while (clazzIter.hasNext() && indexIter.hasNext()) {
|
||||
MultiSearchResponseItem<EntityAsMap> responseItem = responseIterator.next();
|
||||
|
||||
if (responseItem.isResult()) {
|
||||
|
||||
Class clazz = queryParameter.clazz;
|
||||
Class clazz = clazzIter.next();
|
||||
IndexCoordinates index = indexIter.next();
|
||||
ReadDocumentCallback<?> documentCallback = new ReadDocumentCallback<>(elasticsearchConverter, clazz,
|
||||
queryParameter.index);
|
||||
index);
|
||||
SearchDocumentResponseCallback<SearchHits<?>> callback = new ReadSearchDocumentResponseCallback<>(clazz,
|
||||
queryParameter.index);
|
||||
index);
|
||||
|
||||
SearchHits<?> searchHits = callback.doWith(
|
||||
SearchDocumentResponseBuilder.from(responseItem.result(), getEntityCreator(documentCallback), jsonpMapper));
|
||||
@@ -541,8 +585,8 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
|
||||
searchHitsList.add(searchHits);
|
||||
} else {
|
||||
if (LOGGER.isWarnEnabled()) {
|
||||
LOGGER
|
||||
.warn(String.format("multisearch responsecontains failure: {}", responseItem.failure().error().reason()));
|
||||
LOGGER.warn(String.format("multisearch response contains failure: %s",
|
||||
responseItem.failure().error().reason()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -556,6 +600,12 @@ 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) {
|
||||
|
||||
|
||||
+14
-3
@@ -35,14 +35,17 @@ 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) {
|
||||
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext, RequestConverter requestConverter) {
|
||||
this.mappingContext = mappingContext;
|
||||
this.requestConverter = requestConverter;
|
||||
}
|
||||
|
||||
public co.elastic.clients.elasticsearch.core.search.Highlight getHighlight(Highlight highlight,
|
||||
@@ -52,7 +55,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);
|
||||
addParameters(highlight.getParameters(), highlightBuilder, type);
|
||||
|
||||
for (HighlightField highlightField : highlight.getFields()) {
|
||||
String mappedName = mapFieldName(highlightField.getName(), type);
|
||||
@@ -69,7 +72,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) {
|
||||
co.elastic.clients.elasticsearch.core.search.Highlight.Builder builder, @Nullable Class<?> type) {
|
||||
|
||||
if (StringUtils.hasLength(parameters.getBoundaryChars())) {
|
||||
builder.boundaryChars(parameters.getBoundaryChars());
|
||||
@@ -103,6 +106,10 @@ 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()));
|
||||
}
|
||||
@@ -174,6 +181,10 @@ 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()));
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ 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;
|
||||
@@ -44,17 +43,13 @@ final class JsonUtils {
|
||||
mapper.serialize(object, generator);
|
||||
generator.close();
|
||||
String json = "{}";
|
||||
try {
|
||||
json = baos.toString("UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
LOGGER.warn("could not read json", e);
|
||||
}
|
||||
|
||||
json = baos.toString(StandardCharsets.UTF_8);
|
||||
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;
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
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;
|
||||
@@ -54,6 +55,7 @@ public class NativeQuery extends BaseQuery {
|
||||
|
||||
private Map<String, JsonData> searchExtensions = Collections.emptyMap();
|
||||
@Nullable private KnnQuery knnQuery;
|
||||
@Nullable private List<KnnSearch> knnSearches = Collections.emptyList();
|
||||
|
||||
public NativeQuery(NativeQueryBuilder builder) {
|
||||
super(builder);
|
||||
@@ -71,6 +73,7 @@ public class NativeQuery extends BaseQuery {
|
||||
}
|
||||
this.springDataQuery = builder.getSpringDataQuery();
|
||||
this.knnQuery = builder.getKnnQuery();
|
||||
this.knnSearches = builder.getKnnSearches();
|
||||
}
|
||||
|
||||
public NativeQuery(@Nullable Query query) {
|
||||
@@ -129,6 +132,14 @@ public class NativeQuery extends BaseQuery {
|
||||
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;
|
||||
|
||||
+13
-2
@@ -16,6 +16,7 @@
|
||||
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;
|
||||
@@ -26,6 +27,7 @@ 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;
|
||||
@@ -47,11 +49,12 @@ public class NativeQueryBuilder extends BaseQueryBuilder<NativeQuery, NativeQuer
|
||||
private final Map<String, Aggregation> aggregations = new LinkedHashMap<>();
|
||||
@Nullable private Suggester suggester;
|
||||
@Nullable private FieldCollapse fieldCollapse;
|
||||
private List<SortOptions> sortOptions = new ArrayList<>();
|
||||
private Map<String, JsonData> searchExtensions = new LinkedHashMap<>();
|
||||
private final List<SortOptions> sortOptions = new ArrayList<>();
|
||||
private final 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() {}
|
||||
|
||||
@@ -92,6 +95,14 @@ 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;
|
||||
|
||||
@@ -1,174 +0,0 @@
|
||||
/*
|
||||
* 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));
|
||||
}
|
||||
}
|
||||
+34
-2
@@ -61,6 +61,7 @@ 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.DeleteQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.Query;
|
||||
import org.springframework.data.elasticsearch.core.query.SearchTemplateQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.UpdateQuery;
|
||||
@@ -79,6 +80,7 @@ import org.springframework.util.StringUtils;
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Illia Ulianov
|
||||
* @author Junghoon Ban
|
||||
* @since 4.4
|
||||
*/
|
||||
public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearchTemplate {
|
||||
@@ -179,6 +181,15 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
|
||||
return Mono.from(execute(client -> client.deleteByQuery(request))).map(responseConverter::byQueryResponse);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<ByQueryResponse> delete(DeleteQuery query, Class<?> entityType, IndexCoordinates index) {
|
||||
Assert.notNull(query, "query must not be null");
|
||||
|
||||
DeleteByQueryRequest request = requestConverter.documentDeleteByQueryRequest(query, routingResolver.getRouting(),
|
||||
entityType, index, getRefreshPolicy());
|
||||
return Mono.from(execute(client -> client.deleteByQuery(request))).map(responseConverter::byQueryResponse);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Mono<T> get(String id, Class<T> entityType, IndexCoordinates index) {
|
||||
|
||||
@@ -384,7 +395,28 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
|
||||
Function<PitSearchAfter, Publisher<? extends ResponseBody<EntityAsMap>>> resourceClosure = psa -> {
|
||||
|
||||
baseQuery.setPointInTime(new Query.PointInTime(psa.getPit(), pitKeepAlive));
|
||||
baseQuery.addSort(Sort.by("_shard_doc"));
|
||||
|
||||
// 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"));
|
||||
}
|
||||
|
||||
SearchRequest firstSearchRequest = requestConverter.searchRequest(baseQuery, routingResolver.getRouting(),
|
||||
clazz, index, false, true);
|
||||
|
||||
@@ -645,7 +677,7 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
|
||||
*/
|
||||
private RuntimeException translateException(Throwable throwable) {
|
||||
|
||||
RuntimeException runtimeException = throwable instanceof RuntimeException ? (RuntimeException) throwable
|
||||
RuntimeException runtimeException = throwable instanceof RuntimeException ex ? ex
|
||||
: new RuntimeException(throwable.getMessage(), throwable);
|
||||
RuntimeException potentiallyTranslatedException = exceptionTranslator
|
||||
.translateExceptionIfPossible(runtimeException);
|
||||
|
||||
+213
-93
@@ -44,6 +44,7 @@ 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,6 +55,7 @@ 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;
|
||||
@@ -66,6 +68,7 @@ import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -105,10 +108,11 @@ import org.springframework.util.StringUtils;
|
||||
* @author Sascha Woo
|
||||
* @author cdalxndr
|
||||
* @author scoobyzhang
|
||||
* @author Haibo Liu
|
||||
* @since 4.4
|
||||
*/
|
||||
@SuppressWarnings("ClassCanBeRecord")
|
||||
class RequestConverter {
|
||||
class RequestConverter extends AbstractQueryProcessor {
|
||||
|
||||
private static final Log LOGGER = LogFactory.getLog(RequestConverter.class);
|
||||
|
||||
@@ -153,7 +157,6 @@ class RequestConverter {
|
||||
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) {
|
||||
@@ -169,7 +172,6 @@ class RequestConverter {
|
||||
|
||||
private Alias.Builder buildAlias(AliasActionParameters parameters, Alias.Builder aliasBuilder) {
|
||||
|
||||
// noinspection DuplicatedCode
|
||||
if (parameters.getRouting() != null) {
|
||||
aliasBuilder.routing(parameters.getRouting());
|
||||
}
|
||||
@@ -396,10 +398,7 @@ class RequestConverter {
|
||||
.order(putTemplateRequest.getOrder());
|
||||
|
||||
if (putTemplateRequest.getSettings() != null) {
|
||||
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));
|
||||
Map<String, JsonData> settings = getTemplateParams(putTemplateRequest.getSettings().entrySet());
|
||||
builder.settings(settings);
|
||||
}
|
||||
|
||||
@@ -415,7 +414,6 @@ class RequestConverter {
|
||||
if (aliasActions != null) {
|
||||
aliasActions.getActions().forEach(aliasAction -> {
|
||||
AliasActionParameters parameters = aliasAction.getParameters();
|
||||
// noinspection DuplicatedCode
|
||||
String[] parametersAliases = parameters.getAliases();
|
||||
|
||||
if (parametersAliases != null) {
|
||||
@@ -449,7 +447,6 @@ class RequestConverter {
|
||||
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) {
|
||||
@@ -545,13 +542,12 @@ class RequestConverter {
|
||||
Object queryObject = query.getObject();
|
||||
|
||||
if (queryObject != null) {
|
||||
String id = StringUtils.hasText(query.getId()) ? query.getId() : getPersistentEntityId(queryObject);
|
||||
builder //
|
||||
.id(id) //
|
||||
builder
|
||||
.id(StringUtils.hasText(query.getId()) ? query.getId() : getPersistentEntityId(queryObject))
|
||||
.document(elasticsearchConverter.mapObject(queryObject));
|
||||
} else if (query.getSource() != null) {
|
||||
builder //
|
||||
.id(query.getId()) //
|
||||
builder
|
||||
.id(query.getId())
|
||||
.document(new DefaultStringObjectMap<>().fromJson(query.getSource()));
|
||||
} else {
|
||||
throw new InvalidDataAccessApiUsageException(
|
||||
@@ -597,12 +593,13 @@ class RequestConverter {
|
||||
Object queryObject = query.getObject();
|
||||
|
||||
if (queryObject != null) {
|
||||
String id = StringUtils.hasText(query.getId()) ? query.getId() : getPersistentEntityId(queryObject);
|
||||
builder //
|
||||
.id(id) //
|
||||
builder
|
||||
.id(StringUtils.hasText(query.getId()) ? query.getId() : getPersistentEntityId(queryObject))
|
||||
.document(elasticsearchConverter.mapObject(queryObject));
|
||||
} else if (query.getSource() != null) {
|
||||
builder.document(new DefaultStringObjectMap<>().fromJson(query.getSource()));
|
||||
builder
|
||||
.id(query.getId())
|
||||
.document(new DefaultStringObjectMap<>().fromJson(query.getSource()));
|
||||
} else {
|
||||
throw new InvalidDataAccessApiUsageException(
|
||||
"object or source is null, failed to index the document [id: " + query.getId() + ']');
|
||||
@@ -638,12 +635,13 @@ class RequestConverter {
|
||||
Object queryObject = query.getObject();
|
||||
|
||||
if (queryObject != null) {
|
||||
String id = StringUtils.hasText(query.getId()) ? query.getId() : getPersistentEntityId(queryObject);
|
||||
builder //
|
||||
.id(id) //
|
||||
builder
|
||||
.id(StringUtils.hasText(query.getId()) ? query.getId() : getPersistentEntityId(queryObject))
|
||||
.document(elasticsearchConverter.mapObject(queryObject));
|
||||
} else if (query.getSource() != null) {
|
||||
builder.document(new DefaultStringObjectMap<>().fromJson(query.getSource()));
|
||||
builder
|
||||
.id(query.getId())
|
||||
.document(new DefaultStringObjectMap<>().fromJson(query.getSource()));
|
||||
} else {
|
||||
throw new InvalidDataAccessApiUsageException(
|
||||
"object or source is null, failed to index the document [id: " + query.getId() + ']');
|
||||
@@ -966,6 +964,78 @@ class RequestConverter {
|
||||
});
|
||||
}
|
||||
|
||||
public DeleteByQueryRequest documentDeleteByQueryRequest(DeleteQuery query, @Nullable String routing, Class<?> clazz,
|
||||
IndexCoordinates index, @Nullable RefreshPolicy refreshPolicy) {
|
||||
Assert.notNull(query, "query must not be null");
|
||||
Assert.notNull(index, "index must not be null");
|
||||
|
||||
return DeleteByQueryRequest.of(dqb -> {
|
||||
dqb.index(Arrays.asList(index.getIndexNames())) //
|
||||
.query(getQuery(query.getQuery(), clazz))//
|
||||
.refresh(deleteByQueryRefresh(refreshPolicy))
|
||||
.requestsPerSecond(query.getRequestsPerSecond())
|
||||
.maxDocs(query.getMaxDocs())
|
||||
.scroll(time(query.getScroll()))
|
||||
.scrollSize(query.getScrollSize());
|
||||
|
||||
if (query.getRouting() != null) {
|
||||
dqb.routing(query.getRouting());
|
||||
} else if (StringUtils.hasText(routing)) {
|
||||
dqb.routing(routing);
|
||||
}
|
||||
|
||||
if (query.getQ() != null) {
|
||||
dqb.q(query.getQ())
|
||||
.analyzer(query.getAnalyzer())
|
||||
.analyzeWildcard(query.getAnalyzeWildcard())
|
||||
.defaultOperator(operator(query.getDefaultOperator()))
|
||||
.df(query.getDf())
|
||||
.lenient(query.getLenient());
|
||||
}
|
||||
|
||||
if (query.getExpandWildcards() != null && !query.getExpandWildcards().isEmpty()) {
|
||||
dqb.expandWildcards(expandWildcards(query.getExpandWildcards()));
|
||||
}
|
||||
if (query.getStats() != null && !query.getStats().isEmpty()) {
|
||||
dqb.stats(query.getStats());
|
||||
}
|
||||
if (query.getSlices() != null) {
|
||||
dqb.slices(sb -> sb.value(query.getSlices()));
|
||||
}
|
||||
if (query.getSort() != null) {
|
||||
ElasticsearchPersistentEntity<?> persistentEntity = getPersistentEntity(clazz);
|
||||
List<SortOptions> sortOptions = getSortOptions(query.getSort(), persistentEntity);
|
||||
|
||||
if (!sortOptions.isEmpty()) {
|
||||
dqb.sort(
|
||||
sortOptions.stream()
|
||||
.map(sortOption -> {
|
||||
String order = "asc";
|
||||
var sortField = sortOption.field();
|
||||
if (sortField.order() != null) {
|
||||
order = sortField.order().jsonValue();
|
||||
}
|
||||
|
||||
return sortField.field() + ":" + order;
|
||||
})
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
}
|
||||
dqb.allowNoIndices(query.getAllowNoIndices())
|
||||
.conflicts(conflicts(query.getConflicts()))
|
||||
.ignoreUnavailable(query.getIgnoreUnavailable())
|
||||
.preference(query.getPreference())
|
||||
.requestCache(query.getRequestCache())
|
||||
.searchType(searchType(query.getSearchType()))
|
||||
.searchTimeout(time(query.getSearchTimeout()))
|
||||
.terminateAfter(query.getTerminateAfter())
|
||||
.timeout(time(query.getTimeout()))
|
||||
.version(query.getVersion());
|
||||
|
||||
return dqb;
|
||||
});
|
||||
}
|
||||
|
||||
public UpdateRequest<Document, ?> documentUpdateRequest(UpdateQuery query, IndexCoordinates index,
|
||||
@Nullable RefreshPolicy refreshPolicy, @Nullable String routing) {
|
||||
|
||||
@@ -1140,11 +1210,40 @@ class RequestConverter {
|
||||
builder.routing(routing);
|
||||
}
|
||||
|
||||
addFilter(query, builder);
|
||||
addPostFilter(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) {
|
||||
|
||||
@@ -1156,28 +1255,7 @@ class RequestConverter {
|
||||
|
||||
var query = param.query();
|
||||
mrb.searches(sb -> sb //
|
||||
.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;
|
||||
}) //
|
||||
.header(msearchHeaderBuilder(query, param.index(), routing)) //
|
||||
.body(bb -> {
|
||||
bb //
|
||||
.query(getQuery(query, param.clazz()))//
|
||||
@@ -1283,6 +1361,35 @@ class RequestConverter {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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 && nativeQuery.getKnnQuery() != null) ? 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) {
|
||||
|
||||
@@ -1370,8 +1477,8 @@ class RequestConverter {
|
||||
if (query instanceof NativeQuery nativeQuery) {
|
||||
prepareNativeSearch(nativeQuery, builder);
|
||||
}
|
||||
// query.getSort() must be checked after prepareNativeSearch as this already might hav a sort set that must have
|
||||
// higher priority
|
||||
// query.getSort() must be checked after prepareNativeSearch as this already might have a sort set
|
||||
// that must have higher priority
|
||||
if (query.getSort() != null) {
|
||||
List<SortOptions> sortOptions = getSortOptions(query.getSort(), persistentEntity);
|
||||
|
||||
@@ -1393,7 +1500,15 @@ class RequestConverter {
|
||||
}
|
||||
|
||||
if (!isEmpty(query.getSearchAfter())) {
|
||||
builder.searchAfter(query.getSearchAfter().stream().map(TypeUtils::toFieldValue).toList());
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
query.getRescorerQueries().forEach(rescorerQuery -> builder.rescore(getRescore(rescorerQuery)));
|
||||
@@ -1494,7 +1609,7 @@ class RequestConverter {
|
||||
private void addHighlight(Query query, SearchRequest.Builder builder) {
|
||||
|
||||
Highlight highlight = query.getHighlightQuery()
|
||||
.map(highlightQuery -> new HighlightQueryBuilder(elasticsearchConverter.getMappingContext())
|
||||
.map(highlightQuery -> new HighlightQueryBuilder(elasticsearchConverter.getMappingContext(), this)
|
||||
.getHighlight(highlightQuery.getHighlight(), highlightQuery.getType()))
|
||||
.orElse(null);
|
||||
|
||||
@@ -1504,7 +1619,7 @@ class RequestConverter {
|
||||
private void addHighlight(Query query, MultisearchBody.Builder builder) {
|
||||
|
||||
Highlight highlight = query.getHighlightQuery()
|
||||
.map(highlightQuery -> new HighlightQueryBuilder(elasticsearchConverter.getMappingContext())
|
||||
.map(highlightQuery -> new HighlightQueryBuilder(elasticsearchConverter.getMappingContext(), this)
|
||||
.getHighlight(highlightQuery.getHighlight(), highlightQuery.getType()))
|
||||
.orElse(null);
|
||||
|
||||
@@ -1612,7 +1727,18 @@ class RequestConverter {
|
||||
;
|
||||
|
||||
if (query.getKnnQuery() != null) {
|
||||
builder.knn(query.getKnnQuery());
|
||||
var kq = query.getKnnQuery();
|
||||
builder.knn(ksb -> ksb
|
||||
.field(kq.field())
|
||||
.queryVector(kq.queryVector())
|
||||
.numCandidates(kq.numCandidates())
|
||||
.filter(kq.filter())
|
||||
.similarity(kq.similarity()));
|
||||
|
||||
}
|
||||
|
||||
if (!isEmpty(query.getKnnSearches())) {
|
||||
builder.knn(query.getKnnSearches());
|
||||
}
|
||||
|
||||
if (!isEmpty(query.getAggregations())) {
|
||||
@@ -1633,7 +1759,18 @@ class RequestConverter {
|
||||
.sort(query.getSortOptions());
|
||||
|
||||
if (query.getKnnQuery() != null) {
|
||||
builder.knn(query.getKnnQuery());
|
||||
var kq = query.getKnnQuery();
|
||||
builder.knn(ksb -> ksb
|
||||
.field(kq.field())
|
||||
.queryVector(kq.queryVector())
|
||||
.numCandidates(kq.numCandidates())
|
||||
.filter(kq.filter())
|
||||
.similarity(kq.similarity()));
|
||||
|
||||
}
|
||||
|
||||
if (!isEmpty(query.getKnnSearches())) {
|
||||
builder.knn(query.getKnnSearches());
|
||||
}
|
||||
|
||||
if (!isEmpty(query.getAggregations())) {
|
||||
@@ -1646,46 +1783,23 @@ class RequestConverter {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private co.elastic.clients.elasticsearch._types.query_dsl.Query getQuery(@Nullable Query query,
|
||||
co.elastic.clients.elasticsearch._types.query_dsl.Query getQuery(@Nullable Query query,
|
||||
@Nullable Class<?> 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;
|
||||
return getEsQuery(query, (q) -> elasticsearchConverter.updateQuery(q, clazz));
|
||||
}
|
||||
|
||||
private void addFilter(Query query, SearchRequest.Builder builder) {
|
||||
@SuppressWarnings("StatementWithEmptyBody")
|
||||
private void addPostFilter(Query query, SearchRequest.Builder builder) {
|
||||
|
||||
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) {
|
||||
builder.postFilter(((NativeQuery) query).getFilter());
|
||||
} else {
|
||||
throw new IllegalArgumentException("unhandled Query implementation " + query.getClass().getName());
|
||||
// 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 (nativeQuery.getFilter() != null) {
|
||||
builder.postFilter(nativeQuery.getFilter());
|
||||
} else if (nativeQuery.getSpringDataQuery() != null) {
|
||||
addPostFilter(nativeQuery.getSpringDataQuery(), builder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1769,7 +1883,8 @@ class RequestConverter {
|
||||
.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) {
|
||||
@@ -1788,10 +1903,7 @@ class RequestConverter {
|
||||
}
|
||||
|
||||
if (!CollectionUtils.isEmpty(query.getParams())) {
|
||||
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));
|
||||
Map<String, JsonData> params = getTemplateParams(query.getParams().entrySet());
|
||||
builder.params(params);
|
||||
}
|
||||
|
||||
@@ -1799,6 +1911,14 @@ class RequestConverter {
|
||||
});
|
||||
}
|
||||
|
||||
@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) {
|
||||
|
||||
+17
-10
@@ -15,9 +15,8 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.client.elc;
|
||||
|
||||
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 static org.springframework.data.elasticsearch.client.elc.JsonUtils.*;
|
||||
import static org.springframework.data.elasticsearch.client.elc.TypeUtils.*;
|
||||
|
||||
import co.elastic.clients.elasticsearch._types.BulkIndexByScrollFailure;
|
||||
import co.elastic.clients.elasticsearch._types.ErrorCause;
|
||||
@@ -36,7 +35,12 @@ import co.elastic.clients.elasticsearch.indices.get_index_template.IndexTemplate
|
||||
import co.elastic.clients.elasticsearch.indices.get_mapping.IndexMappingRecord;
|
||||
import co.elastic.clients.json.JsonpMapper;
|
||||
|
||||
import java.util.*;
|
||||
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.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -47,7 +51,11 @@ 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.*;
|
||||
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.mapping.IndexCoordinates;
|
||||
import org.springframework.data.elasticsearch.core.query.ByQueryResponse;
|
||||
import org.springframework.data.elasticsearch.core.query.StringQuery;
|
||||
@@ -121,8 +129,7 @@ class ResponseConverter {
|
||||
.build();
|
||||
}
|
||||
|
||||
private TemplateResponseData clusterGetComponentTemplateData(
|
||||
ComponentTemplateSummary componentTemplateSummary) {
|
||||
private TemplateResponseData clusterGetComponentTemplateData(ComponentTemplateSummary componentTemplateSummary) {
|
||||
|
||||
var mapping = typeMapping(componentTemplateSummary.mappings());
|
||||
var settings = new Settings();
|
||||
@@ -183,7 +190,7 @@ class ResponseConverter {
|
||||
|
||||
Map<String, IndexMappingRecord> mappings = getMappingResponse.result();
|
||||
|
||||
if (mappings == null || mappings.size() == 0) {
|
||||
if (mappings == null || mappings.isEmpty()) {
|
||||
return Document.create();
|
||||
}
|
||||
|
||||
@@ -326,7 +333,7 @@ class ResponseConverter {
|
||||
}
|
||||
|
||||
private TemplateResponseData indexGetComponentTemplateData(IndexTemplateSummary indexTemplateSummary,
|
||||
List<String> composedOf) {
|
||||
List<String> composedOf) {
|
||||
var mapping = typeMapping(indexTemplateSummary.mappings());
|
||||
|
||||
Function<IndexSettings, Settings> indexSettingsToSettings = indexSettings -> {
|
||||
@@ -541,7 +548,7 @@ class ResponseConverter {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static ElasticsearchErrorCause toErrorCause(@Nullable ErrorCause errorCause) {
|
||||
static ElasticsearchErrorCause toErrorCause(@Nullable ErrorCause errorCause) {
|
||||
|
||||
if (errorCause != null) {
|
||||
return new ElasticsearchErrorCause( //
|
||||
|
||||
+23
-8
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
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;
|
||||
@@ -36,6 +38,7 @@ 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;
|
||||
@@ -52,6 +55,7 @@ import org.springframework.util.CollectionUtils;
|
||||
* Factory class to create {@link SearchDocumentResponse} instances.
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Haibo Liu
|
||||
* @since 4.4
|
||||
*/
|
||||
class SearchDocumentResponseBuilder {
|
||||
@@ -78,8 +82,9 @@ class SearchDocumentResponseBuilder {
|
||||
Map<String, Aggregate> aggregations = responseBody.aggregations();
|
||||
Map<String, List<Suggestion<EntityAsMap>>> suggest = responseBody.suggest();
|
||||
var pointInTimeId = responseBody.pitId();
|
||||
var shards = responseBody.shards();
|
||||
|
||||
return from(hitsMetadata, scrollId, pointInTimeId, aggregations, suggest, entityCreator, jsonpMapper);
|
||||
return from(hitsMetadata, shards, scrollId, pointInTimeId, aggregations, suggest, entityCreator, jsonpMapper);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -98,13 +103,14 @@ 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();
|
||||
|
||||
return from(hitsMetadata, scrollId, pointInTimeId, aggregations, suggest, entityCreator, jsonpMapper);
|
||||
return from(hitsMetadata, shards, scrollId, pointInTimeId, aggregations, suggest, entityCreator, jsonpMapper);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -120,8 +126,8 @@ class SearchDocumentResponseBuilder {
|
||||
* @param jsonpMapper to map JsonData objects
|
||||
* @return the {@link SearchDocumentResponse}
|
||||
*/
|
||||
public static <T> SearchDocumentResponse from(HitsMetadata<?> hitsMetadata, @Nullable String scrollId,
|
||||
@Nullable String pointInTimeId, @Nullable Map<String, Aggregate> aggregations,
|
||||
public static <T> SearchDocumentResponse from(HitsMetadata<?> hitsMetadata, @Nullable ShardStatistics shards,
|
||||
@Nullable String scrollId, @Nullable String pointInTimeId, @Nullable Map<String, Aggregate> aggregations,
|
||||
Map<String, List<Suggestion<EntityAsMap>>> suggestES, SearchDocumentResponse.EntityCreator<T> entityCreator,
|
||||
JsonpMapper jsonpMapper) {
|
||||
|
||||
@@ -155,8 +161,18 @@ class SearchDocumentResponseBuilder {
|
||||
|
||||
Suggest suggest = suggestFrom(suggestES, entityCreator);
|
||||
|
||||
SearchShardStatistics shardStatistics = shards != null ? shardsFrom(shards) : null;
|
||||
|
||||
return new SearchDocumentResponse(totalHits, totalHitsRelation, maxScore, scrollId, pointInTimeId, searchDocuments,
|
||||
aggregationsContainer, suggest);
|
||||
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);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -218,9 +234,8 @@ 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,6 +18,8 @@ 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;
|
||||
@@ -40,12 +42,15 @@ 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;
|
||||
@@ -500,4 +505,48 @@ 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;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
+2
-5
@@ -57,7 +57,6 @@ 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;
|
||||
@@ -300,6 +299,7 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public ByQueryResponse delete(Query query, Class<?> clazz) {
|
||||
return delete(query, clazz, getIndexCoordinatesFor(clazz));
|
||||
}
|
||||
@@ -779,8 +779,7 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
|
||||
}
|
||||
|
||||
protected interface SearchDocumentResponseCallback<T> {
|
||||
@NonNull
|
||||
T doWith(@NonNull SearchDocumentResponse response);
|
||||
T doWith(SearchDocumentResponse response);
|
||||
}
|
||||
|
||||
protected class ReadSearchDocumentResponseCallback<T> implements SearchDocumentResponseCallback<SearchHits<T>> {
|
||||
@@ -795,7 +794,6 @@ 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());
|
||||
@@ -816,7 +814,6 @@ 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());
|
||||
|
||||
+7
-3
@@ -46,6 +46,7 @@ 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;
|
||||
@@ -55,7 +56,6 @@ 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,9 +409,15 @@ 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
|
||||
@@ -575,7 +581,6 @@ abstract public class AbstractReactiveElasticsearchTemplate
|
||||
* @param document the document to convert
|
||||
* @return a Mono of the entity
|
||||
*/
|
||||
@NonNull
|
||||
Mono<T> toEntity(@Nullable Document document);
|
||||
}
|
||||
|
||||
@@ -593,7 +598,6 @@ abstract public class AbstractReactiveElasticsearchTemplate
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Mono<T> toEntity(@Nullable Document document) {
|
||||
if (document == null) {
|
||||
return Mono.empty();
|
||||
|
||||
@@ -21,6 +21,7 @@ import java.util.List;
|
||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
import org.springframework.data.elasticsearch.core.query.BulkOptions;
|
||||
import org.springframework.data.elasticsearch.core.query.ByQueryResponse;
|
||||
import org.springframework.data.elasticsearch.core.query.DeleteQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.IndexQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.Query;
|
||||
import org.springframework.data.elasticsearch.core.query.UpdateQuery;
|
||||
@@ -279,9 +280,22 @@ 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.
|
||||
*
|
||||
@@ -290,9 +304,23 @@ 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.
|
||||
*
|
||||
|
||||
+26
@@ -25,6 +25,7 @@ import java.util.List;
|
||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
import org.springframework.data.elasticsearch.core.query.BulkOptions;
|
||||
import org.springframework.data.elasticsearch.core.query.ByQueryResponse;
|
||||
import org.springframework.data.elasticsearch.core.query.DeleteQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.Query;
|
||||
import org.springframework.data.elasticsearch.core.query.UpdateQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.UpdateResponse;
|
||||
@@ -331,9 +332,21 @@ 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.
|
||||
*
|
||||
@@ -341,9 +354,22 @@ 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.
|
||||
*
|
||||
|
||||
@@ -22,5 +22,5 @@ package org.springframework.data.elasticsearch.core;
|
||||
* @since 4.2
|
||||
*/
|
||||
public enum RefreshPolicy {
|
||||
NONE, IMMEDIATE, WAIT_UNTIL;
|
||||
NONE, IMMEDIATE, WAIT_UNTIL
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ import org.springframework.util.Assert;
|
||||
* @author Matt Gilene
|
||||
* @author Sascha Woo
|
||||
* @author Jakob Hoeper
|
||||
* @author Haibo Liu
|
||||
* @since 4.0
|
||||
*/
|
||||
public class SearchHitMapping<T> {
|
||||
@@ -84,6 +85,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();
|
||||
String scrollId = searchDocumentResponse.getScrollId();
|
||||
String pointInTimeId = searchDocumentResponse.getPointInTimeId();
|
||||
@@ -103,7 +105,7 @@ public class SearchHitMapping<T> {
|
||||
mapHitsInCompletionSuggestion(suggest);
|
||||
|
||||
return new SearchHitsImpl<>(totalHits, totalHitsRelation, maxScore, scrollId, pointInTimeId, searchHits,
|
||||
aggregations, suggest);
|
||||
aggregations, suggest, shardStatistics);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -165,7 +167,7 @@ public class SearchHitMapping<T> {
|
||||
Map<String, SearchHits<?>> innerHits = new LinkedHashMap<>();
|
||||
Map<String, SearchDocumentResponse> documentInnerHits = searchDocument.getInnerHits();
|
||||
|
||||
if (documentInnerHits != null && documentInnerHits.size() > 0) {
|
||||
if (documentInnerHits != null && !documentInnerHits.isEmpty()) {
|
||||
|
||||
SearchHitMapping<SearchDocument> searchDocumentSearchHitMapping = SearchHitMapping
|
||||
.mappingFor(SearchDocument.class, converter);
|
||||
@@ -233,14 +235,15 @@ public class SearchHitMapping<T> {
|
||||
scrollId = searchHitsImpl.getScrollId();
|
||||
}
|
||||
|
||||
return new SearchHitsImpl<>(searchHits.getTotalHits(), //
|
||||
searchHits.getTotalHitsRelation(), //
|
||||
searchHits.getMaxScore(), //
|
||||
scrollId, //
|
||||
searchHits.getPointInTimeId(), //
|
||||
convertedSearchHits, //
|
||||
searchHits.getAggregations(), //
|
||||
searchHits.getSuggest());
|
||||
return new SearchHitsImpl<>(searchHits.getTotalHits(),
|
||||
searchHits.getTotalHitsRelation(),
|
||||
searchHits.getMaxScore(),
|
||||
scrollId,
|
||||
searchHits.getPointInTimeId(),
|
||||
convertedSearchHits,
|
||||
searchHits.getAggregations(),
|
||||
searchHits.getSuggest(),
|
||||
searchHits.getSearchShardStatistics());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new UncategorizedElasticsearchException("Unable to convert inner hits.", e);
|
||||
@@ -284,8 +287,8 @@ public class SearchHitMapping<T> {
|
||||
}
|
||||
|
||||
private static class ElasticsearchPersistentEntityWithNestedMetaData {
|
||||
@Nullable private ElasticsearchPersistentEntity<?> entity;
|
||||
private NestedMetaData nestedMetaData;
|
||||
@Nullable private final ElasticsearchPersistentEntity<?> entity;
|
||||
private final 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;
|
||||
|
||||
/**
|
||||
|
||||
@@ -27,6 +27,7 @@ import org.springframework.lang.Nullable;
|
||||
*
|
||||
* @param <T> the result data class.
|
||||
* @author Sascha Woo
|
||||
* @author Haibo Liu
|
||||
* @since 4.0
|
||||
*/
|
||||
public interface SearchHits<T> extends Streamable<SearchHit<T>> {
|
||||
@@ -108,4 +109,10 @@ public interface SearchHits<T> extends Streamable<SearchHit<T>> {
|
||||
*/
|
||||
@Nullable
|
||||
String getPointInTimeId();
|
||||
|
||||
/**
|
||||
* @return shard statistics for the search hit.
|
||||
*/
|
||||
@Nullable
|
||||
SearchShardStatistics getSearchShardStatistics();
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import org.springframework.util.Assert;
|
||||
* @param <T> the result data class.
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Sascha Woo
|
||||
* @author Haibo Liu
|
||||
* @since 4.0
|
||||
*/
|
||||
public class SearchHitsImpl<T> implements SearchScrollHits<T> {
|
||||
@@ -41,7 +42,8 @@ public class SearchHitsImpl<T> implements SearchScrollHits<T> {
|
||||
private final Lazy<List<SearchHit<T>>> unmodifiableSearchHits;
|
||||
@Nullable private final AggregationsContainer<?> aggregations;
|
||||
@Nullable private final Suggest suggest;
|
||||
@Nullable private String pointInTimeId;
|
||||
@Nullable private final String pointInTimeId;
|
||||
@Nullable private final SearchShardStatistics searchShardStatistics;
|
||||
|
||||
/**
|
||||
* @param totalHits the number of total hits for the search
|
||||
@@ -53,7 +55,8 @@ public class SearchHitsImpl<T> implements SearchScrollHits<T> {
|
||||
*/
|
||||
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) {
|
||||
@Nullable AggregationsContainer<?> aggregations, @Nullable Suggest suggest,
|
||||
@Nullable SearchShardStatistics searchShardStatistics) {
|
||||
|
||||
Assert.notNull(searchHits, "searchHits must not be null");
|
||||
|
||||
@@ -66,6 +69,7 @@ public class SearchHitsImpl<T> implements SearchScrollHits<T> {
|
||||
this.aggregations = aggregations;
|
||||
this.suggest = suggest;
|
||||
this.unmodifiableSearchHits = Lazy.of(() -> Collections.unmodifiableList(searchHits));
|
||||
this.searchShardStatistics = searchShardStatistics;
|
||||
}
|
||||
|
||||
// region getter
|
||||
@@ -118,6 +122,11 @@ public class SearchHitsImpl<T> implements SearchScrollHits<T> {
|
||||
return pointInTimeId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SearchShardStatistics getSearchShardStatistics() {
|
||||
return searchShardStatistics;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SearchHits{" + //
|
||||
@@ -128,6 +137,7 @@ public class SearchHitsImpl<T> implements SearchScrollHits<T> {
|
||||
", pointInTimeId='" + pointInTimeId + '\'' + //
|
||||
", searchHits={" + searchHits.size() + " elements}" + //
|
||||
", aggregations=" + aggregations + //
|
||||
", shardStatistics=" + searchShardStatistics + //
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -61,10 +61,10 @@ abstract class StreamQueries {
|
||||
|
||||
return new SearchHitsIterator<>() {
|
||||
|
||||
private volatile AtomicInteger currentCount = new AtomicInteger();
|
||||
private final AtomicInteger currentCount = new AtomicInteger();
|
||||
private volatile Iterator<SearchHit<T>> currentScrollHits = searchHits.iterator();
|
||||
private volatile boolean continueScroll = currentScrollHits.hasNext();
|
||||
private volatile ScrollState scrollState = new ScrollState(searchHits.getScrollId());
|
||||
private final ScrollState scrollState = new ScrollState(searchHits.getScrollId());
|
||||
private volatile boolean isClosed = false;
|
||||
|
||||
@Override
|
||||
|
||||
+1
-1
@@ -110,6 +110,6 @@ public interface ElasticsearchConverter
|
||||
* @return a String wihere the property names are replaced with field names
|
||||
* @since 5.2
|
||||
*/
|
||||
public String updateFieldNames(String propertyPath, ElasticsearchPersistentEntity<?> persistentEntity);
|
||||
String updateFieldNames(String propertyPath, ElasticsearchPersistentEntity<?> persistentEntity);
|
||||
// endregion
|
||||
}
|
||||
|
||||
+1
-1
@@ -129,7 +129,7 @@ public class ElasticsearchCustomConversions extends CustomConversions {
|
||||
@WritingConverter
|
||||
enum ByteArrayToBase64Converter implements Converter<byte[], String> {
|
||||
|
||||
INSTANCE,;
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public String convert(byte[] source) {
|
||||
|
||||
+1
-1
@@ -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.size() > 0, "no linestrings defined in polygon");
|
||||
Assert.isTrue(!lines.isEmpty(), "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));
|
||||
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
+103
-79
@@ -34,6 +34,9 @@ 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;
|
||||
@@ -58,6 +61,7 @@ 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;
|
||||
@@ -80,10 +84,11 @@ 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 {
|
||||
implements ElasticsearchConverter, ApplicationContextAware, InitializingBean, EnvironmentCapable {
|
||||
|
||||
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!";
|
||||
@@ -93,7 +98,14 @@ 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;
|
||||
|
||||
@@ -121,6 +133,14 @@ public class MappingElasticsearchConverter
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Environment getEnvironment() {
|
||||
if (environment == null) {
|
||||
environment = new StandardEnvironment();
|
||||
}
|
||||
return environment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> getMappingContext() {
|
||||
return mappingContext;
|
||||
@@ -159,7 +179,8 @@ public class MappingElasticsearchConverter
|
||||
@Override
|
||||
public <R> R read(Class<R> type, Document source) {
|
||||
|
||||
Reader reader = new Reader(mappingContext, conversionService, conversions, typeMapper, spELContext, instantiators);
|
||||
Reader reader = new Reader(mappingContext, conversionService, conversions, typeMapper, expressionEvaluatorFactory,
|
||||
instantiators);
|
||||
return reader.read(type, source);
|
||||
}
|
||||
|
||||
@@ -199,29 +220,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,
|
||||
SpELContext spELContext, EntityInstantiators instantiators) {
|
||||
CachingValueExpressionEvaluatorFactory expressionEvaluatorFactory, EntityInstantiators instantiators) {
|
||||
|
||||
super(mappingContext, conversionService, conversions, typeMapper);
|
||||
this.spELContext = spELContext;
|
||||
this.expressionEvaluatorFactory = expressionEvaluatorFactory;
|
||||
this.instantiators = instantiators;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
/**
|
||||
* Reads the given source into the given type.
|
||||
*
|
||||
* @param type they type to convert the given source to.
|
||||
* @param type the 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);
|
||||
|
||||
@@ -313,8 +334,7 @@ public class MappingElasticsearchConverter
|
||||
private <R> R readEntity(ElasticsearchPersistentEntity<?> entity, Map<String, Object> source) {
|
||||
|
||||
ElasticsearchPersistentEntity<?> targetEntity = computeClosestEntity(entity, source);
|
||||
|
||||
SpELExpressionEvaluator evaluator = new DefaultSpELExpressionEvaluator(source, spELContext);
|
||||
ValueExpressionEvaluator evaluator = expressionEvaluatorFactory.create(source);
|
||||
MapValueAccessor accessor = new MapValueAccessor(source);
|
||||
|
||||
InstanceCreatorMetadata<?> creatorMetadata = entity.getInstanceCreatorMetadata();
|
||||
@@ -332,50 +352,56 @@ public class MappingElasticsearchConverter
|
||||
return instance;
|
||||
}
|
||||
|
||||
Document document = (source instanceof Document) ? (Document) source : null;
|
||||
|
||||
ElasticsearchPropertyValueProvider valueProvider = new ElasticsearchPropertyValueProvider(accessor, evaluator);
|
||||
R result = readProperties(targetEntity, instance, valueProvider);
|
||||
try {
|
||||
R result = readProperties(targetEntity, instance, valueProvider);
|
||||
|
||||
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 (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 (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 SearchDocument searchDocument) {
|
||||
populateScriptFields(targetEntity, result, searchDocument);
|
||||
}
|
||||
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, SpELExpressionEvaluator evaluator) {
|
||||
ElasticsearchPersistentEntity<?> entity, MapValueAccessor source, ValueExpressionEvaluator evaluator) {
|
||||
|
||||
ElasticsearchPropertyValueProvider provider = new ElasticsearchPropertyValueProvider(source, evaluator);
|
||||
|
||||
@@ -384,7 +410,7 @@ public class MappingElasticsearchConverter
|
||||
PersistentEntityParameterValueProvider<ElasticsearchPersistentProperty> parameterProvider = new PersistentEntityParameterValueProvider<>(
|
||||
entity, provider, null);
|
||||
|
||||
return new ConverterAwareSpELExpressionParameterValueProvider(evaluator, conversionService, parameterProvider);
|
||||
return new ConverterAwareValueExpressionParameterValueProvider(evaluator, conversionService, parameterProvider);
|
||||
}
|
||||
|
||||
private boolean isAssignedSeqNo(long seqNo) {
|
||||
@@ -466,7 +492,7 @@ public class MappingElasticsearchConverter
|
||||
TypeInformation<?> collectionComponentType = getCollectionComponentType(type);
|
||||
if (collectionComponentType != null) {
|
||||
Object o = read(collectionComponentType, (Map<String, Object>) value);
|
||||
return getCollectionWithSingleElement(type, collectionComponentType, o);
|
||||
return (o != null) ? getCollectionWithSingleElement(type, collectionComponentType, o) : null;
|
||||
}
|
||||
return (T) read(type, (Map<String, Object>) value);
|
||||
} else {
|
||||
@@ -475,7 +501,7 @@ public class MappingElasticsearchConverter
|
||||
if (collectionComponentType != null
|
||||
&& collectionComponentType.isAssignableFrom(TypeInformation.of(value.getClass()))) {
|
||||
Object o = getPotentiallyConvertedSimpleRead(value, collectionComponentType);
|
||||
return getCollectionWithSingleElement(type, collectionComponentType, o);
|
||||
return (o != null) ? getCollectionWithSingleElement(type, collectionComponentType, o) : null;
|
||||
}
|
||||
|
||||
return (T) getPotentiallyConvertedSimpleRead(value, rawType);
|
||||
@@ -493,7 +519,7 @@ public class MappingElasticsearchConverter
|
||||
|
||||
/**
|
||||
* @param type the type to check
|
||||
* @return true if type is a collectoin, null otherwise,
|
||||
* @return the collection type if type is a collection, null otherwise,
|
||||
*/
|
||||
@Nullable
|
||||
TypeInformation<?> getCollectionComponentType(TypeInformation<?> type) {
|
||||
@@ -503,17 +529,15 @@ public class MappingElasticsearchConverter
|
||||
private Object propertyConverterRead(ElasticsearchPersistentProperty property, Object source) {
|
||||
PropertyValueConverter propertyValueConverter = Objects.requireNonNull(property.getPropertyValueConverter());
|
||||
|
||||
if (source instanceof String[]) {
|
||||
if (source instanceof String[] strings) {
|
||||
// convert to a List
|
||||
source = Arrays.asList((String[]) source);
|
||||
source = Arrays.asList(strings);
|
||||
}
|
||||
|
||||
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());
|
||||
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());
|
||||
} else {
|
||||
source = convertOnRead(propertyValueConverter, source);
|
||||
}
|
||||
@@ -611,9 +635,10 @@ public class MappingElasticsearchConverter
|
||||
* but will be removed from spring-data-commons, so we do it here
|
||||
*/
|
||||
@Nullable
|
||||
private Object convertFromCollectionToObject(Object value, @Nullable Class<?> target) {
|
||||
private Object convertFromCollectionToObject(Object value, Class<?> target) {
|
||||
|
||||
if (value.getClass().isArray()) {
|
||||
// noinspection ArraysAsListWithZeroOrOneArgument
|
||||
value = Arrays.asList(value);
|
||||
}
|
||||
|
||||
@@ -663,9 +688,9 @@ public class MappingElasticsearchConverter
|
||||
class ElasticsearchPropertyValueProvider implements PropertyValueProvider<ElasticsearchPersistentProperty> {
|
||||
|
||||
final MapValueAccessor accessor;
|
||||
final SpELExpressionEvaluator evaluator;
|
||||
final ValueExpressionEvaluator evaluator;
|
||||
|
||||
ElasticsearchPropertyValueProvider(MapValueAccessor accessor, SpELExpressionEvaluator evaluator) {
|
||||
ElasticsearchPropertyValueProvider(MapValueAccessor accessor, ValueExpressionEvaluator evaluator) {
|
||||
this.accessor = accessor;
|
||||
this.evaluator = evaluator;
|
||||
}
|
||||
@@ -685,33 +710,29 @@ public class MappingElasticsearchConverter
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension of {@link SpELExpressionParameterValueProvider} to recursively trigger value conversion on the raw
|
||||
* Extension of {@link ValueExpressionParameterValueProvider} to recursively trigger value conversion on the raw
|
||||
* resolved SpEL value.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
private class ConverterAwareSpELExpressionParameterValueProvider
|
||||
extends SpELExpressionParameterValueProvider<ElasticsearchPersistentProperty> {
|
||||
private class ConverterAwareValueExpressionParameterValueProvider
|
||||
extends ValueExpressionParameterValueProvider<ElasticsearchPersistentProperty> {
|
||||
|
||||
/**
|
||||
* Creates a new {@link ConverterAwareSpELExpressionParameterValueProvider}.
|
||||
* Creates a new {@link ConverterAwareValueExpressionParameterValueProvider}.
|
||||
*
|
||||
* @param evaluator must not be {@literal null}.
|
||||
* @param conversionService must not be {@literal null}.
|
||||
* @param delegate must not be {@literal null}.
|
||||
*/
|
||||
public ConverterAwareSpELExpressionParameterValueProvider(SpELExpressionEvaluator evaluator,
|
||||
public ConverterAwareValueExpressionParameterValueProvider(ValueExpressionEvaluator 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 potentiallyConvertSpelValue(Object object,
|
||||
protected <T> T potentiallyConvertExpressionValue(Object object,
|
||||
Parameter<T, ElasticsearchPersistentProperty> parameter) {
|
||||
return readValue(object, parameter.getType());
|
||||
}
|
||||
@@ -988,12 +1009,8 @@ public class MappingElasticsearchConverter
|
||||
|
||||
private static boolean hasEmptyValue(Object value) {
|
||||
|
||||
if (value instanceof String s && s.isEmpty() || value instanceof Collection<?> c && c.isEmpty()
|
||||
|| value instanceof Map<?, ?> m && m.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return value instanceof String s && s.isEmpty() || value instanceof Collection<?> c && c.isEmpty()
|
||||
|| value instanceof Map<?, ?> m && m.isEmpty();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -1395,12 +1412,18 @@ public class MappingElasticsearchConverter
|
||||
|
||||
if (properties.length > 1) {
|
||||
var persistentProperty = persistentEntity.getPersistentProperty(propertyName);
|
||||
return (persistentProperty != null)
|
||||
? fieldName + "." + updateFieldNames(properties[1], mappingContext.getPersistentEntity(persistentProperty))
|
||||
: fieldName;
|
||||
} else {
|
||||
return fieldName;
|
||||
|
||||
if (persistentProperty != null) {
|
||||
ElasticsearchPersistentEntity<?> nestedPersistentEntity = mappingContext
|
||||
.getPersistentEntity(persistentProperty);
|
||||
if (nestedPersistentEntity != null) {
|
||||
return fieldName + '.' + updateFieldNames(properties[1], nestedPersistentEntity);
|
||||
} else {
|
||||
return fieldName;
|
||||
}
|
||||
}
|
||||
}
|
||||
return fieldName;
|
||||
} else {
|
||||
return propertyPath;
|
||||
}
|
||||
@@ -1409,6 +1432,7 @@ public class MappingElasticsearchConverter
|
||||
|
||||
// endregion
|
||||
|
||||
@SuppressWarnings("ClassCanBeRecord")
|
||||
static class MapValueAccessor {
|
||||
|
||||
final Map<String, Object> target;
|
||||
|
||||
@@ -18,11 +18,7 @@ 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;
|
||||
@@ -30,12 +26,12 @@ import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A representation of a Elasticsearch document as extended {@link StringObjectMap Map}. All iterators preserve original
|
||||
* insertion order.
|
||||
* A representation of an 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 bei either mutable or immutable. In case a subclass is immutable, its methods may throw
|
||||
* Implementing classes can be either mutable or immutable. In case a subclass is immutable, its methods may throw
|
||||
* {@link UnsupportedOperationException} when calling modifying methods.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
@@ -60,7 +56,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, ? extends Object> map) {
|
||||
static Document from(Map<String, ?> map) {
|
||||
|
||||
Assert.notNull(map, "Map must not be null");
|
||||
|
||||
|
||||
+11
-1
@@ -20,6 +20,7 @@ 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;
|
||||
|
||||
@@ -27,6 +28,7 @@ import org.springframework.lang.Nullable;
|
||||
* This represents the complete search response from Elasticsearch, including the returned documents.
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Haibo Liu
|
||||
* @since 4.0
|
||||
*/
|
||||
public class SearchDocumentResponse {
|
||||
@@ -40,10 +42,12 @@ public class SearchDocumentResponse {
|
||||
@Nullable private final Suggest suggest;
|
||||
|
||||
@Nullable String pointInTimeId;
|
||||
@Nullable private final 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) {
|
||||
@Nullable AggregationsContainer<?> aggregationsContainer, @Nullable Suggest suggest,
|
||||
@Nullable SearchShardStatistics searchShardStatistics) {
|
||||
this.totalHits = totalHits;
|
||||
this.totalHitsRelation = totalHitsRelation;
|
||||
this.maxScore = maxScore;
|
||||
@@ -52,6 +56,7 @@ public class SearchDocumentResponse {
|
||||
this.searchDocuments = searchDocuments;
|
||||
this.aggregations = aggregationsContainer;
|
||||
this.suggest = suggest;
|
||||
this.searchShardStatistics = searchShardStatistics;
|
||||
}
|
||||
|
||||
public long getTotalHits() {
|
||||
@@ -93,6 +98,11 @@ 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 GeoPoint topLeft;
|
||||
private GeoPoint bottomRight;
|
||||
private final GeoPoint topLeft;
|
||||
private final GeoPoint bottomRight;
|
||||
|
||||
public GeoBox(GeoPoint topLeft, GeoPoint bottomRight) {
|
||||
this.topLeft = topLeft;
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ public class GeoJsonMultiPolygon implements GeoJson<Iterable<GeoJsonPolygon>> {
|
||||
|
||||
public static final String TYPE = "MultiPolygon";
|
||||
|
||||
private List<GeoJsonPolygon> coordinates = new ArrayList<>();
|
||||
private final List<GeoJsonPolygon> coordinates = new ArrayList<>();
|
||||
|
||||
private GeoJsonMultiPolygon(List<GeoJsonPolygon> polygons) {
|
||||
this.coordinates.addAll(polygons);
|
||||
|
||||
+14
-3
@@ -214,8 +214,9 @@ public class MappingBuilder {
|
||||
@Nullable Field parentFieldAnnotation, @Nullable Dynamic dynamicMapping, @Nullable Document runtimeFields)
|
||||
throws IOException {
|
||||
|
||||
if (entity != null && entity.isAnnotationPresent(Mapping.class)) {
|
||||
Mapping mappingAnnotation = entity.getRequiredAnnotation(Mapping.class);
|
||||
var mappingAnnotation = entity != null ? entity.findAnnotation(Mapping.class) : null;
|
||||
|
||||
if (mappingAnnotation != null) {
|
||||
|
||||
if (!mappingAnnotation.enabled()) {
|
||||
objectNode.put(MAPPING_ENABLED, false);
|
||||
@@ -289,6 +290,16 @@ 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -432,7 +443,7 @@ public class MappingBuilder {
|
||||
contextNode.put(FIELD_CONTEXT_NAME, context.name());
|
||||
contextNode.put(FIELD_CONTEXT_TYPE, context.type().getMappedName());
|
||||
|
||||
if (context.precision().length() > 0) {
|
||||
if (!context.precision().isEmpty()) {
|
||||
contextNode.put(FIELD_CONTEXT_PRECISION, context.precision());
|
||||
}
|
||||
|
||||
|
||||
+6
-4
@@ -84,6 +84,8 @@ public final class MappingParameters {
|
||||
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;
|
||||
@@ -169,8 +171,8 @@ public final class MappingParameters {
|
||||
positiveScoreImpact = field.positiveScoreImpact();
|
||||
dims = field.dims();
|
||||
if (type == FieldType.Dense_Vector) {
|
||||
Assert.isTrue(dims >= 1 && dims <= 2048,
|
||||
"Invalid required parameter! Dense_Vector value \"dims\" must be between 1 and 2048.");
|
||||
Assert.isTrue(dims >= 1 && dims <= 4096,
|
||||
"Invalid required parameter! Dense_Vector value \"dims\" must be between 1 and 4096.");
|
||||
}
|
||||
Assert.isTrue(field.enabled() || type == FieldType.Object, "enabled false is only allowed for field type object");
|
||||
enabled = field.enabled();
|
||||
@@ -212,8 +214,8 @@ public final class MappingParameters {
|
||||
positiveScoreImpact = field.positiveScoreImpact();
|
||||
dims = field.dims();
|
||||
if (type == FieldType.Dense_Vector) {
|
||||
Assert.isTrue(dims >= 1 && dims <= 2048,
|
||||
"Invalid required parameter! Dense_Vector value \"dims\" must be between 1 and 2048.");
|
||||
Assert.isTrue(dims >= 1 && dims <= 4096,
|
||||
"Invalid required parameter! Dense_Vector value \"dims\" must be between 1 and 4096.");
|
||||
}
|
||||
enabled = true;
|
||||
eagerGlobalOrdinals = field.eagerGlobalOrdinals();
|
||||
|
||||
@@ -17,7 +17,7 @@ package org.springframework.data.elasticsearch.core.join;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import org.springframework.data.annotation.PersistenceConstructor;
|
||||
import org.springframework.data.annotation.PersistenceCreator;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
@@ -39,7 +39,7 @@ public class JoinField<ID> {
|
||||
this(name, null);
|
||||
}
|
||||
|
||||
@PersistenceConstructor
|
||||
@PersistenceCreator
|
||||
public JoinField(String name, @Nullable ID parent) {
|
||||
this.name = name;
|
||||
this.parent = parent;
|
||||
|
||||
+2
-2
@@ -73,8 +73,8 @@ public class SimpleElasticsearchPersistentEntity<T> extends BasicPersistentEntit
|
||||
private @Nullable ElasticsearchPersistentProperty joinFieldProperty;
|
||||
private @Nullable ElasticsearchPersistentProperty indexedIndexNameProperty;
|
||||
private @Nullable Document.VersionType versionType;
|
||||
private boolean createIndexAndMapping;
|
||||
private boolean alwaysWriteMapping;
|
||||
private final boolean createIndexAndMapping;
|
||||
private final boolean alwaysWriteMapping;
|
||||
private final Dynamic dynamic;
|
||||
private final Map<String, ElasticsearchPersistentProperty> fieldNamePropertyCache = new ConcurrentHashMap<>();
|
||||
private final ConcurrentHashMap<String, Expression> routingExpressions = new ConcurrentHashMap<>();
|
||||
|
||||
@@ -230,11 +230,17 @@ 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,6 +316,9 @@ 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;
|
||||
}
|
||||
|
||||
@@ -670,8 +673,8 @@ public class Criteria {
|
||||
*/
|
||||
public Criteria boundedBy(String topLeftGeohash, String bottomRightGeohash) {
|
||||
|
||||
Assert.isTrue(!StringUtils.isEmpty(topLeftGeohash), "topLeftGeohash must not be empty");
|
||||
Assert.isTrue(!StringUtils.isEmpty(bottomRightGeohash), "bottomRightGeohash must not be empty");
|
||||
Assert.isTrue(StringUtils.hasLength(topLeftGeohash), "topLeftGeohash must not be empty");
|
||||
Assert.isTrue(StringUtils.hasLength(bottomRightGeohash), "bottomRightGeohash must not be empty");
|
||||
|
||||
filterCriteriaEntries
|
||||
.add(new CriteriaEntry(OperationKey.BBOX, new Object[] { topLeftGeohash, bottomRightGeohash }));
|
||||
@@ -754,7 +757,7 @@ public class Criteria {
|
||||
*/
|
||||
public Criteria within(String geoLocation, String distance) {
|
||||
|
||||
Assert.isTrue(!StringUtils.isEmpty(geoLocation), "geoLocation value must not be null");
|
||||
Assert.isTrue(StringUtils.hasLength(geoLocation), "geoLocation value must not be null");
|
||||
|
||||
filterCriteriaEntries.add(new CriteriaEntry(OperationKey.WITHIN, new Object[] { geoLocation, distance }));
|
||||
return this;
|
||||
@@ -813,6 +816,32 @@ 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
|
||||
@@ -974,7 +1003,11 @@ public class Criteria {
|
||||
/**
|
||||
* @since 5.1
|
||||
*/
|
||||
REGEXP;
|
||||
REGEXP,
|
||||
/**
|
||||
* @since 5.3
|
||||
*/
|
||||
HAS_CHILD, HAS_PARENT;
|
||||
|
||||
/**
|
||||
* @return true if this key does not have an associated value
|
||||
|
||||
@@ -0,0 +1,676 @@
|
||||
/*
|
||||
* 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 isn’t
|
||||
* 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
|
||||
* isn’t 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
/*
|
||||
* 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 document’s 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 document’s 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* 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 String indexName;
|
||||
private float boost;
|
||||
private final String indexName;
|
||||
private final float boost;
|
||||
|
||||
public IndexBoost(String indexName, float boost) {
|
||||
this.indexName = indexName;
|
||||
|
||||
@@ -25,8 +25,8 @@ import java.util.EnumSet;
|
||||
*/
|
||||
public class IndicesOptions {
|
||||
|
||||
private EnumSet<Option> options;
|
||||
private EnumSet<WildcardStates> expandWildcards;
|
||||
private final EnumSet<Option> options;
|
||||
private final 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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* 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,13 +15,12 @@
|
||||
*/
|
||||
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.
|
||||
*
|
||||
@@ -70,7 +69,7 @@ public class Order extends Sort.Order {
|
||||
|
||||
public Order(Sort.Direction direction, String property, Sort.NullHandling nullHandlingHint, @Nullable Mode mode,
|
||||
@Nullable String unmappedType) {
|
||||
this(direction, property, nullHandlingHint, null, unmappedType, null);
|
||||
this(direction, property, nullHandlingHint, mode, unmappedType, null);
|
||||
}
|
||||
|
||||
public Order(Sort.Direction direction, String property, Sort.NullHandling nullHandlingHint, @Nullable Mode mode,
|
||||
@@ -143,18 +142,19 @@ public class Order extends Sort.Order {
|
||||
}
|
||||
|
||||
public static class Nested {
|
||||
private String path;
|
||||
@Nullable private Query filter;
|
||||
private final String path;
|
||||
@Nullable private final Query filter;
|
||||
@Nullable private Integer maxChildren = null;
|
||||
@Nullable private Nested nested;
|
||||
@Nullable private final 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 String path;
|
||||
private final String path;
|
||||
@Nullable private Query filter = null;
|
||||
@Nullable private Integer maxChildren = null;
|
||||
@Nullable private Nested nested = null;
|
||||
@@ -203,8 +203,9 @@ 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.
|
||||
|
||||
@@ -447,7 +447,7 @@ public interface Query {
|
||||
@Nullable
|
||||
default PointInTime getPointInTime() {
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the number of documents that are requested when the reactive code does a batched search operation. This is
|
||||
@@ -469,7 +469,8 @@ public interface Query {
|
||||
/**
|
||||
* @since 5.1
|
||||
*/
|
||||
@Nullable EnumSet<IndicesOptions.WildcardStates> getExpandWildcards();
|
||||
@Nullable
|
||||
EnumSet<IndicesOptions.WildcardStates> getExpandWildcards();
|
||||
|
||||
/**
|
||||
* @return a possible empty list of docvalue_field values to be set on the query.
|
||||
|
||||
@@ -19,6 +19,8 @@ import org.springframework.data.elasticsearch.annotations.FieldType;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* The most trivial implementation of a Field. The {@link #name} is updatable, so it may be changed during query
|
||||
* preparation by the {@link org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter}.
|
||||
@@ -79,4 +81,16 @@ public class SimpleField implements Field {
|
||||
public String toString() {
|
||||
return getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof SimpleField that)) return false;
|
||||
return Objects.equals(name, that.name) && Objects.equals(fieldType, that.fieldType) && Objects.equals(path, that.path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(name, fieldType, path);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
public class UpdateResponse {
|
||||
|
||||
private Result result;
|
||||
private final Result result;
|
||||
|
||||
public UpdateResponse(Result result) {
|
||||
|
||||
@@ -47,6 +47,6 @@ public class UpdateResponse {
|
||||
}
|
||||
|
||||
public enum Result {
|
||||
CREATED, UPDATED, DELETED, NOT_FOUND, NOOP;
|
||||
CREATED, UPDATED, DELETED, NOT_FOUND, NOOP
|
||||
}
|
||||
}
|
||||
|
||||
+1
-40
@@ -15,14 +15,13 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core.query.highlight;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Haibo Liu
|
||||
* @since 4.3
|
||||
*/
|
||||
public class Highlight {
|
||||
@@ -57,42 +56,4 @@ public class Highlight {
|
||||
public List<HighlightField> getFields() {
|
||||
return fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link Highlight} from an Annotation instance.
|
||||
*
|
||||
* @param highlight must not be {@literal null}
|
||||
* @return highlight definition
|
||||
*/
|
||||
public static Highlight of(org.springframework.data.elasticsearch.annotations.Highlight highlight) {
|
||||
|
||||
Assert.notNull(highlight, "highlight must not be null");
|
||||
|
||||
org.springframework.data.elasticsearch.annotations.HighlightParameters parameters = highlight.parameters();
|
||||
HighlightParameters highlightParameters = HighlightParameters.builder() //
|
||||
.withBoundaryChars(parameters.boundaryChars()) //
|
||||
.withBoundaryMaxScan(parameters.boundaryMaxScan()) //
|
||||
.withBoundaryScanner(parameters.boundaryScanner()) //
|
||||
.withBoundaryScannerLocale(parameters.boundaryScannerLocale()) //
|
||||
.withEncoder(parameters.encoder()) //
|
||||
.withForceSource(parameters.forceSource()) //
|
||||
.withFragmenter(parameters.fragmenter()) //
|
||||
.withFragmentSize(parameters.fragmentSize()) //
|
||||
.withNoMatchSize(parameters.noMatchSize()) //
|
||||
.withNumberOfFragments(parameters.numberOfFragments()) //
|
||||
.withOrder(parameters.order()) //
|
||||
.withPhraseLimit(parameters.phraseLimit()) //
|
||||
.withPreTags(parameters.preTags()) //
|
||||
.withPostTags(parameters.postTags()) //
|
||||
.withRequireFieldMatch(parameters.requireFieldMatch()) //
|
||||
.withTagsSchema(parameters.tagsSchema()) //
|
||||
.withType(parameters.type()) //
|
||||
.build();
|
||||
|
||||
List<HighlightField> highlightFields = Arrays.stream(highlight.fields()) //
|
||||
.map(HighlightField::of) //
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return new Highlight(highlightParameters, highlightFields);
|
||||
}
|
||||
}
|
||||
|
||||
+19
@@ -15,10 +15,13 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core.query.highlight;
|
||||
|
||||
import org.springframework.data.elasticsearch.core.query.Query;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Haibo Liu
|
||||
* @since 4.3
|
||||
*/
|
||||
public abstract class HighlightCommonParameters {
|
||||
@@ -31,6 +34,7 @@ public abstract class HighlightCommonParameters {
|
||||
private final int fragmentSize;
|
||||
private final int noMatchSize;
|
||||
private final int numberOfFragments;
|
||||
@Nullable private final Query highlightQuery;
|
||||
private final String order;
|
||||
private final int phraseLimit;
|
||||
private final String[] preTags;
|
||||
@@ -51,6 +55,7 @@ public abstract class HighlightCommonParameters {
|
||||
fragmentSize = builder.fragmentSize;
|
||||
noMatchSize = builder.noMatchSize;
|
||||
numberOfFragments = builder.numberOfFragments;
|
||||
highlightQuery = builder.highlightQuery;
|
||||
order = builder.order;
|
||||
phraseLimit = builder.phraseLimit;
|
||||
preTags = builder.preTags;
|
||||
@@ -95,6 +100,11 @@ public abstract class HighlightCommonParameters {
|
||||
return numberOfFragments;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Query getHighlightQuery() {
|
||||
return highlightQuery;
|
||||
}
|
||||
|
||||
public String getOrder() {
|
||||
return order;
|
||||
}
|
||||
@@ -130,6 +140,10 @@ public abstract class HighlightCommonParameters {
|
||||
private int fragmentSize = -1;
|
||||
private int noMatchSize = -1;
|
||||
private int numberOfFragments = -1;
|
||||
/**
|
||||
* Only the search query part of the {@link Query} takes effect, others are just ignored.
|
||||
*/
|
||||
@Nullable private Query highlightQuery = null;
|
||||
private String order = "";
|
||||
private int phraseLimit = -1;
|
||||
private String[] preTags = new String[0];
|
||||
@@ -184,6 +198,11 @@ public abstract class HighlightCommonParameters {
|
||||
return (SELF) this;
|
||||
}
|
||||
|
||||
public SELF withHighlightQuery(@Nullable Query highlightQuery) {
|
||||
this.highlightQuery = highlightQuery;
|
||||
return (SELF) this;
|
||||
}
|
||||
|
||||
public SELF withOrder(String order) {
|
||||
this.order = order;
|
||||
return (SELF) this;
|
||||
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core.query.types;
|
||||
|
||||
/**
|
||||
* Define the types of conflicts that occur when a query encounters version conflicts.
|
||||
*
|
||||
* @author Aouichaoui Youssef
|
||||
* @since 5.3
|
||||
*/
|
||||
public enum ConflictsType {
|
||||
Abort, Proceed
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core.query.types;
|
||||
|
||||
/**
|
||||
* Define the default operator for a query string query.
|
||||
*
|
||||
* @author Aouichaoui Youssef
|
||||
* @since 5.3
|
||||
*/
|
||||
public enum OperatorType {
|
||||
And, Or
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
@org.springframework.lang.NonNullApi
|
||||
@org.springframework.lang.NonNullFields
|
||||
package org.springframework.data.elasticsearch.core.query.types;
|
||||
+3
-1
@@ -19,6 +19,7 @@ import java.lang.annotation.Annotation;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
@@ -41,6 +42,7 @@ import org.w3c.dom.Element;
|
||||
* @author Mohsin Husen
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
* @author Junghoon Ban
|
||||
*/
|
||||
public class ElasticsearchRepositoryConfigExtension extends RepositoryConfigurationExtensionSupport {
|
||||
|
||||
@@ -106,7 +108,7 @@ public class ElasticsearchRepositoryConfigExtension extends RepositoryConfigurat
|
||||
*/
|
||||
@Override
|
||||
protected Collection<Class<?>> getIdentifyingTypes() {
|
||||
return Arrays.asList(ElasticsearchRepository.class, ElasticsearchRepository.class);
|
||||
return List.of(ElasticsearchRepository.class);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
+17
-21
@@ -15,20 +15,18 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.repository.query;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.core.SearchHitSupport;
|
||||
import org.springframework.data.elasticsearch.core.SearchHits;
|
||||
import org.springframework.data.elasticsearch.core.SearchHitsImpl;
|
||||
import org.springframework.data.elasticsearch.core.TotalHitsRelation;
|
||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
import org.springframework.data.elasticsearch.core.query.BaseQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.DeleteQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.Query;
|
||||
import org.springframework.data.repository.query.ParametersParameterAccessor;
|
||||
import org.springframework.data.repository.query.QueryMethod;
|
||||
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
|
||||
import org.springframework.data.repository.query.RepositoryQuery;
|
||||
import org.springframework.data.repository.query.ResultProcessor;
|
||||
import org.springframework.data.util.StreamUtils;
|
||||
@@ -42,6 +40,7 @@ import org.springframework.util.ClassUtils;
|
||||
* @author Rizwan Idrees
|
||||
* @author Mohsin Husen
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Haibo Liu
|
||||
*/
|
||||
|
||||
public abstract class AbstractElasticsearchRepositoryQuery implements RepositoryQuery {
|
||||
@@ -50,12 +49,20 @@ public abstract class AbstractElasticsearchRepositoryQuery implements Repository
|
||||
protected ElasticsearchQueryMethod queryMethod;
|
||||
protected final ElasticsearchOperations elasticsearchOperations;
|
||||
protected final ElasticsearchConverter elasticsearchConverter;
|
||||
protected final QueryMethodEvaluationContextProvider evaluationContextProvider;
|
||||
|
||||
public AbstractElasticsearchRepositoryQuery(ElasticsearchQueryMethod queryMethod,
|
||||
ElasticsearchOperations elasticsearchOperations) {
|
||||
ElasticsearchOperations elasticsearchOperations,
|
||||
QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
|
||||
Assert.notNull(queryMethod, "queryMethod must not be null");
|
||||
Assert.notNull(elasticsearchOperations, "elasticsearchOperations must not be null");
|
||||
Assert.notNull(evaluationContextProvider, "evaluationContextProvider must not be null");
|
||||
|
||||
this.queryMethod = queryMethod;
|
||||
this.elasticsearchOperations = elasticsearchOperations;
|
||||
this.elasticsearchConverter = elasticsearchOperations.getElasticsearchConverter();
|
||||
this.evaluationContextProvider = evaluationContextProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -88,7 +95,7 @@ public abstract class AbstractElasticsearchRepositoryQuery implements Repository
|
||||
|
||||
if (isDeleteQuery()) {
|
||||
result = countOrGetDocumentsForDelete(query, parameterAccessor);
|
||||
elasticsearchOperations.delete(query, clazz, index);
|
||||
elasticsearchOperations.delete(DeleteQuery.builder(query).build(), clazz, index);
|
||||
elasticsearchOperations.indexOps(index).refresh();
|
||||
} else if (isCountQuery()) {
|
||||
result = elasticsearchOperations.count(query, clazz, index);
|
||||
@@ -107,24 +114,13 @@ public abstract class AbstractElasticsearchRepositoryQuery implements Repository
|
||||
: PageRequest.of(0, DEFAULT_STREAM_BATCH_SIZE));
|
||||
result = StreamUtils.createStreamFromIterator(elasticsearchOperations.searchForStream(query, clazz, index));
|
||||
} else if (queryMethod.isCollectionQuery()) {
|
||||
|
||||
if (parameterAccessor.getPageable().isUnpaged()) {
|
||||
int itemCount = (int) elasticsearchOperations.count(query, clazz, index);
|
||||
|
||||
if (itemCount == 0) {
|
||||
result = new SearchHitsImpl<>(0, TotalHitsRelation.EQUAL_TO, Float.NaN, null,
|
||||
query.getPointInTime() != null ? query.getPointInTime().id() : null, Collections.emptyList(), null, null);
|
||||
} else {
|
||||
query.setPageable(PageRequest.of(0, Math.max(1, itemCount)));
|
||||
}
|
||||
query.setPageable(PageRequest.of(0, Math.max(1, itemCount)));
|
||||
} else {
|
||||
query.setPageable(parameterAccessor.getPageable());
|
||||
}
|
||||
|
||||
if (result == null) {
|
||||
result = elasticsearchOperations.search(query, clazz, index);
|
||||
}
|
||||
|
||||
result = elasticsearchOperations.search(query, clazz, index);
|
||||
} else {
|
||||
result = elasticsearchOperations.searchOne(query, clazz, index);
|
||||
}
|
||||
@@ -137,12 +133,12 @@ public abstract class AbstractElasticsearchRepositoryQuery implements Repository
|
||||
public Query createQuery(Object[] parameters) {
|
||||
|
||||
ElasticsearchParametersParameterAccessor parameterAccessor = getParameterAccessor(parameters);
|
||||
ResultProcessor resultProcessor = queryMethod.getResultProcessor().withDynamicProjection(parameterAccessor);
|
||||
|
||||
var query = createQuery(parameterAccessor);
|
||||
Assert.notNull(query, "unsupported query");
|
||||
|
||||
queryMethod.addMethodParameter(query, parameterAccessor, elasticsearchOperations.getElasticsearchConverter());
|
||||
queryMethod.addMethodParameter(query, parameterAccessor, elasticsearchOperations.getElasticsearchConverter(),
|
||||
evaluationContextProvider);
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
+16
-4
@@ -28,12 +28,14 @@ import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersiste
|
||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
import org.springframework.data.elasticsearch.core.query.BaseQuery;
|
||||
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.repository.query.ReactiveElasticsearchQueryExecution.ResultProcessingConverter;
|
||||
import org.springframework.data.elasticsearch.repository.query.ReactiveElasticsearchQueryExecution.ResultProcessingExecution;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.repository.query.ParameterAccessor;
|
||||
import org.springframework.data.repository.query.QueryMethod;
|
||||
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
|
||||
import org.springframework.data.repository.query.RepositoryQuery;
|
||||
import org.springframework.data.repository.query.ResultProcessor;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -43,18 +45,26 @@ import org.springframework.util.Assert;
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Haibo Liu
|
||||
* @since 3.2
|
||||
*/
|
||||
abstract class AbstractReactiveElasticsearchRepositoryQuery implements RepositoryQuery {
|
||||
|
||||
protected final ReactiveElasticsearchQueryMethod queryMethod;
|
||||
private final ReactiveElasticsearchOperations elasticsearchOperations;
|
||||
protected final QueryMethodEvaluationContextProvider evaluationContextProvider;
|
||||
|
||||
AbstractReactiveElasticsearchRepositoryQuery(ReactiveElasticsearchQueryMethod queryMethod,
|
||||
ReactiveElasticsearchOperations elasticsearchOperations) {
|
||||
ReactiveElasticsearchOperations elasticsearchOperations,
|
||||
QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
|
||||
Assert.notNull(queryMethod, "queryMethod must not be null");
|
||||
Assert.notNull(elasticsearchOperations, "elasticsearchOperations must not be null");
|
||||
Assert.notNull(evaluationContextProvider, "evaluationContextProvider must not be null");
|
||||
|
||||
this.queryMethod = queryMethod;
|
||||
this.elasticsearchOperations = elasticsearchOperations;
|
||||
this.evaluationContextProvider = evaluationContextProvider;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -95,7 +105,8 @@ abstract class AbstractReactiveElasticsearchRepositoryQuery implements Repositor
|
||||
var query = createQuery(parameterAccessor);
|
||||
Assert.notNull(query, "unsupported query");
|
||||
|
||||
queryMethod.addMethodParameter(query, parameterAccessor, elasticsearchOperations.getElasticsearchConverter());
|
||||
queryMethod.addMethodParameter(query, parameterAccessor, elasticsearchOperations.getElasticsearchConverter(),
|
||||
evaluationContextProvider);
|
||||
|
||||
String indexName = queryMethod.getEntityInformation().getIndexName();
|
||||
IndexCoordinates index = IndexCoordinates.of(indexName);
|
||||
@@ -112,7 +123,7 @@ abstract class AbstractReactiveElasticsearchRepositoryQuery implements Repositor
|
||||
* @param accessor must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
protected abstract BaseQuery createQuery(ElasticsearchParameterAccessor accessor);
|
||||
protected abstract BaseQuery createQuery(ElasticsearchParametersParameterAccessor accessor);
|
||||
|
||||
private ReactiveElasticsearchQueryExecution getExecution(ElasticsearchParameterAccessor accessor,
|
||||
Converter<Object, Object> resultProcessing) {
|
||||
@@ -123,7 +134,8 @@ abstract class AbstractReactiveElasticsearchRepositoryQuery implements Repositor
|
||||
ReactiveElasticsearchOperations operations) {
|
||||
|
||||
if (isDeleteQuery()) {
|
||||
return (query, type, targetType, indexCoordinates) -> operations.delete(query, type, indexCoordinates)
|
||||
return (query, type, targetType, indexCoordinates) -> operations
|
||||
.delete(DeleteQuery.builder(query).build(), type, indexCoordinates)
|
||||
.map(ByQueryResponse::getDeleted);
|
||||
} else if (isCountQuery()) {
|
||||
return (query, type, targetType, indexCoordinates) -> operations.count(query, type, indexCoordinates);
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@ import org.springframework.data.util.TypeInformation;
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 5.2
|
||||
*/
|
||||
class ElasticsearchParameter extends Parameter {
|
||||
public class ElasticsearchParameter extends Parameter {
|
||||
|
||||
/**
|
||||
* Creates a new {@link ElasticsearchParameter}.
|
||||
|
||||
+6
-4
@@ -15,12 +15,12 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.repository.query;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.data.repository.query.Parameters;
|
||||
import org.springframework.data.repository.query.ParametersSource;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
|
||||
/**
|
||||
@@ -33,10 +33,13 @@ public class ElasticsearchParameters extends Parameters<ElasticsearchParameters,
|
||||
private final List<ElasticsearchParameter> scriptedFields = new ArrayList<>();
|
||||
private final List<ElasticsearchParameter> runtimeFields = new ArrayList<>();
|
||||
|
||||
public ElasticsearchParameters(Method method, TypeInformation<?> domainType) {
|
||||
public ElasticsearchParameters(ParametersSource parametersSource) {
|
||||
|
||||
super(method, parameter -> new ElasticsearchParameter(parameter, domainType));
|
||||
super(parametersSource,
|
||||
parameter -> new ElasticsearchParameter(parameter, parametersSource.getDomainTypeInformation()));
|
||||
|
||||
var domainType = parametersSource.getDomainTypeInformation();
|
||||
var method = parametersSource.getMethod();
|
||||
int parameterCount = method.getParameterCount();
|
||||
for (int i = 0; i < parameterCount; i++) {
|
||||
MethodParameter methodParameter = new MethodParameter(method, i);
|
||||
@@ -50,7 +53,6 @@ public class ElasticsearchParameters extends Parameters<ElasticsearchParameters,
|
||||
runtimeFields.add(parameter);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private ElasticsearchParameter parameterFactory(MethodParameter methodParameter, TypeInformation<?> domainType) {
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ import org.springframework.data.repository.query.ParametersParameterAccessor;
|
||||
* @author Christoph Strobl
|
||||
* @since 3.2
|
||||
*/
|
||||
class ElasticsearchParametersParameterAccessor extends ParametersParameterAccessor
|
||||
public class ElasticsearchParametersParameterAccessor extends ParametersParameterAccessor
|
||||
implements ElasticsearchParameterAccessor {
|
||||
|
||||
private final Object[] values;
|
||||
|
||||
+5
-5
@@ -18,11 +18,9 @@ package org.springframework.data.elasticsearch.repository.query;
|
||||
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
|
||||
import org.springframework.data.elasticsearch.core.query.BaseQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.Query;
|
||||
import org.springframework.data.elasticsearch.core.query.RuntimeField;
|
||||
import org.springframework.data.elasticsearch.core.query.ScriptedField;
|
||||
import org.springframework.data.elasticsearch.repository.query.parser.ElasticsearchQueryCreator;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
|
||||
import org.springframework.data.repository.query.parser.PartTree;
|
||||
|
||||
/**
|
||||
@@ -34,14 +32,16 @@ import org.springframework.data.repository.query.parser.PartTree;
|
||||
* @author Mark Paluch
|
||||
* @author Rasmus Faber-Espensen
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Haibo Liu
|
||||
*/
|
||||
public class ElasticsearchPartQuery extends AbstractElasticsearchRepositoryQuery {
|
||||
|
||||
private final PartTree tree;
|
||||
private final MappingContext<?, ElasticsearchPersistentProperty> mappingContext;
|
||||
|
||||
public ElasticsearchPartQuery(ElasticsearchQueryMethod method, ElasticsearchOperations elasticsearchOperations) {
|
||||
super(method, elasticsearchOperations);
|
||||
public ElasticsearchPartQuery(ElasticsearchQueryMethod method, ElasticsearchOperations elasticsearchOperations,
|
||||
QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
super(method, elasticsearchOperations, evaluationContextProvider);
|
||||
this.tree = new PartTree(queryMethod.getName(), queryMethod.getResultProcessor().getReturnedType().getDomainType());
|
||||
this.mappingContext = elasticsearchConverter.getMappingContext();
|
||||
}
|
||||
|
||||
+37
-31
@@ -24,6 +24,7 @@ import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.elasticsearch.annotations.Highlight;
|
||||
import org.springframework.data.elasticsearch.annotations.Query;
|
||||
@@ -41,16 +42,16 @@ import org.springframework.data.elasticsearch.core.query.HighlightQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.RuntimeField;
|
||||
import org.springframework.data.elasticsearch.core.query.ScriptedField;
|
||||
import org.springframework.data.elasticsearch.core.query.SourceFilter;
|
||||
import org.springframework.data.elasticsearch.repository.support.StringQueryUtil;
|
||||
import org.springframework.data.elasticsearch.repository.support.QueryStringProcessor;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.projection.ProjectionFactory;
|
||||
import org.springframework.data.repository.core.RepositoryMetadata;
|
||||
import org.springframework.data.repository.query.ParameterAccessor;
|
||||
import org.springframework.data.repository.query.Parameters;
|
||||
import org.springframework.data.repository.query.ParametersSource;
|
||||
import org.springframework.data.repository.query.QueryMethod;
|
||||
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
|
||||
import org.springframework.data.repository.util.QueryExecutionConverters;
|
||||
import org.springframework.data.repository.util.ReactiveWrapperConverters;
|
||||
import org.springframework.data.util.Lazy;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -66,12 +67,13 @@ import org.springframework.util.ClassUtils;
|
||||
* @author Christoph Strobl
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Alexander Torres
|
||||
* @author Haibo Liu
|
||||
*/
|
||||
public class ElasticsearchQueryMethod extends QueryMethod {
|
||||
|
||||
// the following 2 variables exits in the base class, but are private. We need them for
|
||||
// the following 2 variables exist in the base class, but are private. We need them for
|
||||
// correct handling of return types (SearchHits), so we have our own values here.
|
||||
// Alas this means that we have to copy code that initializes these variables and in the
|
||||
// This means that we have to copy code that initializes these variables and in the
|
||||
// base class uses them in order to use our variables
|
||||
protected final Method method;
|
||||
protected final Class<?> unwrappedReturnType;
|
||||
@@ -81,8 +83,6 @@ public class ElasticsearchQueryMethod extends QueryMethod {
|
||||
@Nullable private ElasticsearchEntityMetadata<?> metadata;
|
||||
@Nullable private final Query queryAnnotation;
|
||||
@Nullable private final Highlight highlightAnnotation;
|
||||
private final Lazy<HighlightQuery> highlightQueryLazy = Lazy.of(this::createAnnotatedHighlightQuery);
|
||||
|
||||
@Nullable private final SourceFilters sourceFilters;
|
||||
|
||||
public ElasticsearchQueryMethod(Method method, RepositoryMetadata repositoryMetadata, ProjectionFactory factory,
|
||||
@@ -102,9 +102,16 @@ public class ElasticsearchQueryMethod extends QueryMethod {
|
||||
verifyCountQueryTypes();
|
||||
}
|
||||
|
||||
@SuppressWarnings("removal")
|
||||
@Override
|
||||
@Deprecated
|
||||
protected Parameters<?, ?> createParameters(Method method, TypeInformation<?> domainType) {
|
||||
return new ElasticsearchParameters(method, domainType);
|
||||
return new ElasticsearchParameters(ParametersSource.of(method));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Parameters<?, ?> createParameters(ParametersSource parametersSource) {
|
||||
return new ElasticsearchParameters(parametersSource);
|
||||
}
|
||||
|
||||
protected void verifyCountQueryTypes() {
|
||||
@@ -143,20 +150,12 @@ public class ElasticsearchQueryMethod extends QueryMethod {
|
||||
* @throws IllegalArgumentException if no {@link Highlight} annotation is present on the method
|
||||
* @see #hasAnnotatedHighlight()
|
||||
*/
|
||||
public HighlightQuery getAnnotatedHighlightQuery() {
|
||||
public HighlightQuery getAnnotatedHighlightQuery(HighlightConverter highlightConverter) {
|
||||
|
||||
Assert.isTrue(hasAnnotatedHighlight(), "no Highlight annotation present on " + getName());
|
||||
|
||||
return highlightQueryLazy.get();
|
||||
}
|
||||
|
||||
private HighlightQuery createAnnotatedHighlightQuery() {
|
||||
|
||||
Assert.notNull(highlightAnnotation, "highlightAnnotation must not be null");
|
||||
|
||||
return new HighlightQuery(
|
||||
org.springframework.data.elasticsearch.core.query.highlight.Highlight.of(highlightAnnotation),
|
||||
getDomainClass());
|
||||
return new HighlightQuery(highlightConverter.convert(highlightAnnotation), getDomainClass());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -296,42 +295,46 @@ public class ElasticsearchQueryMethod extends QueryMethod {
|
||||
* @param parameterAccessor the accessor with the query method parameter details
|
||||
* @param converter {@link ElasticsearchConverter} needed to convert entity property names to the Elasticsearch field
|
||||
* names and for parameter conversion when the includes or excludes are defined as parameters
|
||||
* @param evaluationContextProvider to provide an evaluation context for SpEL evaluation
|
||||
* @return source filter with includes and excludes for a query, {@literal null} when no {@link SourceFilters}
|
||||
* annotation was set on the method.
|
||||
* @since 5.0
|
||||
*/
|
||||
@Nullable
|
||||
SourceFilter getSourceFilter(ParameterAccessor parameterAccessor, ElasticsearchConverter converter) {
|
||||
SourceFilter getSourceFilter(ElasticsearchParametersParameterAccessor parameterAccessor,
|
||||
ElasticsearchConverter converter,
|
||||
QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
|
||||
if (sourceFilters == null || (sourceFilters.includes().length == 0 && sourceFilters.excludes().length == 0)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
StringQueryUtil stringQueryUtil = new StringQueryUtil(converter.getConversionService());
|
||||
ConversionService conversionService = converter.getConversionService();
|
||||
FetchSourceFilterBuilder fetchSourceFilterBuilder = new FetchSourceFilterBuilder();
|
||||
|
||||
if (sourceFilters.includes().length > 0) {
|
||||
fetchSourceFilterBuilder
|
||||
.withIncludes(mapParameters(sourceFilters.includes(), parameterAccessor, stringQueryUtil));
|
||||
fetchSourceFilterBuilder.withIncludes(mapParameters(sourceFilters.includes(), parameterAccessor,
|
||||
conversionService, evaluationContextProvider));
|
||||
}
|
||||
|
||||
if (sourceFilters.excludes().length > 0) {
|
||||
fetchSourceFilterBuilder
|
||||
.withExcludes(mapParameters(sourceFilters.excludes(), parameterAccessor, stringQueryUtil));
|
||||
fetchSourceFilterBuilder.withExcludes(mapParameters(sourceFilters.excludes(), parameterAccessor,
|
||||
conversionService, evaluationContextProvider));
|
||||
}
|
||||
|
||||
return fetchSourceFilterBuilder.build();
|
||||
}
|
||||
|
||||
private String[] mapParameters(String[] source, ParameterAccessor parameterAccessor,
|
||||
StringQueryUtil stringQueryUtil) {
|
||||
private String[] mapParameters(String[] source, ElasticsearchParametersParameterAccessor parameterAccessor,
|
||||
ConversionService conversionService, QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
|
||||
List<String> fieldNames = new ArrayList<>();
|
||||
|
||||
for (String s : source) {
|
||||
|
||||
if (!s.isBlank()) {
|
||||
String fieldName = stringQueryUtil.replacePlaceholders(s, parameterAccessor);
|
||||
String fieldName = new QueryStringProcessor(s, this, conversionService, evaluationContextProvider)
|
||||
.createQuery(parameterAccessor);
|
||||
// this could be "[\"foo\",\"bar\"]", must be split
|
||||
if (fieldName.startsWith("[") && fieldName.endsWith("]")) {
|
||||
// noinspection RegExpRedundantEscape
|
||||
@@ -352,7 +355,7 @@ public class ElasticsearchQueryMethod extends QueryMethod {
|
||||
/*
|
||||
* Copied from the QueryMethod class adding support for collections of SearchHit instances. No static method here.
|
||||
*/
|
||||
private Class<? extends Object> potentiallyUnwrapReturnTypeFor(RepositoryMetadata metadata, Method method) {
|
||||
private Class<?> potentiallyUnwrapReturnTypeFor(RepositoryMetadata metadata, Method method) {
|
||||
TypeInformation<?> returnType = metadata.getReturnType(method);
|
||||
if (!QueryExecutionConverters.supports(returnType.getType())
|
||||
&& !ReactiveWrapperConverters.supports(returnType.getType())) {
|
||||
@@ -375,13 +378,16 @@ public class ElasticsearchQueryMethod extends QueryMethod {
|
||||
}
|
||||
|
||||
void addMethodParameter(BaseQuery query, ElasticsearchParametersParameterAccessor parameterAccessor,
|
||||
ElasticsearchConverter elasticsearchConverter) {
|
||||
ElasticsearchConverter elasticsearchConverter,
|
||||
QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
|
||||
if (hasAnnotatedHighlight()) {
|
||||
query.setHighlightQuery(getAnnotatedHighlightQuery());
|
||||
var highlightQuery = getAnnotatedHighlightQuery(new HighlightConverter(parameterAccessor,
|
||||
elasticsearchConverter.getConversionService(), evaluationContextProvider, this));
|
||||
query.setHighlightQuery(highlightQuery);
|
||||
}
|
||||
|
||||
var sourceFilter = getSourceFilter(parameterAccessor, elasticsearchConverter);
|
||||
var sourceFilter = getSourceFilter(parameterAccessor, elasticsearchConverter, evaluationContextProvider);
|
||||
if (sourceFilter != null) {
|
||||
query.addSourceFilter(sourceFilter);
|
||||
}
|
||||
|
||||
+15
-10
@@ -15,11 +15,12 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.repository.query;
|
||||
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.core.query.BaseQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.Query;
|
||||
import org.springframework.data.elasticsearch.core.query.StringQuery;
|
||||
import org.springframework.data.elasticsearch.repository.support.StringQueryUtil;
|
||||
import org.springframework.data.elasticsearch.repository.support.QueryStringProcessor;
|
||||
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -30,15 +31,19 @@ import org.springframework.util.Assert;
|
||||
* @author Mark Paluch
|
||||
* @author Taylor Ono
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Haibo Liu
|
||||
*/
|
||||
public class ElasticsearchStringQuery extends AbstractElasticsearchRepositoryQuery {
|
||||
|
||||
private final String queryString;
|
||||
|
||||
public ElasticsearchStringQuery(ElasticsearchQueryMethod queryMethod, ElasticsearchOperations elasticsearchOperations,
|
||||
String queryString) {
|
||||
super(queryMethod, elasticsearchOperations);
|
||||
String queryString, QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
super(queryMethod, elasticsearchOperations, evaluationContextProvider);
|
||||
|
||||
Assert.notNull(queryString, "Query cannot be empty");
|
||||
Assert.notNull(evaluationContextProvider, "ExpressionEvaluationContextProvider must not be null");
|
||||
|
||||
this.queryString = queryString;
|
||||
}
|
||||
|
||||
@@ -58,12 +63,12 @@ public class ElasticsearchStringQuery extends AbstractElasticsearchRepositoryQue
|
||||
}
|
||||
|
||||
protected BaseQuery createQuery(ElasticsearchParametersParameterAccessor parameterAccessor) {
|
||||
ConversionService conversionService = elasticsearchOperations.getElasticsearchConverter().getConversionService();
|
||||
var processed = new QueryStringProcessor(queryString, queryMethod, conversionService, evaluationContextProvider)
|
||||
.createQuery(parameterAccessor);
|
||||
|
||||
String queryString = new StringQueryUtil(elasticsearchOperations.getElasticsearchConverter().getConversionService())
|
||||
.replacePlaceholders(this.queryString, parameterAccessor);
|
||||
|
||||
var query = new StringQuery(queryString);
|
||||
query.addSort(parameterAccessor.getSort());
|
||||
return query;
|
||||
return new StringQuery(processed)
|
||||
.addSort(parameterAccessor.getSort());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* 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.repository.query;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.data.elasticsearch.core.query.Query;
|
||||
import org.springframework.data.elasticsearch.core.query.StringQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.highlight.Highlight;
|
||||
import org.springframework.data.elasticsearch.core.query.highlight.HighlightField;
|
||||
import org.springframework.data.elasticsearch.core.query.highlight.HighlightParameters;
|
||||
import org.springframework.data.elasticsearch.repository.support.QueryStringProcessor;
|
||||
import org.springframework.data.repository.query.QueryMethod;
|
||||
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Convert {@link org.springframework.data.elasticsearch.annotations.Highlight} to {@link Highlight}.
|
||||
*
|
||||
* @author Haibo Liu
|
||||
*/
|
||||
public class HighlightConverter {
|
||||
|
||||
private final ElasticsearchParametersParameterAccessor parameterAccessor;
|
||||
private final ConversionService conversionService;
|
||||
private final QueryMethodEvaluationContextProvider evaluationContextProvider;
|
||||
private final QueryMethod queryMethod;
|
||||
|
||||
HighlightConverter(ElasticsearchParametersParameterAccessor parameterAccessor,
|
||||
ConversionService conversionService,
|
||||
QueryMethodEvaluationContextProvider evaluationContextProvider,
|
||||
QueryMethod queryMethod) {
|
||||
|
||||
Assert.notNull(parameterAccessor, "parameterAccessor must not be null");
|
||||
Assert.notNull(conversionService, "conversionService must not be null");
|
||||
Assert.notNull(evaluationContextProvider, "evaluationContextProvider must not be null");
|
||||
Assert.notNull(queryMethod, "queryMethod must not be null");
|
||||
|
||||
this.parameterAccessor = parameterAccessor;
|
||||
this.conversionService = conversionService;
|
||||
this.evaluationContextProvider = evaluationContextProvider;
|
||||
this.queryMethod = queryMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link Highlight} from an Annotation instance.
|
||||
*
|
||||
* @param highlight must not be {@literal null}
|
||||
* @return highlight definition
|
||||
*/
|
||||
Highlight convert(org.springframework.data.elasticsearch.annotations.Highlight highlight) {
|
||||
|
||||
Assert.notNull(highlight, "highlight must not be null");
|
||||
|
||||
org.springframework.data.elasticsearch.annotations.HighlightParameters parameters = highlight.parameters();
|
||||
|
||||
// replace placeholders in highlight query with actual parameters
|
||||
Query highlightQuery = null;
|
||||
if (!parameters.highlightQuery().value().isEmpty()) {
|
||||
String rawQuery = parameters.highlightQuery().value();
|
||||
String query = new QueryStringProcessor(rawQuery, queryMethod, conversionService, evaluationContextProvider)
|
||||
.createQuery(parameterAccessor);
|
||||
highlightQuery = new StringQuery(query);
|
||||
}
|
||||
|
||||
HighlightParameters highlightParameters = HighlightParameters.builder() //
|
||||
.withBoundaryChars(parameters.boundaryChars()) //
|
||||
.withBoundaryMaxScan(parameters.boundaryMaxScan()) //
|
||||
.withBoundaryScanner(parameters.boundaryScanner()) //
|
||||
.withBoundaryScannerLocale(parameters.boundaryScannerLocale()) //
|
||||
.withEncoder(parameters.encoder()) //
|
||||
.withForceSource(parameters.forceSource()) //
|
||||
.withFragmenter(parameters.fragmenter()) //
|
||||
.withFragmentSize(parameters.fragmentSize()) //
|
||||
.withNoMatchSize(parameters.noMatchSize()) //
|
||||
.withNumberOfFragments(parameters.numberOfFragments()) //
|
||||
.withHighlightQuery(highlightQuery) //
|
||||
.withOrder(parameters.order()) //
|
||||
.withPhraseLimit(parameters.phraseLimit()) //
|
||||
.withPreTags(parameters.preTags()) //
|
||||
.withPostTags(parameters.postTags()) //
|
||||
.withRequireFieldMatch(parameters.requireFieldMatch()) //
|
||||
.withTagsSchema(parameters.tagsSchema()) //
|
||||
.withType(parameters.type()) //
|
||||
.build();
|
||||
|
||||
List<HighlightField> highlightFields = Arrays.stream(highlight.fields()) //
|
||||
.map(HighlightField::of) //
|
||||
.toList();
|
||||
|
||||
return new Highlight(highlightParameters, highlightFields);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -22,7 +22,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.repository.util.ReactiveWrapperConverters;
|
||||
import org.springframework.data.repository.util.ReactiveWrappers;
|
||||
import org.springframework.data.util.ReactiveWrappers;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
|
||||
-5
@@ -101,11 +101,6 @@ public class ReactiveElasticsearchQueryMethod extends ElasticsearchQueryMethod {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ElasticsearchParameters createParameters(Method method, TypeInformation<?> domainType) {
|
||||
return new ElasticsearchParameters(method, domainType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given {@link org.springframework.data.repository.query.QueryMethod} receives a reactive parameter
|
||||
* wrapper as one of its parameters.
|
||||
|
||||
+20
-13
@@ -15,42 +15,49 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.repository.query;
|
||||
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.core.query.BaseQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.StringQuery;
|
||||
import org.springframework.data.elasticsearch.repository.support.StringQueryUtil;
|
||||
import org.springframework.data.elasticsearch.repository.support.QueryStringProcessor;
|
||||
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @author Taylor Ono
|
||||
* @author Haibo Liu
|
||||
* @since 3.2
|
||||
*/
|
||||
public class ReactiveElasticsearchStringQuery extends AbstractReactiveElasticsearchRepositoryQuery {
|
||||
|
||||
private final String query;
|
||||
private final QueryMethodEvaluationContextProvider evaluationContextProvider;
|
||||
|
||||
public ReactiveElasticsearchStringQuery(ReactiveElasticsearchQueryMethod queryMethod,
|
||||
ReactiveElasticsearchOperations operations, SpelExpressionParser expressionParser,
|
||||
QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
ReactiveElasticsearchOperations operations, QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
|
||||
this(queryMethod.getAnnotatedQuery(), queryMethod, operations, expressionParser, evaluationContextProvider);
|
||||
this(queryMethod.getAnnotatedQuery(), queryMethod, operations, evaluationContextProvider);
|
||||
}
|
||||
|
||||
public ReactiveElasticsearchStringQuery(String query, ReactiveElasticsearchQueryMethod queryMethod,
|
||||
ReactiveElasticsearchOperations operations, SpelExpressionParser expressionParser,
|
||||
QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
ReactiveElasticsearchOperations operations, QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
super(queryMethod, operations, evaluationContextProvider);
|
||||
|
||||
Assert.notNull(query, "query must not be null");
|
||||
Assert.notNull(evaluationContextProvider, "evaluationContextProvider must not be null");
|
||||
|
||||
super(queryMethod, operations);
|
||||
this.query = query;
|
||||
this.evaluationContextProvider = evaluationContextProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected StringQuery createQuery(ElasticsearchParameterAccessor parameterAccessor) {
|
||||
String queryString = new StringQueryUtil(
|
||||
getElasticsearchOperations().getElasticsearchConverter().getConversionService()).replacePlaceholders(this.query,
|
||||
parameterAccessor);
|
||||
return new StringQuery(queryString);
|
||||
protected BaseQuery createQuery(ElasticsearchParametersParameterAccessor parameterAccessor) {
|
||||
ConversionService conversionService = getElasticsearchOperations().getElasticsearchConverter()
|
||||
.getConversionService();
|
||||
String processed = new QueryStringProcessor(query, queryMethod, conversionService, evaluationContextProvider)
|
||||
.createQuery(parameterAccessor);
|
||||
return new StringQuery(processed);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+6
-3
@@ -19,12 +19,14 @@ import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperatio
|
||||
import org.springframework.data.elasticsearch.core.query.BaseQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.CriteriaQuery;
|
||||
import org.springframework.data.elasticsearch.repository.query.parser.ElasticsearchQueryCreator;
|
||||
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
|
||||
import org.springframework.data.repository.query.ResultProcessor;
|
||||
import org.springframework.data.repository.query.parser.PartTree;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Haibo Liu
|
||||
* @since 3.2
|
||||
*/
|
||||
public class ReactivePartTreeElasticsearchQuery extends AbstractReactiveElasticsearchRepositoryQuery {
|
||||
@@ -32,15 +34,16 @@ public class ReactivePartTreeElasticsearchQuery extends AbstractReactiveElastics
|
||||
private final PartTree tree;
|
||||
|
||||
public ReactivePartTreeElasticsearchQuery(ReactiveElasticsearchQueryMethod queryMethod,
|
||||
ReactiveElasticsearchOperations elasticsearchOperations) {
|
||||
super(queryMethod, elasticsearchOperations);
|
||||
ReactiveElasticsearchOperations elasticsearchOperations,
|
||||
QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
super(queryMethod, elasticsearchOperations, evaluationContextProvider);
|
||||
|
||||
ResultProcessor processor = queryMethod.getResultProcessor();
|
||||
this.tree = new PartTree(queryMethod.getName(), processor.getReturnedType().getDomainType());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BaseQuery createQuery(ElasticsearchParameterAccessor accessor) {
|
||||
protected BaseQuery createQuery(ElasticsearchParametersParameterAccessor accessor) {
|
||||
CriteriaQuery query = new ElasticsearchQueryCreator(tree, accessor, getMappingContext()).createQuery();
|
||||
|
||||
if (tree.isLimiting()) {
|
||||
|
||||
+139
-144
@@ -15,9 +15,6 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.repository.query.parser;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.elasticsearch.core.geo.GeoBox;
|
||||
@@ -36,6 +33,9 @@ import org.springframework.data.repository.query.parser.Part;
|
||||
import org.springframework.data.repository.query.parser.PartTree;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* ElasticsearchQueryCreator
|
||||
*
|
||||
@@ -44,168 +44,163 @@ import org.springframework.lang.Nullable;
|
||||
* @author Franck Marchand
|
||||
* @author Artur Konczak
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Junghoon Ban
|
||||
*/
|
||||
public class ElasticsearchQueryCreator extends AbstractQueryCreator<CriteriaQuery, CriteriaQuery> {
|
||||
|
||||
private final MappingContext<?, ElasticsearchPersistentProperty> context;
|
||||
private final MappingContext<?, ElasticsearchPersistentProperty> context;
|
||||
|
||||
public ElasticsearchQueryCreator(PartTree tree, ParameterAccessor parameters,
|
||||
MappingContext<?, ElasticsearchPersistentProperty> context) {
|
||||
super(tree, parameters);
|
||||
this.context = context;
|
||||
}
|
||||
public ElasticsearchQueryCreator(PartTree tree, ParameterAccessor parameters,
|
||||
MappingContext<?, ElasticsearchPersistentProperty> context) {
|
||||
super(tree, parameters);
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public ElasticsearchQueryCreator(PartTree tree, MappingContext<?, ElasticsearchPersistentProperty> context) {
|
||||
super(tree);
|
||||
this.context = context;
|
||||
}
|
||||
public ElasticsearchQueryCreator(PartTree tree, MappingContext<?, ElasticsearchPersistentProperty> context) {
|
||||
super(tree);
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CriteriaQuery create(Part part, Iterator<Object> iterator) {
|
||||
PersistentPropertyPath<ElasticsearchPersistentProperty> path = context
|
||||
.getPersistentPropertyPath(part.getProperty());
|
||||
return new CriteriaQuery(from(part,
|
||||
new Criteria(path.toDotPath(ElasticsearchPersistentProperty.QueryPropertyToFieldNameConverter.INSTANCE)),
|
||||
iterator));
|
||||
}
|
||||
@Override
|
||||
protected CriteriaQuery create(Part part, Iterator<Object> iterator) {
|
||||
PersistentPropertyPath<ElasticsearchPersistentProperty> path = context.getPersistentPropertyPath(
|
||||
part.getProperty());
|
||||
return new CriteriaQuery(from(part,
|
||||
new Criteria(path.toDotPath(ElasticsearchPersistentProperty.QueryPropertyToFieldNameConverter.INSTANCE)),
|
||||
iterator));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CriteriaQuery and(Part part, CriteriaQuery base, Iterator<Object> iterator) {
|
||||
if (base == null) {
|
||||
return create(part, iterator);
|
||||
}
|
||||
PersistentPropertyPath<ElasticsearchPersistentProperty> path = context
|
||||
.getPersistentPropertyPath(part.getProperty());
|
||||
return base.addCriteria(from(part,
|
||||
new Criteria(path.toDotPath(ElasticsearchPersistentProperty.QueryPropertyToFieldNameConverter.INSTANCE)),
|
||||
iterator));
|
||||
}
|
||||
@Override
|
||||
protected CriteriaQuery and(Part part, CriteriaQuery base, Iterator<Object> iterator) {
|
||||
if (base == null) {
|
||||
return create(part, iterator);
|
||||
}
|
||||
PersistentPropertyPath<ElasticsearchPersistentProperty> path = context.getPersistentPropertyPath(
|
||||
part.getProperty());
|
||||
return base.addCriteria(from(part,
|
||||
new Criteria(path.toDotPath(ElasticsearchPersistentProperty.QueryPropertyToFieldNameConverter.INSTANCE)),
|
||||
iterator));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CriteriaQuery or(CriteriaQuery base, CriteriaQuery query) {
|
||||
return new CriteriaQuery(base.getCriteria().or(query.getCriteria()));
|
||||
}
|
||||
@Override
|
||||
protected CriteriaQuery or(CriteriaQuery base, CriteriaQuery query) {
|
||||
return new CriteriaQuery(base.getCriteria().or(query.getCriteria()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CriteriaQuery complete(@Nullable CriteriaQuery query, Sort sort) {
|
||||
@Override
|
||||
protected CriteriaQuery complete(@Nullable CriteriaQuery query, Sort sort) {
|
||||
|
||||
if (query == null) {
|
||||
// this is the case in a findAllByOrderByField method, add empty criteria
|
||||
query = new CriteriaQuery(new Criteria());
|
||||
}
|
||||
return query.addSort(sort);
|
||||
}
|
||||
if (query == null) {
|
||||
// this is the case in a findAllByOrderByField method, add empty criteria
|
||||
query = new CriteriaQuery(new Criteria());
|
||||
}
|
||||
return query.addSort(sort);
|
||||
}
|
||||
|
||||
private Criteria from(Part part, Criteria criteria, Iterator<?> parameters) {
|
||||
private Criteria from(Part part, Criteria criteria, Iterator<?> parameters) {
|
||||
|
||||
Part.Type type = part.getType();
|
||||
Part.Type type = part.getType();
|
||||
|
||||
switch (type) {
|
||||
case TRUE:
|
||||
return criteria.is(true);
|
||||
case FALSE:
|
||||
return criteria.is(false);
|
||||
case NEGATING_SIMPLE_PROPERTY:
|
||||
return criteria.is(parameters.next()).not();
|
||||
case REGEX:
|
||||
return criteria.expression(parameters.next().toString());
|
||||
case LIKE:
|
||||
case STARTING_WITH:
|
||||
return criteria.startsWith(parameters.next().toString());
|
||||
case ENDING_WITH:
|
||||
return criteria.endsWith(parameters.next().toString());
|
||||
case CONTAINING:
|
||||
return criteria.contains(parameters.next().toString());
|
||||
case GREATER_THAN:
|
||||
return criteria.greaterThan(parameters.next());
|
||||
case AFTER:
|
||||
case GREATER_THAN_EQUAL:
|
||||
return criteria.greaterThanEqual(parameters.next());
|
||||
case LESS_THAN:
|
||||
return criteria.lessThan(parameters.next());
|
||||
case BEFORE:
|
||||
case LESS_THAN_EQUAL:
|
||||
return criteria.lessThanEqual(parameters.next());
|
||||
case BETWEEN:
|
||||
return criteria.between(parameters.next(), parameters.next());
|
||||
case IN:
|
||||
return criteria.in(asArray(parameters.next()));
|
||||
case NOT_IN:
|
||||
return criteria.notIn(asArray(parameters.next()));
|
||||
case SIMPLE_PROPERTY:
|
||||
case WITHIN: {
|
||||
Object firstParameter = parameters.next();
|
||||
Object secondParameter = null;
|
||||
if (type == Part.Type.SIMPLE_PROPERTY) {
|
||||
if (part.getProperty().getType() != GeoPoint.class) {
|
||||
if (firstParameter != null) {
|
||||
return criteria.is(firstParameter);
|
||||
} else {
|
||||
// searching for null is a must_not (exists)
|
||||
return criteria.exists().not();
|
||||
}
|
||||
} else {
|
||||
// it means it's a simple find with exact geopoint matching (e.g. findByLocation)
|
||||
// and because Elasticsearch does not have any kind of query with just a geopoint
|
||||
// as argument we use a "geo distance" query with a distance of one meter.
|
||||
secondParameter = ".001km";
|
||||
}
|
||||
} else {
|
||||
secondParameter = parameters.next();
|
||||
}
|
||||
return switch (type) {
|
||||
case TRUE -> criteria.is(true);
|
||||
case FALSE -> criteria.is(false);
|
||||
case NEGATING_SIMPLE_PROPERTY -> criteria.is(parameters.next()).not();
|
||||
case REGEX -> criteria.expression(parameters.next().toString());
|
||||
case LIKE, STARTING_WITH -> criteria.startsWith(parameters.next().toString());
|
||||
case ENDING_WITH -> criteria.endsWith(parameters.next().toString());
|
||||
case CONTAINING -> criteria.contains(parameters.next().toString());
|
||||
case GREATER_THAN -> criteria.greaterThan(parameters.next());
|
||||
case AFTER, GREATER_THAN_EQUAL -> criteria.greaterThanEqual(parameters.next());
|
||||
case LESS_THAN -> criteria.lessThan(parameters.next());
|
||||
case BEFORE, LESS_THAN_EQUAL -> criteria.lessThanEqual(parameters.next());
|
||||
case BETWEEN -> criteria.between(parameters.next(), parameters.next());
|
||||
case IN -> criteria.in(asArray(parameters.next()));
|
||||
case NOT_IN -> criteria.notIn(asArray(parameters.next()));
|
||||
case SIMPLE_PROPERTY, WITHIN -> within(part, criteria, parameters);
|
||||
case NEAR -> near(criteria, parameters);
|
||||
case EXISTS, IS_NOT_NULL -> criteria.exists();
|
||||
case IS_NULL -> criteria.not().exists();
|
||||
case IS_EMPTY -> criteria.empty();
|
||||
case IS_NOT_EMPTY -> criteria.notEmpty();
|
||||
default -> throw new InvalidDataAccessApiUsageException("Illegal criteria found '" + type + "'.");
|
||||
};
|
||||
}
|
||||
|
||||
if (firstParameter instanceof GeoPoint && secondParameter instanceof String)
|
||||
return criteria.within((GeoPoint) firstParameter, (String) secondParameter);
|
||||
private Criteria within(Part part, Criteria criteria, Iterator<?> parameters) {
|
||||
|
||||
if (firstParameter instanceof Point && secondParameter instanceof Distance)
|
||||
return criteria.within((Point) firstParameter, (Distance) secondParameter);
|
||||
Object firstParameter = parameters.next();
|
||||
Object secondParameter;
|
||||
|
||||
if (firstParameter instanceof String && secondParameter instanceof String)
|
||||
return criteria.within((String) firstParameter, (String) secondParameter);
|
||||
}
|
||||
case NEAR: {
|
||||
Object firstParameter = parameters.next();
|
||||
if (part.getType() == Part.Type.SIMPLE_PROPERTY) {
|
||||
if (part.getProperty().getType() != GeoPoint.class) {
|
||||
if (firstParameter != null) {
|
||||
return criteria.is(firstParameter);
|
||||
} else {
|
||||
// searching for null is a must_not (exists)
|
||||
return criteria.exists().not();
|
||||
}
|
||||
} else {
|
||||
// it means it's a simple find with exact geopoint matching (e.g. findByLocation)
|
||||
// and because Elasticsearch does not have any kind of query with just a geopoint
|
||||
// as argument we use a "geo distance" query with a distance of one meter.
|
||||
secondParameter = ".001km";
|
||||
}
|
||||
} else {
|
||||
secondParameter = parameters.next();
|
||||
}
|
||||
|
||||
if (firstParameter instanceof GeoBox) {
|
||||
return criteria.boundedBy((GeoBox) firstParameter);
|
||||
}
|
||||
return doWithinIfPossible(criteria, firstParameter, secondParameter);
|
||||
}
|
||||
|
||||
if (firstParameter instanceof Box) {
|
||||
return criteria.boundedBy(GeoBox.fromBox((Box) firstParameter));
|
||||
}
|
||||
private Criteria near(Criteria criteria, Iterator<?> parameters) {
|
||||
|
||||
Object secondParameter = parameters.next();
|
||||
Object firstParameter = parameters.next();
|
||||
|
||||
// "near" query can be the same query as the "within" query
|
||||
if (firstParameter instanceof GeoPoint && secondParameter instanceof String)
|
||||
return criteria.within((GeoPoint) firstParameter, (String) secondParameter);
|
||||
if (firstParameter instanceof GeoBox geoBox) {
|
||||
return criteria.boundedBy(geoBox);
|
||||
}
|
||||
|
||||
if (firstParameter instanceof Point && secondParameter instanceof Distance)
|
||||
return criteria.within((Point) firstParameter, (Distance) secondParameter);
|
||||
if (firstParameter instanceof Box box) {
|
||||
return criteria.boundedBy(GeoBox.fromBox(box));
|
||||
}
|
||||
|
||||
if (firstParameter instanceof String && secondParameter instanceof String)
|
||||
return criteria.within((String) firstParameter, (String) secondParameter);
|
||||
}
|
||||
case EXISTS:
|
||||
case IS_NOT_NULL:
|
||||
return criteria.exists();
|
||||
case IS_NULL:
|
||||
return criteria.not().exists();
|
||||
case IS_EMPTY:
|
||||
return criteria.empty();
|
||||
case IS_NOT_EMPTY:
|
||||
return criteria.notEmpty();
|
||||
default:
|
||||
throw new InvalidDataAccessApiUsageException("Illegal criteria found '" + type + "'.");
|
||||
}
|
||||
}
|
||||
Object secondParameter = parameters.next();
|
||||
|
||||
private Object[] asArray(Object o) {
|
||||
if (o instanceof Collection) {
|
||||
return ((Collection<?>) o).toArray();
|
||||
} else if (o.getClass().isArray()) {
|
||||
return (Object[]) o;
|
||||
}
|
||||
return new Object[] { o };
|
||||
}
|
||||
return doWithinIfPossible(criteria, firstParameter, secondParameter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Do a within query if possible, otherwise return the criteria unchanged.
|
||||
*
|
||||
* @param criteria must not be {@literal null}
|
||||
* @param firstParameter must not be {@literal null}
|
||||
* @param secondParameter must not be {@literal null}
|
||||
* @return the criteria with the within query applied if possible.
|
||||
* @author Junghoon Ban
|
||||
*/
|
||||
private Criteria doWithinIfPossible(Criteria criteria, Object firstParameter, Object secondParameter) {
|
||||
|
||||
if (firstParameter instanceof GeoPoint geoPoint && secondParameter instanceof String string) {
|
||||
return criteria.within(geoPoint, string);
|
||||
}
|
||||
|
||||
if (firstParameter instanceof Point point && secondParameter instanceof Distance distance) {
|
||||
return criteria.within(point, distance);
|
||||
}
|
||||
|
||||
if (firstParameter instanceof String firstString && secondParameter instanceof String secondString) {
|
||||
return criteria.within(firstString, secondString);
|
||||
}
|
||||
|
||||
return criteria;
|
||||
}
|
||||
|
||||
private Object[] asArray(Object o) {
|
||||
if (o instanceof Collection) {
|
||||
return ((Collection<?>) o).toArray();
|
||||
} else if (o.getClass().isArray()) {
|
||||
return (Object[]) o;
|
||||
}
|
||||
return new Object[]{o};
|
||||
}
|
||||
}
|
||||
|
||||
+18
-9
@@ -15,6 +15,11 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.repository.support;
|
||||
|
||||
import static org.springframework.data.querydsl.QuerydslUtils.*;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
|
||||
import org.springframework.data.elasticsearch.repository.query.ElasticsearchPartQuery;
|
||||
@@ -37,11 +42,6 @@ import org.springframework.data.repository.query.RepositoryQuery;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.springframework.data.querydsl.QuerydslUtils.QUERY_DSL_PRESENT;
|
||||
|
||||
/**
|
||||
* Factory to create {@link ElasticsearchRepository}
|
||||
*
|
||||
@@ -54,6 +54,7 @@ import static org.springframework.data.querydsl.QuerydslUtils.QUERY_DSL_PRESENT;
|
||||
* @author Sascha Woo
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Ezequiel Antúnez Camacho
|
||||
* @author Haibo Liu
|
||||
*/
|
||||
public class ElasticsearchRepositoryFactory extends RepositoryFactorySupport {
|
||||
|
||||
@@ -96,11 +97,17 @@ public class ElasticsearchRepositoryFactory extends RepositoryFactorySupport {
|
||||
@Override
|
||||
protected Optional<QueryLookupStrategy> getQueryLookupStrategy(@Nullable Key key,
|
||||
QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
return Optional.of(new ElasticsearchQueryLookupStrategy());
|
||||
return Optional.of(new ElasticsearchQueryLookupStrategy(evaluationContextProvider));
|
||||
}
|
||||
|
||||
private class ElasticsearchQueryLookupStrategy implements QueryLookupStrategy {
|
||||
|
||||
private final QueryMethodEvaluationContextProvider evaluationContextProvider;
|
||||
|
||||
ElasticsearchQueryLookupStrategy(QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
this.evaluationContextProvider = evaluationContextProvider;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.QueryLookupStrategy#resolveQuery(java.lang.reflect.Method, org.springframework.data.repository.core.RepositoryMetadata, org.springframework.data.projection.ProjectionFactory, org.springframework.data.repository.core.NamedQueries)
|
||||
@@ -115,11 +122,13 @@ public class ElasticsearchRepositoryFactory extends RepositoryFactorySupport {
|
||||
|
||||
if (namedQueries.hasQuery(namedQueryName)) {
|
||||
String namedQuery = namedQueries.getQuery(namedQueryName);
|
||||
return new ElasticsearchStringQuery(queryMethod, elasticsearchOperations, namedQuery);
|
||||
return new ElasticsearchStringQuery(queryMethod, elasticsearchOperations, namedQuery,
|
||||
evaluationContextProvider);
|
||||
} else if (queryMethod.hasAnnotatedQuery()) {
|
||||
return new ElasticsearchStringQuery(queryMethod, elasticsearchOperations, queryMethod.getAnnotatedQuery());
|
||||
return new ElasticsearchStringQuery(queryMethod, elasticsearchOperations, queryMethod.getAnnotatedQuery(),
|
||||
evaluationContextProvider);
|
||||
}
|
||||
return new ElasticsearchPartQuery(queryMethod, elasticsearchOperations);
|
||||
return new ElasticsearchPartQuery(queryMethod, elasticsearchOperations, evaluationContextProvider);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+28
-44
@@ -15,38 +15,52 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.repository.support;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.data.elasticsearch.core.convert.ConversionException;
|
||||
import org.springframework.data.elasticsearch.repository.support.value.ElasticsearchQueryValueConversionService;
|
||||
import org.springframework.data.repository.query.ParameterAccessor;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.NumberUtils;
|
||||
|
||||
/**
|
||||
* To replace the placeholders like `?0`, `?1, `?2` of the query string.
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Niklas Herder
|
||||
* @author Haibo Liu
|
||||
*/
|
||||
final public class StringQueryUtil {
|
||||
final public class QueryStringPlaceholderReplacer {
|
||||
|
||||
private static final Pattern PARAMETER_PLACEHOLDER = Pattern.compile("\\?(\\d+)");
|
||||
|
||||
private final ConversionService conversionService;
|
||||
|
||||
public StringQueryUtil(ConversionService conversionService) {
|
||||
this.conversionService = conversionService;
|
||||
public QueryStringPlaceholderReplacer(ConversionService conversionService) {
|
||||
|
||||
Assert.notNull(conversionService, "conversionService must not be null");
|
||||
|
||||
this.conversionService = ElasticsearchQueryValueConversionService.getInstance(conversionService);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the placeholders of the query string.
|
||||
*
|
||||
* @param input raw query string
|
||||
* @param accessor parameter info
|
||||
* @return a plain string with placeholders replaced
|
||||
*/
|
||||
public String replacePlaceholders(String input, ParameterAccessor accessor) {
|
||||
|
||||
Matcher matcher = PARAMETER_PLACEHOLDER.matcher(input);
|
||||
String result = input;
|
||||
while (matcher.find()) {
|
||||
|
||||
while (matcher.find()) {
|
||||
String placeholder = Pattern.quote(matcher.group()) + "(?!\\d+)";
|
||||
int index = NumberUtils.parseNumber(matcher.group(1), Integer.class);
|
||||
String replacement = Matcher.quoteReplacement(getParameterWithIndex(accessor, index));
|
||||
String replacement = Matcher.quoteReplacement(getParameterWithIndex(accessor, index, input));
|
||||
result = result.replaceAll(placeholder, replacement);
|
||||
// need to escape backslashes that are not escapes for quotes so that they are sent as double-backslashes
|
||||
// to Elasticsearch
|
||||
@@ -55,47 +69,17 @@ final public class StringQueryUtil {
|
||||
return result;
|
||||
}
|
||||
|
||||
private String getParameterWithIndex(ParameterAccessor accessor, int index) {
|
||||
private String getParameterWithIndex(ParameterAccessor accessor, int index, String input) {
|
||||
|
||||
Object parameter = accessor.getBindableValue(index);
|
||||
String parameterValue = "null";
|
||||
String value = conversionService.convert(parameter, String.class);
|
||||
|
||||
if (parameter != null) {
|
||||
parameterValue = convert(parameter);
|
||||
}
|
||||
|
||||
return parameterValue;
|
||||
|
||||
}
|
||||
|
||||
private String convert(Object parameter) {
|
||||
if (Collection.class.isAssignableFrom(parameter.getClass())) {
|
||||
Collection<?> collectionParam = (Collection<?>) parameter;
|
||||
StringBuilder sb = new StringBuilder("[");
|
||||
sb.append(collectionParam.stream().map(o -> {
|
||||
if (o instanceof String) {
|
||||
return "\"" + convert(o) + "\"";
|
||||
} else {
|
||||
return convert(o);
|
||||
}
|
||||
}).collect(Collectors.joining(",")));
|
||||
sb.append("]");
|
||||
return sb.toString();
|
||||
} else {
|
||||
String parameterValue = "null";
|
||||
if (conversionService.canConvert(parameter.getClass(), String.class)) {
|
||||
String converted = conversionService.convert(parameter, String.class);
|
||||
|
||||
if (converted != null) {
|
||||
parameterValue = converted;
|
||||
}
|
||||
} else {
|
||||
parameterValue = parameter.toString();
|
||||
}
|
||||
|
||||
parameterValue = parameterValue.replaceAll("\"", Matcher.quoteReplacement("\\\""));
|
||||
return parameterValue;
|
||||
if (value == null) {
|
||||
throw new ConversionException(String.format(
|
||||
"Parameter value can't be null for placeholder at index '%s' in query '%s' when querying elasticsearch",
|
||||
index, input));
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user