Compare commits

...

73 Commits
main ... 4.3.10

Author SHA1 Message Date
Mark Paluch
24195361d9
Release version 4.3.10 (2021.1.10).
See #2333
2022-11-18 10:22:10 +01:00
Mark Paluch
3cf8783e44
Prepare 4.3.10 (2021.1.10).
See #2333
2022-11-18 10:21:07 +01:00
Tiny
831b12a686
Update Dynamic.java
Original Pull Request #2357
Closes #2359

(cherry picked from commit f8ddf16c0cb85c8a5ce1427600f083720f61c914)
(cherry picked from commit a1094527a9e12a6a711e502aa2f65f285805de8e)
2022-11-10 20:38:54 +01:00
Spring Builds
79813a428b
After release cleanups.
See #2331
2022-10-13 12:37:12 +00:00
Spring Builds
bb33ab299d
Prepare next development iteration.
See #2331
2022-10-13 12:36:59 +00:00
Spring Builds
9e85d0f27c
Release version 4.3.9 (2021.1.9).
See #2331
2022-10-13 11:54:22 +00:00
Spring Builds
535cc84df5
Prepare 4.3.9 (2021.1.9).
See #2331
2022-10-13 11:51:47 +00:00
Spring Builds
3647fc6463
After release cleanups.
See #2297
2022-10-13 07:52:13 +00:00
Spring Builds
bdb7ca03c6
Prepare next development iteration.
See #2297
2022-10-13 07:52:00 +00:00
Spring Builds
200a18b4b1
Release version 4.3.8 (2021.1.8).
See #2297
2022-10-13 07:28:26 +00:00
Spring Builds
d0f7f79968
Prepare 4.3.8 (2021.1.8).
See #2297
2022-10-13 07:26:04 +00:00
Peter-Josef Meisch
027ff29436
Escape backslash in StringQuery.
Original Pull Request
Closes #2326

(cherry picked from commit 03ecc48b09e42a041c9f04bc47801b36adf91306)
(cherry picked from commit 2fa15f772a294edc59dc79b2bb2ae18328f357b3)
2022-10-11 22:49:30 +02:00
Spring Builds
69d80bd602
After release cleanups.
See #2220
2022-09-19 09:04:59 +00:00
Spring Builds
f7a788ce2d
Prepare next development iteration.
See #2220
2022-09-19 09:04:47 +00:00
Spring Builds
230616a3c3
Release version 4.3.7 (2021.1.7).
See #2220
2022-09-19 08:40:25 +00:00
Spring Builds
44ae2b2af5
Prepare 4.3.7 (2021.1.7).
See #2220
2022-09-19 08:38:11 +00:00
Peter-Josef Meisch
e934a2eea8
Fix mapping of property values into a collection.
When reading from Elasticsearch into a property of type Collection<T> (List<T> or Set<T>) the MappingElasticsearchConverter now can read both from the returned JSON:

    an array of T objects - will put the objects in a corresponding collection
    a single T object will put the single object into a corrsponding colletcion

This is implemented and tested for both: entities where the properties have setters and immutable classes that only provide an all-args constructor.

Original Pull Request #2282
Closes #2280

(cherry picked from commit 86634ceb38dbc9d1a3985171a4bf12a6d5912612)
(cherry picked from commit 346c5cce580fc876162435c613cedcd3f62313e4)
2022-08-29 21:22:12 +02:00
Peter-Josef Meisch
468b573b77
Fix update call in reactive client (Elasticsearch 7 client)
Original Pull Request #2281
Closes #2276

(cherry picked from commit 8377f64a8a919b31c37f337fc92c3ea8ee9154d8)
(cherry picked from commit a3ebd8be781b896afbb428422273b5e82d2c74f2)
2022-08-26 08:41:26 +02:00
Peter-Josef Meisch
9a442df5b9
Fix exists query for imperative repository implementation.
Original Pull Request #2236
Closes #2162

(cherry picked from commit 373be49f97fe333714d29ce5e78ace38d7b0354f)
(cherry picked from commit be70a398be3e8788f3ab11d476811f9aa6802e9c)
2022-07-22 22:32:39 +02:00
Mark Paluch
7b96041bc3
Refine CI triggers.
See #2220
2022-07-18 09:18:28 +02:00
Christoph Strobl
ac73ba4774
After release cleanups.
See #2185
2022-07-15 10:47:37 +02:00
Christoph Strobl
fa7a285606
Prepare next development iteration.
See #2185
2022-07-15 10:47:34 +02:00
Christoph Strobl
939179ced4
Release version 4.3.6 (2021.1.6).
See #2185
2022-07-15 10:34:18 +02:00
Christoph Strobl
a45281b1fd
Prepare 4.3.6 (2021.1.6).
See #2185
2022-07-15 10:33:40 +02:00
diamondT
474ff84d6e Fix handling of array-of-strings parameters for @Query-annotated queries.
Original Pull Request  #2182
Closes #2135

(cherry picked from commit 259c43af19db269476d31bfe5df5554807b7b2f9)
(cherry picked from commit aa4aecacdf0596578577461af5129cf12c9a5c7b)
2022-07-12 20:57:48 +02:00
Peter-Josef Meisch
c58e3b3a8f
Fix updatebyquery request.
Original Pull Request #2197
Closes #2191

(cherry picked from commit f90138076659e5452fd38444c85cfee0745b074c)
(cherry picked from commit ae66cbd619b012b898072ef059de71ac1626b330)
2022-06-25 20:11:03 +02:00
Mark Paluch
4ed7f96919
After release cleanups.
See #2141
2022-06-20 11:12:23 +02:00
Mark Paluch
776a30af75
Prepare next development iteration.
See #2141
2022-06-20 11:12:20 +02:00
Mark Paluch
c92480b97d
Release version 4.3.5 (2021.1.5).
See #2141
2022-06-20 10:59:11 +02:00
Mark Paluch
6e8357caa7
Prepare 4.3.5 (2021.1.5).
See #2141
2022-06-20 10:58:45 +02:00
Mark Paluch
541faff07f
Upgrade to Maven Wrapper 3.8.5.
See #2179
2022-06-03 09:42:29 +02:00
Mark Paluch
9aeac006d1
Update CI properties.
See #2141
2022-06-03 09:35:44 +02:00
panzhenchao
6e52b97b76
Fix incorrect argument check asserts.
Original Pull Request #2169
Closes #2170

(cherry picked from commit c826adb152fb1b00b49f9a8b69db8f969b9ba486)
(cherry picked from commit 7efd4b3be7ed5ebffe05c3722627559d1b57956c)
2022-05-27 20:42:28 +02:00
Christoph Strobl
c7550e8d82
After release cleanups.
See #2119
2022-04-19 12:13:25 +02:00
Christoph Strobl
c17159ce1c
Prepare next development iteration.
See #2119
2022-04-19 12:13:22 +02:00
Christoph Strobl
0a1d10f8b4
Release version 4.3.4 (2021.1.4).
See #2119
2022-04-19 12:03:16 +02:00
Christoph Strobl
ef0e47c6bb
Prepare 4.3.4 (2021.1.4).
See #2119
2022-04-19 12:02:46 +02:00
Mark Paluch
7e2ebb299c
After release cleanups.
See #2091
2022-03-21 15:06:38 +01:00
Mark Paluch
594566a44a
Prepare next development iteration.
See #2091
2022-03-21 15:06:36 +01:00
Mark Paluch
bf3248b536
Release version 4.3.3 (2021.1.3).
See #2091
2022-03-21 14:58:52 +01:00
Mark Paluch
c59bb0b434
Prepare 4.3.3 (2021.1.3).
See #2091
2022-03-21 14:58:28 +01:00
Peter-Josef Meisch
d93d1e0738
Set visibility SimpleElasticsearchPersistentEntity.ContextConfiguration to public.
Original Pull Request #2116
Closes #2114

(cherry picked from commit 709b4c615e0e8ce5ac9f8b8fc43fef43569d2ddc)
2022-03-21 09:31:08 +01:00
Mark Paluch
aebdc8f86b
Use Java 8 to build snapshots for Artifactory.
Closes #2095
2022-03-15 14:20:01 +01:00
Peter-Josef Meisch
2be27593d6
MappingBuilder must set configured date formats for date_range fields.
Original Pull Request #2103
Closes #2102

(cherry picked from commit bf080002bc60aaf63d27d1569833a2ae0374a161)
2022-02-22 21:13:43 +01:00
Mark Paluch
aa22d8239d
Update CI properties.
See #2091
2022-02-22 14:09:14 +01:00
Mark Paluch
6746bc5278
Upgrade to Maven Wrapper 3.8.4.
See #2100
2022-02-22 13:56:16 +01:00
Mark Paluch
a6cb959605
Polishing.
Fix Javadoc errors.

See #2095
2022-02-22 09:56:23 +01:00
Mark Paluch
e04905a1a7
Use Java 17 to build snapshots for Artifactory.
Closes #2095
2022-02-22 08:53:04 +01:00
Peter-Josef Meisch
834b10f578
Remove blocking code in SearchDocument processing.
Original Pull Request #2094
Closes #2025

(cherry picked from commit c1a1ea9724a9cd6590758895281261bc01a272ab)
2022-02-20 13:34:49 +01:00
Peter-Josef Meisch
823cfa919a
keep the documentation change from the cherrypick 2022-02-18 20:51:46 +01:00
Peter-Josef Meisch
ab29ae4219
Documentation about compatibility headers.
Original Pull Request #2093
Closes #2088

(cherry picked from commit cf380e289d4e01c220c6a7fc0484e46ab959ac94)
2022-02-18 20:36:51 +01:00
Mark Paluch
62fb89208a
After release cleanups.
See #2056
2022-02-18 10:49:02 +01:00
Mark Paluch
e8c3badc56
Prepare next development iteration.
See #2056
2022-02-18 10:49:00 +01:00
Mark Paluch
187c9bbd5d
Release version 4.3.2 (2021.1.2).
See #2056
2022-02-18 10:41:01 +01:00
Mark Paluch
e1a5811406
Prepare 4.3.2 (2021.1.2).
See #2056
2022-02-18 10:40:40 +01:00
Greg L. Turnquist
b643669d36 Update CI properties.
See #2056
2022-02-14 14:39:49 -06:00
Peter-Josef Meisch
f66af53480
Fix exception on property conversion with criteria exists/empty/non-empty.
Original Pull Request #2081
Closes #2080

(cherry picked from commit 32fa7391c42a64710dc073bb38cbf4611d9ac5aa)
2022-02-09 23:25:34 +01:00
Mark Paluch
14099970bb
Polishing.
Extract Docker and Artifactory credentials into properties file.

See #2074
2022-02-04 15:18:50 +01:00
Greg L. Turnquist
0ab253422f
Externalize build properties.
See #2074.
2022-02-04 15:18:48 +01:00
Christoph Strobl
0d2a6b98e8
After release cleanups.
See #1987
2022-01-14 10:45:02 +01:00
Christoph Strobl
30602496bd
Prepare next development iteration.
See #1987
2022-01-14 10:44:59 +01:00
Christoph Strobl
95762b4fde
Release version 4.3.1 (2021.1.1).
See #1987
2022-01-14 10:28:59 +01:00
Christoph Strobl
0771e90031
Prepare 4.3.1 (2021.1.1).
See #1987
2022-01-14 10:28:27 +01:00
Peter-Josef Meisch
6729330500
Update to log4j 2.17.0 2021-12-18 20:34:28 +01:00
Peter-Josef Meisch
8796292611
update log4j dependency version 2021-12-14 13:53:59 +01:00
Peter-Josef Meisch
f3f9ca4002
Fix FieldType mapping.
Original PullRequest #2026
Closes #2024

(cherry picked from commit f7a6a97c4e2fcde86fa182f599e548a75ce73865)
2021-12-13 21:52:28 +01:00
Peter-Josef Meisch
083a38ed57
Fix IndexOutOfBoundsException when try to map inner hits with no results returned.
Original Pull Request #1998
Closes #1997
Co-authored-by: Peter-Josef Meisch <pj.meisch@sothawo.com>

(cherry picked from commit 49324a369af627e390f981ed6b793f0f503526c7)
2021-12-05 09:02:06 +01:00
Sascha Woo
4f3aa52958
Fix IndexOutOfBoundsException when try to map inner hits with no results returned.
Original Pull Request #1998
Closes #1997
Co-authored-by: Peter-Josef Meisch <pj.meisch@sothawo.com>

(cherry picked from commit 49324a369af627e390f981ed6b793f0f503526c7)
2021-11-23 20:26:14 +01:00
Peter-Josef Meisch
3256a2bfe0
Fix RestStatusException cause.
Original Pull Request #1996
Closes #1995

(cherry picked from commit 45b4c99e951e4519c3fe1e2d4a51c8bd16ab33b0)
2021-11-16 07:38:01 +01:00
Peter-Josef Meisch
95401a5bd7
Exclude commons-logging dependency from Elasticsearch dependencies.
Original Pull Request #1993
Closes #1989
2021-11-13 14:56:57 +01:00
Mark Paluch
12cd64cfc8
Update build trigger to use branch build.
See #1965
2021-11-12 14:31:54 +01:00
Jens Schauder
7e557317d1
After release cleanups.
See #1965
2021-11-12 11:00:07 +01:00
Jens Schauder
c9846ab8ad
Prepare next development iteration.
See #1965
2021-11-12 11:00:05 +01:00
51 changed files with 1779 additions and 595 deletions

View File

@ -1,3 +1,3 @@
#Mon Oct 11 14:30:32 CEST 2021
#Fri Jun 03 09:42:29 CEST 2022
wrapperUrl=https\://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar
distributionUrl=https\://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.3/apache-maven-3.8.3-bin.zip
distributionUrl=https\://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.5/apache-maven-3.8.5-bin.zip

82
Jenkinsfile vendored
View File

@ -1,9 +1,15 @@
def p = [:]
node {
checkout scm
p = readProperties interpolate: true, file: 'ci/pipeline.properties'
}
pipeline {
agent none
triggers {
pollSCM 'H/10 * * * *'
upstream(upstreamProjects: "spring-data-commons/main", threshold: hudson.model.Result.SUCCESS)
upstream(upstreamProjects: "spring-data-commons/2.6.x", threshold: hudson.model.Result.SUCCESS)
}
options {
@ -12,10 +18,11 @@ pipeline {
}
stages {
stage("test: baseline (jdk8)") {
stage("test: baseline (main)") {
when {
beforeAgent(true)
anyOf {
branch 'main'
branch(pattern: "main|(\\d\\.\\d\\.x)", comparator: "REGEXP")
not { triggeredBy 'UpstreamCause' }
}
}
@ -25,14 +32,14 @@ pipeline {
options { timeout(time: 30, unit: 'MINUTES') }
environment {
DOCKER_HUB = credentials('hub.docker.com-springbuildmaster')
ARTIFACTORY = credentials('02bd1690-b54f-4c9f-819d-a77cb7a9822c')
DOCKER_HUB = credentials("${p['docker.credentials']}")
ARTIFACTORY = credentials("${p['artifactory.credentials']}")
}
steps {
script {
docker.withRegistry('', 'hub.docker.com-springbuildmaster') {
docker.image('adoptopenjdk/openjdk8:latest').inside('-u root -v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v $HOME:/tmp/jenkins-home') {
docker.withRegistry(p['docker.registry'], p['docker.credentials']) {
docker.image(p['docker.java.main.image']).inside(p['docker.java.inside.docker']) {
sh "docker login --username ${DOCKER_HUB_USR} --password ${DOCKER_HUB_PSW}"
sh 'PROFILE=none ci/verify.sh'
sh "ci/clean.sh"
@ -50,23 +57,23 @@ pipeline {
}
}
parallel {
stage("test: baseline (jdk11)") {
stage("test: baseline (next)") {
agent {
label 'data'
}
options { timeout(time: 30, unit: 'MINUTES') }
environment {
DOCKER_HUB = credentials('hub.docker.com-springbuildmaster')
ARTIFACTORY = credentials('02bd1690-b54f-4c9f-819d-a77cb7a9822c')
DOCKER_HUB = credentials("${p['docker.credentials']}")
ARTIFACTORY = credentials("${p['artifactory.credentials']}")
}
steps {
script {
docker.withRegistry('', 'hub.docker.com-springbuildmaster') {
docker.image('adoptopenjdk/openjdk11:latest').inside('-u root -v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v $HOME:/tmp/jenkins-home') {
docker.withRegistry(p['docker.registry'], p['docker.credentials']) {
docker.image(p['docker.java.next.image']).inside(p['docker.java.inside.docker']) {
sh "docker login --username ${DOCKER_HUB_USR} --password ${DOCKER_HUB_PSW}"
sh 'PROFILE=java11 ci/verify.sh'
sh 'PROFILE=none ci/verify.sh'
sh "ci/clean.sh"
}
}
@ -74,23 +81,23 @@ pipeline {
}
}
stage("test: baseline (jdk17)") {
stage("test: baseline (LTS)") {
agent {
label 'data'
}
options { timeout(time: 30, unit: 'MINUTES') }
environment {
DOCKER_HUB = credentials('hub.docker.com-springbuildmaster')
ARTIFACTORY = credentials('02bd1690-b54f-4c9f-819d-a77cb7a9822c')
DOCKER_HUB = credentials("${p['docker.credentials']}")
ARTIFACTORY = credentials("${p['artifactory.credentials']}")
}
steps {
script {
docker.withRegistry('', 'hub.docker.com-springbuildmaster') {
docker.image('openjdk:17-bullseye').inside('-u root -v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v $HOME:/tmp/jenkins-home') {
docker.withRegistry(p['docker.registry'], p['docker.credentials']) {
docker.image(p['docker.java.lts.image']).inside(p['docker.java.inside.docker']) {
sh "docker login --username ${DOCKER_HUB_USR} --password ${DOCKER_HUB_PSW}"
sh 'PROFILE=java11 ci/verify.sh'
sh 'PROFILE=none ci/verify.sh'
sh "ci/clean.sh"
}
}
@ -102,8 +109,9 @@ pipeline {
stage('Release to artifactory') {
when {
beforeAgent(true)
anyOf {
branch 'main'
branch(pattern: "main|(\\d\\.\\d\\.x)", comparator: "REGEXP")
not { triggeredBy 'UpstreamCause' }
}
}
@ -113,13 +121,13 @@ pipeline {
options { timeout(time: 20, unit: 'MINUTES') }
environment {
ARTIFACTORY = credentials('02bd1690-b54f-4c9f-819d-a77cb7a9822c')
ARTIFACTORY = credentials("${p['artifactory.credentials']}")
}
steps {
script {
docker.withRegistry('', 'hub.docker.com-springbuildmaster') {
docker.image('adoptopenjdk/openjdk8:latest').inside('-v $HOME:/tmp/jenkins-home') {
docker.withRegistry(p['docker.registry'], p['docker.credentials']) {
docker.image(p['docker.java.main.image']).inside(p['docker.java.inside.basic']) {
sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw -s settings.xml -Pci,artifactory -Dmaven.repo.local=/tmp/jenkins-home/.m2/spring-data-elasticsearch-non-root ' +
'-Dartifactory.server=https://repo.spring.io ' +
"-Dartifactory.username=${ARTIFACTORY_USR} " +
@ -133,34 +141,6 @@ pipeline {
}
}
}
stage('Publish documentation') {
when {
branch 'main'
}
agent {
label 'data'
}
options { timeout(time: 20, unit: 'MINUTES') }
environment {
ARTIFACTORY = credentials('02bd1690-b54f-4c9f-819d-a77cb7a9822c')
}
steps {
script {
docker.withRegistry('', 'hub.docker.com-springbuildmaster') {
docker.image('adoptopenjdk/openjdk8:latest').inside('-v $HOME:/tmp/jenkins-home') {
sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw -s settings.xml -Pci,distribute -Dmaven.repo.local=/tmp/jenkins-home/.m2/spring-data-elasticsearch-non-root ' +
'-Dartifactory.server=https://repo.spring.io ' +
"-Dartifactory.username=${ARTIFACTORY_USR} " +
"-Dartifactory.password=${ARTIFACTORY_PSW} " +
"-Dartifactory.distribution-repository=temp-private-local " +
'-Dmaven.test.skip=true clean deploy -U -B'
}
}
}
}
}
}
post {

29
ci/pipeline.properties Normal file
View File

@ -0,0 +1,29 @@
# Java versions
java.main.tag=8u332-b09-jdk
java.next.tag=11.0.15_10-jdk
java.lts.tag=17.0.3_7-jdk
# Docker container images - standard
docker.java.main.image=harbor-repo.vmware.com/dockerhub-proxy-cache/library/eclipse-temurin:${java.main.tag}
docker.java.next.image=harbor-repo.vmware.com/dockerhub-proxy-cache/library/eclipse-temurin:${java.next.tag}
docker.java.lts.image=harbor-repo.vmware.com/dockerhub-proxy-cache/library/eclipse-temurin:${java.lts.tag}
# Supported versions of MongoDB
docker.mongodb.4.0.version=4.0.28
docker.mongodb.4.4.version=4.4.12
docker.mongodb.5.0.version=5.0.6
# Supported versions of Redis
docker.redis.6.version=6.2.6
# Supported versions of Cassandra
docker.cassandra.3.version=3.11.12
# Docker environment settings
docker.java.inside.basic=-v $HOME:/tmp/jenkins-home
docker.java.inside.docker=-u root -v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v $HOME:/tmp/jenkins-home
# Credentials
docker.registry=
docker.credentials=hub.docker.com-springbuildmaster
artifactory.credentials=02bd1690-b54f-4c9f-819d-a77cb7a9822c

24
pom.xml
View File

@ -5,12 +5,12 @@
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-elasticsearch</artifactId>
<version>4.3.0</version>
<version>4.3.10</version>
<parent>
<groupId>org.springframework.data.build</groupId>
<artifactId>spring-data-parent</artifactId>
<version>2.6.0</version>
<version>2.6.10</version>
</parent>
<name>Spring Data Elasticsearch</name>
@ -19,9 +19,9 @@
<properties>
<elasticsearch>7.15.2</elasticsearch>
<log4j>2.14.1</log4j>
<log4j>2.17.0</log4j>
<netty>4.1.65.Final</netty>
<springdata.commons>2.6.0</springdata.commons>
<springdata.commons>2.6.10</springdata.commons>
<testcontainers>1.15.3</testcontainers>
<blockhound-junit>1.0.6.RELEASE</blockhound-junit>
<java-module-name>spring.data.elasticsearch</java-module-name>
@ -145,6 +145,12 @@
<groupId>org.elasticsearch.client</groupId>
<artifactId>transport</artifactId>
<version>${elasticsearch}</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
@ -152,6 +158,12 @@
<groupId>org.elasticsearch.plugin</groupId>
<artifactId>transport-netty4-client</artifactId>
<version>${elasticsearch}</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
@ -461,7 +473,9 @@
</module>
</checkstyleRules>
<includes>**/*</includes>
<excludes>.git/**/*,target/**/*,**/target/**/*,.idea/**/*,**/spring.schemas,**/*.svg,mvnw,mvnw.cmd,**/*.policy</excludes>
<excludes>
.git/**/*,target/**/*,**/target/**/*,.idea/**/*,**/spring.schemas,**/*.svg,mvnw,mvnw.cmd,**/*.policy
</excludes>
<sourceDirectories>./</sourceDirectories>
</configuration>
</plugin>

View File

@ -199,6 +199,29 @@ Default is 5 sec.
IMPORTANT: Adding a Header supplier as shown in above example allows to inject headers that may change over the time, like authentication JWT tokens.
If this is used in the reactive setup, the supplier function *must not* block!
=== Elasticsearch 7 compatibility headers
When using Spring Data Elasticsearch 4 - which uses the Elasticsearch 7 client libraries - and accessing an Elasticsearch cluster that is running on version 8, it is necessary to set the compatibility headers
https://www.elastic.co/guide/en/elasticsearch/reference/8.0/rest-api-compatibility.html[see Elasticserach documentation].
This should be done using a header supplier like shown above:
====
[source,java]
----
ClientConfigurationBuilder configurationBuilder = new ClientConfigurationBuilder();
configurationBuilder //
// ...
.withHeaders(() -> {
HttpHeaders defaultCompatibilityHeaders = new HttpHeaders();
defaultCompatibilityHeaders.add("Accept",
"application/vnd.elasticsearch+json;compatible-with=7");
defaultCompatibilityHeaders.add("Content-Type",
"application/vnd.elasticsearch+json;compatible-with=7");
return defaultCompatibilityHeaders;
});
----
====
[[elasticsearch.clients.logging]]
== Client Logging

View File

@ -46,6 +46,16 @@ public @interface CompletionContext {
* @since 4.3
*/
enum ContextMappingType {
CATEGORY, GEO
CATEGORY("category"), GEO("geo");
private final String mappedName;
ContextMappingType(String mappedName) {
this.mappedName = mappedName;
}
public String getMappedName() {
return mappedName;
}
}
}

View File

@ -44,7 +44,7 @@ public @interface Document {
* Name of the Elasticsearch index.
* <ul>
* <li>Lowercase only</li>
* <li>Cannot include \, /, *, ?, ", <, >, |, ` ` (space character), ,, #</li>
* <li>Cannot include \, /, *, ?, ", &gt;, &lt;, |, ` ` (space character), ,, #</li>
* <li>Cannot start with -, _, +</li>
* <li>Cannot be . or ..</li>
* <li>Cannot be longer than 255 bytes (note it is bytes, so multi-byte characters will count towards the 255 limit

View File

@ -17,7 +17,7 @@ package org.springframework.data.elasticsearch.annotations;
/**
* Values for the {@code dynamic} mapping parameter.
*
*
* @author Sascha Woo
* @since 4.3
*/
@ -25,26 +25,36 @@ public enum Dynamic {
/**
* New fields are added to the mapping.
*/
TRUE,
TRUE("true"),
/**
* New fields are added to the mapping as
* <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/runtime.html">runtime fields</a>. These
* fields are not indexed, and are loaded from {@code _source} at query time.
*/
RUNTIME,
RUNTIME("runtime"),
/**
* New fields are ignored. These fields will not be indexed or searchable, but will still appear in the
* {@code _source} field of returned hits. These fields will not be added to the mapping, and new fields must be added
* explicitly.
*/
FALSE,
FALSE("false"),
/**
* If new fields are detected, an exception is thrown and the document is rejected. New fields must be explicitly
* added to the mapping.
*/
STRICT,
STRICT("strict"),
/**
* Inherit the dynamic setting from their parent object or from the mapping type.
*/
INHERIT
INHERIT("inherit");
private final String mappedName;
Dynamic(String mappedName) {
this.mappedName = mappedName;
}
public String getMappedName() {
return mappedName;
}
}

View File

@ -17,13 +17,23 @@ package org.springframework.data.elasticsearch.annotations;
/**
* values for the {@link DynamicMapping annotation}
*
*
* @author Peter-Josef Meisch
* @author Sascha Woo
* @since 4.0
* @deprecated since 4.3, use {@link Document#dynamic()} or {@link Field#dynamic()} instead.
* @deprecated since 4.3, use {@link Document#dynamic()} or {@link Field#dynamic()} instead.
*/
@Deprecated
public enum DynamicMappingValue {
True, False, Strict
True("true"), False("false"), Strict("strict");
private final String mappedName;
DynamicMappingValue(String mappedName) {
this.mappedName = mappedName;
}
public String getMappedName() {
return mappedName;
}
}

View File

@ -26,40 +26,51 @@ package org.springframework.data.elasticsearch.annotations;
* @author Morgan Lutz
*/
public enum FieldType {
Auto, //
Text, //
Keyword, //
Long, //
Integer, //
Short, //
Byte, //
Double, //
Float, //
Half_Float, //
Scaled_Float, //
Date, //
Date_Nanos, //
Boolean, //
Binary, //
Integer_Range, //
Float_Range, //
Long_Range, //
Double_Range, //
Date_Range, //
Ip_Range, //
Object, //
Nested, //
Ip, //
TokenCount, //
Percolator, //
Flattened, //
Search_As_You_Type, //
Auto("auto"), //
Text("text"), //
Keyword("keyword"), //
Long("long"), //
Integer("integer"), //
Short("short"), //
Byte("byte"), //
Double("double"), //
Float("float"), //
Half_Float("half_float"), //
Scaled_Float("scaled_float"), //
Date("date"), //
Date_Nanos("date_nanos"), //
Boolean("boolean"), //
Binary("binary"), //
Integer_Range("integer_range"), //
Float_Range("float_range"), //
Long_Range("long_range"), //
Double_Range("double_range"), //
Date_Range("date_range"), //
Ip_Range("ip_range"), //
Object("object"), //
Nested("nested"), //
Ip("ip"), //
TokenCount("token_count"), //
Percolator("percolator"), //
Flattened("flattened"), //
Search_As_You_Type("search_as_you_type"), //
/** @since 4.1 */
Rank_Feature, //
Rank_Feature("rank_feature"), //
/** @since 4.1 */
Rank_Features, //
Rank_Features("rank_features"), //
/** since 4.2 */
Wildcard, //
Wildcard("wildcard"), //
/** @since 4.2 */
Dense_Vector //
Dense_Vector("dense_vector") //
;
private final String mappedName;
FieldType(String mappedName) {
this.mappedName = mappedName;
}
public String getMappedName() {
return mappedName;
}
}

View File

@ -336,8 +336,13 @@ public class RequestConverters {
public static Request index(IndexRequest indexRequest) {
String method = Strings.hasLength(indexRequest.id()) ? HttpMethod.PUT.name() : HttpMethod.POST.name();
boolean isCreate = (indexRequest.opType() == DocWriteRequest.OpType.CREATE);
String endpoint = endpoint(indexRequest.index(), indexRequest.type(), indexRequest.id(),
isCreate ? "_create" : null);
String endpoint;
if (indexRequest.opType() == DocWriteRequest.OpType.CREATE) {
endpoint = indexRequest.type().equals("_doc") ? endpoint(indexRequest.index(), "_create", indexRequest.id())
: endpoint(indexRequest.index(), indexRequest.type(), indexRequest.id(), "_create");
} else {
endpoint = endpoint(indexRequest.index(), indexRequest.type(), indexRequest.id());
}
Request request = new Request(method, endpoint);
Params parameters = new Params(request);
@ -362,7 +367,9 @@ public class RequestConverters {
}
public static Request update(UpdateRequest updateRequest) {
String endpoint = endpoint(updateRequest.index(), updateRequest.type(), updateRequest.id(), "_update");
String endpoint = updateRequest.type().equals("_doc")
? endpoint(updateRequest.index(), "_update", updateRequest.id())
: endpoint(updateRequest.index(), updateRequest.type(), updateRequest.id(), "_update");
Request request = new Request(HttpMethod.POST.name(), endpoint);
Params parameters = new Params(request);
@ -500,8 +507,11 @@ public class RequestConverters {
}
public static Request explain(ExplainRequest explainRequest) {
String endpoint = explainRequest.type().equals("_doc")
? endpoint(explainRequest.index(), "_explain", explainRequest.id())
: endpoint(explainRequest.index(), explainRequest.type(), explainRequest.id(), "_explain");
Request request = new Request(HttpMethod.GET.name(),
endpoint(explainRequest.index(), explainRequest.type(), explainRequest.id(), "_explain"));
endpoint(explainRequest.index(), explainRequest.type(), explainRequest.id(), endpoint));
Params params = new Params(request);
params.withStoredFields(explainRequest.storedFields());

View File

@ -20,6 +20,7 @@ import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@ -109,7 +110,8 @@ public abstract class AbstractElasticsearchRestTransportTemplate extends Abstrac
List<SearchHits<T>> res = new ArrayList<>(queries.size());
int c = 0;
for (Query query : queries) {
res.add(callback.doWith(SearchDocumentResponse.from(items[c++].getResponse(), documentCallback::doWith)));
res.add(
callback.doWith(SearchDocumentResponse.from(items[c++].getResponse(), getEntityCreator(documentCallback))));
}
return res;
}
@ -142,7 +144,7 @@ public abstract class AbstractElasticsearchRestTransportTemplate extends Abstrac
index);
SearchResponse response = items[c++].getResponse();
res.add(callback.doWith(SearchDocumentResponse.from(response, documentCallback::doWith)));
res.add(callback.doWith(SearchDocumentResponse.from(response, getEntityCreator(documentCallback))));
}
return res;
}
@ -175,7 +177,7 @@ public abstract class AbstractElasticsearchRestTransportTemplate extends Abstrac
index);
SearchResponse response = items[c++].getResponse();
res.add(callback.doWith(SearchDocumentResponse.from(response, documentCallback::doWith)));
res.add(callback.doWith(SearchDocumentResponse.from(response, getEntityCreator(documentCallback))));
}
return res;
}
@ -215,5 +217,9 @@ public abstract class AbstractElasticsearchRestTransportTemplate extends Abstrac
return suggest(suggestion, getIndexCoordinatesFor(clazz));
}
protected <T> SearchDocumentResponse.EntityCreator<T> getEntityCreator(ReadDocumentCallback<T> documentCallback) {
return searchDocument -> CompletableFuture.completedFuture(documentCallback.doWith(searchDocument));
}
// endregion
}

View File

@ -64,9 +64,9 @@ public class ElasticsearchExceptionTranslator implements PersistenceExceptionTra
}
if (elasticsearchException instanceof ElasticsearchStatusException) {
ElasticsearchStatusException restStatusException = (ElasticsearchStatusException) elasticsearchException;
return new RestStatusException(restStatusException.status().getStatus(), restStatusException.getMessage(),
restStatusException.getCause());
ElasticsearchStatusException elasticsearchStatusException = (ElasticsearchStatusException) elasticsearchException;
return new RestStatusException(elasticsearchStatusException.status().getStatus(),
elasticsearchStatusException.getMessage(), elasticsearchStatusException);
}
return new UncategorizedElasticsearchException(ex.getMessage(), ex);

View File

@ -1,5 +1,5 @@
/*
* Copyright 2013-2021 the original author or authors.
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -319,7 +319,7 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchRestTranspor
ReadDocumentCallback<T> documentCallback = new ReadDocumentCallback<T>(elasticsearchConverter, clazz, index);
SearchDocumentResponseCallback<SearchHits<T>> callback = new ReadSearchDocumentResponseCallback<>(clazz, index);
return callback.doWith(SearchDocumentResponse.from(response, documentCallback::doWith));
return callback.doWith(SearchDocumentResponse.from(response, getEntityCreator(documentCallback)));
}
@Override
@ -336,7 +336,7 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchRestTranspor
ReadDocumentCallback<T> documentCallback = new ReadDocumentCallback<T>(elasticsearchConverter, clazz, index);
SearchDocumentResponseCallback<SearchScrollHits<T>> callback = new ReadSearchScrollDocumentResponseCallback<>(clazz,
index);
return callback.doWith(SearchDocumentResponse.from(response, documentCallback::doWith));
return callback.doWith(SearchDocumentResponse.from(response, getEntityCreator(documentCallback)));
}
@Override
@ -351,7 +351,7 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchRestTranspor
ReadDocumentCallback<T> documentCallback = new ReadDocumentCallback<T>(elasticsearchConverter, clazz, index);
SearchDocumentResponseCallback<SearchScrollHits<T>> callback = new ReadSearchScrollDocumentResponseCallback<>(clazz,
index);
return callback.doWith(SearchDocumentResponse.from(response, documentCallback::doWith));
return callback.doWith(SearchDocumentResponse.from(response, getEntityCreator(documentCallback)));
}
@Override
@ -378,8 +378,8 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchRestTranspor
Assert.isTrue(items.length == request.requests().size(), "Response should has same length with queries");
return items;
}
// endregion
// endregion
// region ClientCallback
/**
* Callback interface to be used with {@link #execute(ClientCallback)} for operating directly on

View File

@ -354,7 +354,7 @@ public class ElasticsearchTemplate extends AbstractElasticsearchRestTransportTem
ReadDocumentCallback<T> documentCallback = new ReadDocumentCallback<T>(elasticsearchConverter, clazz, index);
SearchDocumentResponseCallback<SearchHits<T>> callback = new ReadSearchDocumentResponseCallback<>(clazz, index);
return callback.doWith(SearchDocumentResponse.from(response, documentCallback::doWith));
return callback.doWith(SearchDocumentResponse.from(response, getEntityCreator(documentCallback)));
}
@Override
@ -372,7 +372,7 @@ public class ElasticsearchTemplate extends AbstractElasticsearchRestTransportTem
ReadDocumentCallback<T> documentCallback = new ReadDocumentCallback<T>(elasticsearchConverter, clazz, index);
SearchDocumentResponseCallback<SearchScrollHits<T>> callback = new ReadSearchScrollDocumentResponseCallback<>(clazz,
index);
return callback.doWith(SearchDocumentResponse.from(response, documentCallback::doWith));
return callback.doWith(SearchDocumentResponse.from(response, getEntityCreator(documentCallback)));
}
@Override
@ -389,7 +389,7 @@ public class ElasticsearchTemplate extends AbstractElasticsearchRestTransportTem
ReadDocumentCallback<T> documentCallback = new ReadDocumentCallback<T>(elasticsearchConverter, clazz, index);
SearchDocumentResponseCallback<SearchScrollHits<T>> callback = new ReadSearchScrollDocumentResponseCallback<>(clazz,
index);
return callback.doWith(SearchDocumentResponse.from(response, documentCallback::doWith));
return callback.doWith(SearchDocumentResponse.from(response, getEntityCreator(documentCallback)));
}
@Override

View File

@ -1,5 +1,5 @@
/*
* Copyright 2018-2021 the original author or authors.
* Copyright 2018-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -23,7 +23,6 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.elasticsearch.Version;
@ -771,15 +770,18 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
});
}
private Mono<SearchDocumentResponse> doFindForResponse(Query query, Class<?> clazz, IndexCoordinates index) {
private <T> Mono<SearchDocumentResponse> doFindForResponse(Query query, Class<?> clazz, IndexCoordinates index) {
return Mono.defer(() -> {
SearchRequest request = requestFactory.searchRequest(query, clazz, index);
request = prepareSearchRequest(request, false);
SearchDocumentCallback<?> documentCallback = new ReadSearchDocumentCallback<>(clazz, index);
// noinspection unchecked
SearchDocumentResponse.EntityCreator<T> entityCreator = searchDocument -> ((Mono<T>) documentCallback
.toEntity(searchDocument)).toFuture();
return doFindForResponse(request, searchDocument -> documentCallback.toEntity(searchDocument).block());
return doFindForResponse(request, entityCreator);
});
}
@ -896,19 +898,18 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
* Customization hook on the actual execution result {@link Mono}. <br />
*
* @param request the already prepared {@link SearchRequest} ready to be executed.
* @param suggestEntityCreator
* @param entityCreator
* @return a {@link Mono} emitting the result of the operation converted to s {@link SearchDocumentResponse}.
*/
protected Mono<SearchDocumentResponse> doFindForResponse(SearchRequest request,
Function<SearchDocument, ? extends Object> suggestEntityCreator) {
protected <T> Mono<SearchDocumentResponse> doFindForResponse(SearchRequest request,
SearchDocumentResponse.EntityCreator<T> entityCreator) {
if (QUERY_LOGGER.isDebugEnabled()) {
QUERY_LOGGER.debug("Executing doFindForResponse: {}", request);
}
return Mono.from(execute(client1 -> client1.searchForResponse(request))).map(searchResponse -> {
return SearchDocumentResponse.from(searchResponse, suggestEntityCreator);
});
return Mono.from(execute(client -> client.searchForResponse(request)))
.map(searchResponse -> SearchDocumentResponse.from(searchResponse, entityCreator));
}
/**

View File

@ -123,7 +123,6 @@ public interface ReactiveSearchOperations {
/**
* Search the index for entities matching the given {@link Query query}.
*
* @param <T>
* @param query must not be {@literal null}.
* @param entityType must not be {@literal null}.
* @param <T>
@ -137,7 +136,6 @@ public interface ReactiveSearchOperations {
/**
* Search the index for entities matching the given {@link Query query}.
*
* @param <T>
* @param query must not be {@literal null}.
* @param entityType must not be {@literal null}.
* @param resultType the projection result type.
@ -150,7 +148,6 @@ public interface ReactiveSearchOperations {
/**
* Search the index for entities matching the given {@link Query query}.
*
* @param <T>
* @param query must not be {@literal null}.
* @param entityType must not be {@literal null}.
* @param index the target index, must not be {@literal null}
@ -165,7 +162,6 @@ public interface ReactiveSearchOperations {
/**
* Search the index for entities matching the given {@link Query query}.
*
* @param <T>
* @param query must not be {@literal null}.
* @param entityType must not be {@literal null}.
* @param resultType the projection result type.

View File

@ -1472,8 +1472,7 @@ class RequestFactory {
public UpdateByQueryRequest updateByQueryRequest(UpdateQuery query, IndexCoordinates index) {
String indexName = index.getIndexName();
final UpdateByQueryRequest updateByQueryRequest = new UpdateByQueryRequest(indexName);
final UpdateByQueryRequest updateByQueryRequest = new UpdateByQueryRequest(index.getIndexNames());
updateByQueryRequest.setScript(getScript(query));
if (query.getAbortOnVersionConflict() != null) {

View File

@ -24,6 +24,7 @@ import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.elasticsearch.UncategorizedElasticsearchException;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
import org.springframework.data.elasticsearch.core.document.Document;
import org.springframework.data.elasticsearch.core.document.NestedMetaData;
@ -45,6 +46,7 @@ import org.springframework.util.Assert;
* @author Mark Paluch
* @author Roman Puchkovskiy
* @author Matt Gilene
* @author Sascha Woo
* @since 4.0
*/
class SearchHitMapping<T> {
@ -194,7 +196,7 @@ class SearchHitMapping<T> {
*/
private SearchHits<?> mapInnerDocuments(SearchHits<SearchDocument> searchHits, Class<T> type) {
if (searchHits.getTotalHits() == 0) {
if (searchHits.isEmpty()) {
return searchHits;
}
@ -239,7 +241,7 @@ class SearchHitMapping<T> {
searchHits.getSuggest());
}
} catch (Exception e) {
LOGGER.warn("Could not map inner_hits", e);
throw new UncategorizedElasticsearchException("Unable to convert inner hits.", e);
}
return searchHits;

View File

@ -76,7 +76,10 @@ public abstract class AbstractRangePropertyValueConverter<T> extends AbstractPro
public Object write(Object value) {
Assert.notNull(value, "value must not be null.");
Assert.isInstanceOf(Range.class, value, "value must be instance of Range.");
if (!Range.class.isAssignableFrom(value.getClass())) {
return value.toString();
}
try {
Range<T> range = (Range<T>) value;

View File

@ -58,6 +58,10 @@ public class DatePropertyValueConverter extends AbstractPropertyValueConverter {
@Override
public Object write(Object value) {
if (!Date.class.isAssignableFrom(value.getClass())) {
return value.toString();
}
try {
return dateConverters.get(0).format((Date) value);
} catch (Exception e) {

View File

@ -66,7 +66,6 @@ final public class ElasticsearchDateConverter {
*/
public static ElasticsearchDateConverter of(String pattern) {
Assert.notNull(pattern, "pattern must not be null");
Assert.hasText(pattern, "pattern must not be empty");
String[] subPatterns = pattern.split("\\|\\|");
@ -86,7 +85,7 @@ final public class ElasticsearchDateConverter {
*/
public String format(TemporalAccessor accessor) {
Assert.notNull("accessor", "accessor must not be null");
Assert.notNull(accessor, "accessor must not be null");
if (accessor instanceof Instant) {
Instant instant = (Instant) accessor;

View File

@ -21,6 +21,14 @@ import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.time.temporal.TemporalAccessor;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
@ -51,16 +59,7 @@ import org.springframework.data.mapping.MappingException;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.PreferredConstructor;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
import org.springframework.data.mapping.model.DefaultSpELExpressionEvaluator;
import org.springframework.data.mapping.model.EntityInstantiator;
import org.springframework.data.mapping.model.EntityInstantiators;
import org.springframework.data.mapping.model.ParameterValueProvider;
import org.springframework.data.mapping.model.PersistentEntityParameterValueProvider;
import org.springframework.data.mapping.model.PropertyValueProvider;
import org.springframework.data.mapping.model.SpELContext;
import org.springframework.data.mapping.model.SpELExpressionEvaluator;
import org.springframework.data.mapping.model.SpELExpressionParameterValueProvider;
import org.springframework.data.mapping.model.*;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.TypeInformation;
import org.springframework.format.datetime.DateFormatterRegistrar;
@ -196,7 +195,7 @@ public class MappingElasticsearchConverter
}
/**
* Class to do the actual writing. The methods originally were in the MappingElasticsearchConverter class, but are
* Class to do the actual reading. The methods originally were in the MappingElasticsearchConverter class, but are
* refactored to allow for keeping state during the conversion of an object.
*/
private static class Reader extends Base {
@ -215,10 +214,17 @@ public class MappingElasticsearchConverter
}
@SuppressWarnings("unchecked")
/**
* Reads the given source into the given type.
*
* @param type they type to convert the given source to.
* @param source the source to create an object of the given type from.
* @return the object that was read
*/
<R> R read(Class<R> type, Document source) {
TypeInformation<R> typeHint = ClassTypeInformation.from((Class<R>) ClassUtils.getUserClass(type));
R r = read(typeHint, source);
TypeInformation<R> typeInformation = ClassTypeInformation.from((Class<R>) ClassUtils.getUserClass(type));
R r = read(typeInformation, source);
if (r == null) {
throw new ConversionException("could not convert into object of class " + type);
@ -229,11 +235,11 @@ public class MappingElasticsearchConverter
@Nullable
@SuppressWarnings("unchecked")
private <R> R read(TypeInformation<R> type, Map<String, Object> source) {
private <R> R read(TypeInformation<R> typeInformation, Map<String, Object> source) {
Assert.notNull(source, "Source must not be null!");
TypeInformation<? extends R> typeToUse = typeMapper.readType(source, type);
TypeInformation<? extends R> typeToUse = typeMapper.readType(source, typeInformation);
Class<? extends R> rawType = typeToUse.getType();
if (conversions.hasCustomReadTarget(source.getClass(), rawType)) {
@ -251,8 +257,8 @@ public class MappingElasticsearchConverter
if (typeToUse.equals(ClassTypeInformation.OBJECT)) {
return (R) source;
}
// Retrieve persistent entity info
// Retrieve persistent entity info
ElasticsearchPersistentEntity<?> entity = mappingContext.getPersistentEntity(typeToUse);
if (entity == null) {
@ -370,7 +376,6 @@ public class MappingElasticsearchConverter
}
return result;
}
private ParameterValueProvider<ElasticsearchPersistentProperty> getParameterProvider(
@ -460,12 +465,44 @@ public class MappingElasticsearchConverter
} else if (value.getClass().isArray()) {
return (T) readCollectionOrArray(type, Arrays.asList((Object[]) value));
} else if (value instanceof Map) {
TypeInformation<?> collectionComponentType = getCollectionComponentType(type);
if (collectionComponentType != null) {
Object o = read(collectionComponentType, (Map<String, Object>) value);
return getCollectionWithSingleElement(type, collectionComponentType, o);
}
return (T) read(type, (Map<String, Object>) value);
} else {
TypeInformation<?> collectionComponentType = getCollectionComponentType(type);
if (collectionComponentType != null
&& collectionComponentType.isAssignableFrom(ClassTypeInformation.from(value.getClass()))) {
Object o = getPotentiallyConvertedSimpleRead(value, collectionComponentType);
return getCollectionWithSingleElement(type, collectionComponentType, o);
}
return (T) getPotentiallyConvertedSimpleRead(value, rawType);
}
}
@SuppressWarnings("unchecked")
private static <T> T getCollectionWithSingleElement(TypeInformation<?> collectionType,
TypeInformation<?> componentType, Object element) {
Collection<Object> collection = CollectionFactory.createCollection(collectionType.getType(),
componentType.getType(), 1);
collection.add(element);
return (T) collection;
}
/**
* @param type the type to check
* @return true if type is a collectoin, null otherwise,
*/
@Nullable
TypeInformation<?> getCollectionComponentType(TypeInformation<?> type) {
return type.isCollectionLike() ? type.getComponentType() : null;
}
private Object propertyConverterRead(ElasticsearchPersistentProperty property, Object source) {
PropertyValueConverter propertyValueConverter = Objects.requireNonNull(property.getPropertyValueConverter());
@ -1131,17 +1168,18 @@ public class MappingElasticsearchConverter
Assert.notNull(query, "query must not be null");
if (domainClass != null) {
if (domainClass == null) {
return;
}
updateFieldsAndSourceFilter(query, domainClass);
updatePropertiesInFieldsAndSourceFilter(query, domainClass);
if (query instanceof CriteriaQuery) {
updateCriteriaQuery((CriteriaQuery) query, domainClass);
}
if (query instanceof CriteriaQuery) {
updatePropertiesInCriteriaQuery((CriteriaQuery) query, domainClass);
}
}
private void updateFieldsAndSourceFilter(Query query, Class<?> domainClass) {
private void updatePropertiesInFieldsAndSourceFilter(Query query, Class<?> domainClass) {
ElasticsearchPersistentEntity<?> persistentEntity = mappingContext.getPersistentEntity(domainClass);
@ -1174,14 +1212,22 @@ public class MappingElasticsearchConverter
}
}
private List<String> updateFieldNames(List<String> fields, ElasticsearchPersistentEntity<?> persistentEntity) {
return fields.stream().map(fieldName -> {
/**
* relaces the fieldName with the property name of a property of the persistentEntity with the corresponding
* fieldname. If no such property exists, the original fieldName is kept.
*
* @param fieldNames list of fieldnames
* @param persistentEntity the persistent entity to check
* @return an updated list of field names
*/
private List<String> updateFieldNames(List<String> fieldNames, ElasticsearchPersistentEntity<?> persistentEntity) {
return fieldNames.stream().map(fieldName -> {
ElasticsearchPersistentProperty persistentProperty = persistentEntity.getPersistentProperty(fieldName);
return persistentProperty != null ? persistentProperty.getFieldName() : fieldName;
}).collect(Collectors.toList());
}
private void updateCriteriaQuery(CriteriaQuery criteriaQuery, Class<?> domainClass) {
private void updatePropertiesInCriteriaQuery(CriteriaQuery criteriaQuery, Class<?> domainClass) {
Assert.notNull(criteriaQuery, "criteriaQuery must not be null");
Assert.notNull(domainClass, "domainClass must not be null");
@ -1190,17 +1236,17 @@ public class MappingElasticsearchConverter
if (persistentEntity != null) {
for (Criteria chainedCriteria : criteriaQuery.getCriteria().getCriteriaChain()) {
updateCriteria(chainedCriteria, persistentEntity);
updatePropertiesInCriteria(chainedCriteria, persistentEntity);
}
for (Criteria subCriteria : criteriaQuery.getCriteria().getSubCriteria()) {
for (Criteria chainedCriteria : subCriteria.getCriteriaChain()) {
updateCriteria(chainedCriteria, persistentEntity);
updatePropertiesInCriteria(chainedCriteria, persistentEntity);
}
}
}
}
private void updateCriteria(Criteria criteria, ElasticsearchPersistentEntity<?> persistentEntity) {
private void updatePropertiesInCriteria(Criteria criteria, ElasticsearchPersistentEntity<?> persistentEntity) {
Field field = criteria.getField();
@ -1256,14 +1302,19 @@ public class MappingElasticsearchConverter
PropertyValueConverter propertyValueConverter = Objects
.requireNonNull(persistentProperty.getPropertyValueConverter());
criteria.getQueryCriteriaEntries().forEach(criteriaEntry -> {
Object value = criteriaEntry.getValue();
if (value.getClass().isArray()) {
Object[] objects = (Object[]) value;
for (int i = 0; i < objects.length; i++) {
objects[i] = propertyValueConverter.write(objects[i]);
if (criteriaEntry.getKey().hasValue()) {
Object value = criteriaEntry.getValue();
if (value.getClass().isArray()) {
Object[] objects = (Object[]) value;
for (int i = 0; i < objects.length; i++) {
objects[i] = propertyValueConverter.write(objects[i]);
}
} else {
criteriaEntry.setValue(propertyValueConverter.write(value));
}
} else {
criteriaEntry.setValue(propertyValueConverter.write(value));
}
});
}

View File

@ -61,6 +61,10 @@ public class TemporalPropertyValueConverter extends AbstractPropertyValueConvert
@Override
public Object write(Object value) {
if (!TemporalAccessor.class.isAssignableFrom(value.getClass())) {
return value.toString();
}
try {
return dateConverters.get(0).format((TemporalAccessor) value);
} catch (Exception e) {

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2021 the original author or authors.
* Copyright 2019-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -17,8 +17,11 @@ package org.springframework.data.elasticsearch.core.document;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.lucene.search.TotalHits;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.text.Text;
@ -38,13 +41,15 @@ import org.springframework.util.Assert;
/**
* This represents the complete search response from Elasticsearch, including the returned documents. Instances must be
* created with the {@link #from(SearchResponse,Function)} method.
* created with the {@link #from(SearchResponse, EntityCreator)} method.
*
* @author Peter-Josef Meisch
* @since 4.0
*/
public class SearchDocumentResponse {
private static final Log LOGGER = LogFactory.getLog(SearchDocumentResponse.class);
private final long totalHits;
private final String totalHitsRelation;
private final float maxScore;
@ -98,12 +103,11 @@ public class SearchDocumentResponse {
* creates a SearchDocumentResponse from the {@link SearchResponse}
*
* @param searchResponse must not be {@literal null}
* @param suggestEntityCreator function to create an entity from a {@link SearchDocument}
* @param entityCreator function to create an entity from a {@link SearchDocument}
* @param <T> entity type
* @return the SearchDocumentResponse
*/
public static <T> SearchDocumentResponse from(SearchResponse searchResponse,
Function<SearchDocument, T> suggestEntityCreator) {
public static <T> SearchDocumentResponse from(SearchResponse searchResponse, EntityCreator<T> entityCreator) {
Assert.notNull(searchResponse, "searchResponse must not be null");
@ -112,7 +116,7 @@ public class SearchDocumentResponse {
Aggregations aggregations = searchResponse.getAggregations();
org.elasticsearch.search.suggest.Suggest suggest = searchResponse.getSuggest();
return from(searchHits, scrollId, aggregations, suggest, suggestEntityCreator);
return from(searchHits, scrollId, aggregations, suggest, entityCreator);
}
/**
@ -122,14 +126,14 @@ public class SearchDocumentResponse {
* @param scrollId scrollId
* @param aggregations aggregations
* @param suggestES the suggestion response from Elasticsearch
* @param suggestEntityCreator function to create an entity from a {@link SearchDocument}
* @param entityCreator function to create an entity from a {@link SearchDocument}
* @param <T> entity type
* @return the {@link SearchDocumentResponse}
* @since 4.3
*/
public static <T> SearchDocumentResponse from(SearchHits searchHits, @Nullable String scrollId,
@Nullable Aggregations aggregations, @Nullable org.elasticsearch.search.suggest.Suggest suggestES,
Function<SearchDocument, T> suggestEntityCreator) {
EntityCreator<T> entityCreator) {
TotalHits responseTotalHits = searchHits.getTotalHits();
@ -153,14 +157,14 @@ public class SearchDocumentResponse {
}
}
Suggest suggest = suggestFrom(suggestES, suggestEntityCreator);
Suggest suggest = suggestFrom(suggestES, entityCreator);
return new SearchDocumentResponse(totalHits, totalHitsRelation, maxScore, scrollId, searchDocuments, aggregations,
suggest);
}
@Nullable
private static <T> Suggest suggestFrom(@Nullable org.elasticsearch.search.suggest.Suggest suggestES,
Function<SearchDocument, T> entityCreator) {
EntityCreator<T> entityCreator) {
if (suggestES == null) {
return null;
@ -219,7 +223,19 @@ public class SearchDocumentResponse {
List<CompletionSuggestion.Entry.Option<T>> options = new ArrayList<>();
for (org.elasticsearch.search.suggest.completion.CompletionSuggestion.Entry.Option optionES : entryES) {
SearchDocument searchDocument = optionES.getHit() != null ? DocumentAdapters.from(optionES.getHit()) : null;
T hitEntity = searchDocument != null ? entityCreator.apply(searchDocument) : null;
T hitEntity = null;
if (searchDocument != null) {
try {
hitEntity = entityCreator.apply(searchDocument).get();
} catch (Exception e) {
if (LOGGER.isWarnEnabled()) {
LOGGER.warn("Error creating entity from SearchDocument");
}
}
}
options.add(new CompletionSuggestion.Entry.Option<T>(textToString(optionES.getText()),
textToString(optionES.getHighlighted()), optionES.getScore(), optionES.collateMatch(),
optionES.getContexts(), scoreDocFrom(optionES.getDoc()), searchDocument, hitEntity));
@ -254,4 +270,14 @@ public class SearchDocumentResponse {
private static String textToString(@Nullable Text text) {
return text != null ? text.string() : "";
}
/**
* 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.
*
* @param <T> the entity type
*/
@FunctionalInterface
public interface EntityCreator<T> extends Function<SearchDocument, CompletableFuture<T>> {}
}

View File

@ -20,7 +20,7 @@ import org.springframework.data.elasticsearch.core.convert.GeoConverters;
import org.springframework.data.elasticsearch.core.document.Document;
/**
* Interface definition for structures defined in <a href="https://geojson.org/>GeoJSON</a> format. copied from Spring
* Interface definition for structures defined in <a href="https://geojson.org/">GeoJSON</a> format. copied from Spring
* Data Mongodb
*
* @author Christoph Strobl

View File

@ -230,8 +230,7 @@ public class MappingBuilder {
boolean writeNestedProperties = !isRootObject && (isAnyPropertyAnnotatedWithField(entity) || nestedOrObjectField);
if (writeNestedProperties) {
String type = nestedOrObjectField ? fieldType.toString().toLowerCase()
: FieldType.Object.toString().toLowerCase();
String type = nestedOrObjectField ? fieldType.getMappedName() : FieldType.Object.getMappedName();
ObjectNode nestedObjectNode = objectMapper.createObjectNode();
nestedObjectNode.put(FIELD_PARAM_TYPE, type);
@ -247,9 +246,9 @@ public class MappingBuilder {
}
if (entity != null && entity.dynamic() != Dynamic.INHERIT) {
objectNode.put(TYPE_DYNAMIC, entity.dynamic().name().toLowerCase());
objectNode.put(TYPE_DYNAMIC, entity.dynamic().getMappedName());
} else if (dynamicMapping != null) {
objectNode.put(TYPE_DYNAMIC, dynamicMapping.value().name().toLowerCase());
objectNode.put(TYPE_DYNAMIC, dynamicMapping.value().getMappedName());
}
ObjectNode propertiesNode = objectNode.putObject(FIELD_PROPERTIES);
@ -418,7 +417,7 @@ public class MappingBuilder {
ObjectNode contextNode = contextsNode.addObject();
contextNode.put(FIELD_CONTEXT_NAME, context.name());
contextNode.put(FIELD_CONTEXT_TYPE, context.type().name().toLowerCase());
contextNode.put(FIELD_CONTEXT_TYPE, context.type().getMappedName());
if (context.precision().length() > 0) {
contextNode.put(FIELD_CONTEXT_PRECISION, context.precision());
@ -450,7 +449,7 @@ public class MappingBuilder {
}
propertiesNode.set(property.getFieldName(), objectMapper.createObjectNode() //
.put(FIELD_PARAM_TYPE, field.type().name().toLowerCase()) //
.put(FIELD_PARAM_TYPE, field.type().getMappedName()) //
.put(MAPPING_ENABLED, false) //
);
@ -479,9 +478,9 @@ public class MappingBuilder {
if (nestedOrObjectField) {
if (annotation.dynamic() != Dynamic.INHERIT) {
fieldNode.put(TYPE_DYNAMIC, annotation.dynamic().name().toLowerCase());
fieldNode.put(TYPE_DYNAMIC, annotation.dynamic().getMappedName());
} else if (dynamicMapping != null) {
fieldNode.put(TYPE_DYNAMIC, dynamicMapping.value().name().toLowerCase());
fieldNode.put(TYPE_DYNAMIC, dynamicMapping.value().getMappedName());
}
}
}
@ -530,9 +529,9 @@ public class MappingBuilder {
if (nestedOrObjectField) {
if (annotation.mainField().dynamic() != Dynamic.INHERIT) {
mainFieldNode.put(TYPE_DYNAMIC, annotation.mainField().dynamic().name().toLowerCase());
mainFieldNode.put(TYPE_DYNAMIC, annotation.mainField().dynamic().getMappedName());
} else if (dynamicMapping != null) {
mainFieldNode.put(TYPE_DYNAMIC, dynamicMapping.value().name().toLowerCase());
mainFieldNode.put(TYPE_DYNAMIC, dynamicMapping.value().getMappedName());
}
}

View File

@ -237,9 +237,9 @@ public final class MappingParameters {
}
if (type != FieldType.Auto) {
objectNode.put(FIELD_PARAM_TYPE, type.toString().toLowerCase());
objectNode.put(FIELD_PARAM_TYPE, type.getMappedName());
if (type == FieldType.Date) {
if (type == FieldType.Date || type == FieldType.Date_Nanos || type == FieldType.Date_Range) {
List<String> formats = new ArrayList<>();
// built-in formats

View File

@ -24,7 +24,8 @@ package org.springframework.data.elasticsearch.core.mapping;
public interface PropertyValueConverter {
/**
* Converts a property value to an elasticsearch value.
* Converts a property value to an elasticsearch value. If the converter cannot convert the value, it must return a
* String representation.
*
* @param value the value to convert, must not be {@literal null}
* @return The elasticsearch property value, must not be {@literal null}

View File

@ -172,7 +172,7 @@ public class SimpleElasticsearchPersistentEntity<T> extends BasicPersistentEntit
@Override
public boolean writeTypeHints() {
boolean writeTypeHints = contextConfiguration.writeTypeHints;
boolean writeTypeHints = contextConfiguration.getWriteTypeHints();
if (document != null) {
switch (document.writeTypeHint()) {
@ -548,7 +548,7 @@ public class SimpleElasticsearchPersistentEntity<T> extends BasicPersistentEntit
/**
* Configuration settings passed in from the creating {@link SimpleElasticsearchMappingContext}.
*/
static class ContextConfiguration {
public static class ContextConfiguration {
private final FieldNamingStrategy fieldNamingStrategy;
private final boolean writeTypeHints;
@ -561,6 +561,10 @@ public class SimpleElasticsearchPersistentEntity<T> extends BasicPersistentEntit
public FieldNamingStrategy getFieldNamingStrategy() {
return fieldNamingStrategy;
}
public boolean getWriteTypeHints() {
return writeTypeHints;
}
}
@Override

View File

@ -954,7 +954,23 @@ public class Criteria {
/**
* @since 4.3
*/
NOT_EMPTY
NOT_EMPTY;
/**
* @return true if this key does not have an associated value
* @since 4.4
*/
public boolean hasNoValue() {
return this == OperationKey.EXISTS || this == OperationKey.EMPTY || this == OperationKey.NOT_EMPTY;
}
/**
* @return true if this key does have an associated value
* @since 4.4
*/
public boolean hasValue() {
return !hasNoValue();
}
}
/**
@ -967,9 +983,8 @@ public class Criteria {
protected CriteriaEntry(OperationKey key) {
boolean keyIsValid = key == OperationKey.EXISTS || key == OperationKey.EMPTY || key == OperationKey.NOT_EMPTY;
Assert.isTrue(keyIsValid,
"key must be OperationKey.EXISTS, OperationKey.EMPTY or OperationKey.EMPTY for this call");
Assert.isTrue(key.hasNoValue(),
"key must be OperationKey.EXISTS, OperationKey.EMPTY or OperationKey.NOT_EMPTY for this call");
this.key = key;
}

View File

@ -30,7 +30,7 @@ import org.springframework.lang.Nullable;
* @author Mohsin Husen
* @author Peter-Josef Meisch
* @author Farid Faoudi
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-update.html>docs</a>
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-update.html">docs</a>
*/
public class UpdateQuery {

View File

@ -82,8 +82,7 @@ abstract class AbstractReactiveElasticsearchRepositoryQuery implements Repositor
ResultProcessor processor = queryMethod.getResultProcessor().withDynamicProjection(parameterAccessor);
Query query = createQuery(
new ConvertingParameterAccessor(elasticsearchOperations.getElasticsearchConverter(), parameterAccessor));
Query query = createQuery(parameterAccessor);
if (queryMethod.hasAnnotatedHighlight()) {
query.setHighlightQuery(queryMethod.getAnnotatedHighlightQuery());

View File

@ -1,95 +0,0 @@
/*
* Copyright 2019-2021 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.Iterator;
import java.util.Optional;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
import org.springframework.lang.Nullable;
/**
* @author Christoph Strobl
* @author Peter-Josef Meisch
* @since 3.2
*/
public class ConvertingParameterAccessor implements ElasticsearchParameterAccessor {
private final ElasticsearchConverter converter;
private final ElasticsearchParameterAccessor delegate;
public ConvertingParameterAccessor(ElasticsearchConverter converter, ElasticsearchParameterAccessor delegate) {
this.converter = converter;
this.delegate = delegate;
}
@Override
public Object[] getValues() {
return delegate.getValues();
}
@Override
public Pageable getPageable() {
return delegate.getPageable();
}
@Override
public Sort getSort() {
return delegate.getSort();
}
@Override
public Optional<Class<?>> getDynamicProjection() {
return delegate.getDynamicProjection();
}
@Override
public Class<?> findDynamicProjection() {
return delegate.findDynamicProjection();
}
@Override
public Object getBindableValue(int index) {
return getConvertedValue(delegate.getBindableValue(index));
}
@Override
public boolean hasBindableNullValue() {
return delegate.hasBindableNullValue();
}
@Override
public Iterator<Object> iterator() {
return delegate.iterator();
}
@Nullable
private Object getConvertedValue(Object value) {
if (value == null) {
return "null";
}
if (converter.getConversionService().canConvert(value.getClass(), String.class)) {
return converter.getConversionService().convert(value, String.class);
}
return value.toString();
}
}

View File

@ -128,6 +128,9 @@ public class ElasticsearchPartQuery extends AbstractElasticsearchRepositoryQuery
} else if (tree.isCountProjection()) {
result = elasticsearchOperations.count(query, clazz, index);
} else if (tree.isExistsProjection()) {
long count = elasticsearchOperations.count(query, clazz, index);
result = count > 0;
} else {
result = elasticsearchOperations.searchOne(query, clazz, index);
}

View File

@ -46,7 +46,11 @@ final public class StringQueryUtil {
String placeholder = Pattern.quote(matcher.group()) + "(?!\\d+)";
int index = NumberUtils.parseNumber(matcher.group(1), Integer.class);
result = result.replaceAll(placeholder, Matcher.quoteReplacement(getParameterWithIndex(accessor, index)));
String replacement = Matcher.quoteReplacement(getParameterWithIndex(accessor, index));
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
result = result.replaceAll("\\\\([^\"'])", "\\\\\\\\$1");
}
return result;
}
@ -56,7 +60,6 @@ final public class StringQueryUtil {
Object parameter = accessor.getBindableValue(index);
String parameterValue = "null";
// noinspection ConstantConditions
if (parameter != null) {
parameterValue = convert(parameter);

View File

@ -1,4 +1,4 @@
Spring Data Elasticsearch 4.3 GA (2021.1.0)
Spring Data Elasticsearch 4.3.10 (2021.1.10)
Copyright (c) [2013-2021] Pivotal Software, Inc.
This product is licensed to you under the Apache License, Version 2.0 (the "License").
@ -22,6 +22,16 @@ conditions of the subcomponent's license, as noted in the LICENSE file.

View File

@ -375,7 +375,7 @@ public class ReactiveElasticsearchClientUnitTests {
});
URI uri = hostProvider.when(HOST).captureUri();
assertThat(uri.getRawPath()).isEqualTo("/twitter/_doc/10/_create");
assertThat(uri.getRawPath()).isEqualTo("/twitter/_create/10");
}
@Test // DATAES-488
@ -439,7 +439,7 @@ public class ReactiveElasticsearchClientUnitTests {
hostProvider.when(HOST) //
.receiveUpdateOk();
client.update(new UpdateRequest("twitter", "doc", "1").doc(Collections.singletonMap("user", "cstrobl"))).then() //
client.update(new UpdateRequest("twitter", "1").doc(Collections.singletonMap("user", "cstrobl"))).then() //
.as(StepVerifier::create) //
.verifyComplete();
@ -449,7 +449,7 @@ public class ReactiveElasticsearchClientUnitTests {
});
URI uri = hostProvider.when(HOST).captureUri();
assertThat(uri.getRawPath()).isEqualTo("/twitter/doc/1/_update");
assertThat(uri.getRawPath()).isEqualTo("/twitter/_update/1");
}
@Test // DATAES-488

View File

@ -124,7 +124,7 @@ public class ElasticsearchRestTemplateTests extends ElasticsearchTemplateTests {
assertThat(fetchSourceContext.excludes()).containsExactlyInAnyOrder("excl");
}
@Test // #1446
@Test // #1446, #2191
void shouldUseAllOptionsFromUpdateByQuery() throws JSONException {
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()) //
@ -154,9 +154,10 @@ public class ElasticsearchRestTemplateTests extends ElasticsearchTemplateTests {
" }" + " }" + '}';
// when
UpdateByQueryRequest request = getRequestFactory().updateByQueryRequest(updateQuery, IndexCoordinates.of("index"));
UpdateByQueryRequest request = getRequestFactory().updateByQueryRequest(updateQuery, IndexCoordinates.of("index1", "index2"));
// then
assertThat(request.indices()).containsExactlyInAnyOrder("index1", "index2");
assertThat(request).isNotNull();
assertThat(request.getSearchRequest().indicesOptions()).usingRecursiveComparison()
.isEqualTo(IndicesOptions.LENIENT_EXPAND_OPEN);

View File

@ -2783,6 +2783,33 @@ public abstract class ElasticsearchTemplateTests {
assertThat(searchHits.getSearchHit(1).getInnerHits("innerHits").getTotalHits()).isEqualTo(1);
}
@Test // #1997
@DisplayName("should return document with inner hits size zero")
void shouldReturnDocumentWithInnerHitsSizeZero() {
// given
SampleEntity sampleEntity = SampleEntity.builder().id(nextIdAsString()).message("message 1").rate(1)
.version(System.currentTimeMillis()).build();
List<IndexQuery> indexQueries = getIndexQueries(Arrays.asList(sampleEntity));
operations.bulkIndex(indexQueries, IndexCoordinates.of(indexNameProvider.indexName()));
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery())
.withCollapseBuilder(new CollapseBuilder("rate").setInnerHits(new InnerHitBuilder("innerHits").setSize(0)))
.build();
// when
SearchHits<SampleEntity> searchHits = operations.search(searchQuery, SampleEntity.class,
IndexCoordinates.of(indexNameProvider.indexName()));
// then
assertThat(searchHits).isNotNull();
assertThat(searchHits.getTotalHits()).isEqualTo(1);
assertThat(searchHits.getSearchHits()).hasSize(1);
assertThat(searchHits.getSearchHit(0).getContent().getMessage()).isEqualTo("message 1");
}
private IndexQuery getIndexQuery(SampleEntity sampleEntity) {
return new IndexQueryBuilder().withId(sampleEntity.getId()).withObject(sampleEntity)
.withVersion(sampleEntity.getVersion()).build();

View File

@ -33,6 +33,8 @@ import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.json.JSONException;
import org.junit.jupiter.api.BeforeEach;
@ -66,6 +68,9 @@ import org.springframework.data.elasticsearch.core.geo.GeoJsonPolygon;
import org.springframework.data.elasticsearch.core.geo.GeoPoint;
import org.springframework.data.elasticsearch.core.mapping.PropertyValueConverter;
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
import org.springframework.data.elasticsearch.core.query.Criteria;
import org.springframework.data.elasticsearch.core.query.CriteriaQuery;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.core.query.SeqNoPrimaryTerm;
import org.springframework.data.geo.Box;
import org.springframework.data.geo.Circle;
@ -620,7 +625,8 @@ public class MappingElasticsearchConverterUnitTests {
assertThat(target.address).isEqualTo(bigBunsCafe);
}
@Test // DATAES-716
@Test
// DATAES-716
void shouldWriteLocalDate() throws JSONException {
Person person = new Person();
person.setId("4711");
@ -662,7 +668,8 @@ public class MappingElasticsearchConverterUnitTests {
assertEquals(expected, json, false);
}
@Test // DATAES-716
@Test
// DATAES-716
void shouldReadLocalDate() {
Document document = Document.create();
document.put("id", "4711");
@ -692,7 +699,8 @@ public class MappingElasticsearchConverterUnitTests {
assertThat(entity.getDates()).hasSize(2).containsExactly(LocalDate.of(2020, 9, 15), LocalDate.of(2019, 5, 1));
}
@Test // DATAES-763
@Test
// DATAES-763
void writeEntityWithMapDataType() {
Notification notification = new Notification();
@ -709,7 +717,8 @@ public class MappingElasticsearchConverterUnitTests {
assertThat(document).isEqualTo(notificationAsMap);
}
@Test // DATAES-763
@Test
// DATAES-763
void readEntityWithMapDataType() {
Document document = Document.create();
@ -726,7 +735,8 @@ public class MappingElasticsearchConverterUnitTests {
assertThat(notification.params.get("content")).isNull();
}
@Test // DATAES-795
@Test
// DATAES-795
void readGenericMapWithSimpleTypes() {
Map<String, Object> mapWithSimpleValues = new HashMap<>();
mapWithSimpleValues.put("int", 1);
@ -740,7 +750,8 @@ public class MappingElasticsearchConverterUnitTests {
assertThat(wrapper.getSchemaLessObject()).isEqualTo(mapWithSimpleValues);
}
@Test // DATAES-797
@Test
// DATAES-797
void readGenericListWithMaps() {
Map<String, Object> simpleMap = new HashMap<>();
simpleMap.put("int", 1);
@ -758,7 +769,8 @@ public class MappingElasticsearchConverterUnitTests {
assertThat(wrapper.getSchemaLessObject()).isEqualTo(mapWithSimpleList);
}
@Test // DATAES-799
@Test
// DATAES-799
void shouldNotWriteSeqNoPrimaryTermProperty() {
EntityWithSeqNoPrimaryTerm entity = new EntityWithSeqNoPrimaryTerm();
entity.seqNoPrimaryTerm = new SeqNoPrimaryTerm(1L, 2L);
@ -769,7 +781,8 @@ public class MappingElasticsearchConverterUnitTests {
assertThat(document).doesNotContainKey("seqNoPrimaryTerm");
}
@Test // DATAES-799
@Test
// DATAES-799
void shouldNotReadSeqNoPrimaryTermProperty() {
Document document = Document.create().append("seqNoPrimaryTerm", emptyMap());
@ -778,7 +791,8 @@ public class MappingElasticsearchConverterUnitTests {
assertThat(entity.seqNoPrimaryTerm).isNull();
}
@Test // DATAES-845
@Test
// DATAES-845
void shouldWriteCollectionsWithNullValues() throws JSONException {
EntityWithListProperty entity = new EntityWithListProperty();
entity.setId("42");
@ -795,7 +809,8 @@ public class MappingElasticsearchConverterUnitTests {
assertEquals(expected, json, false);
}
@Test // DATAES-857
@Test
// DATAES-857
void shouldWriteEntityWithListOfGeoPoints() throws JSONException {
GeoPointListEntity entity = new GeoPointListEntity();
@ -824,7 +839,8 @@ public class MappingElasticsearchConverterUnitTests {
assertEquals(expected, json, false);
}
@Test // DATAES-857
@Test
// DATAES-857
void shouldReadEntityWithListOfGeoPoints() {
String json = "{\n" + //
@ -849,7 +865,8 @@ public class MappingElasticsearchConverterUnitTests {
assertThat(entity.locations).containsExactly(new GeoPoint(12.34, 23.45), new GeoPoint(34.56, 45.67));
}
@Test // DATAES-865
@Test
// DATAES-865
void shouldWriteEntityWithMapAsObject() throws JSONException {
Map<String, Object> map = new LinkedHashMap<>();
@ -1496,6 +1513,315 @@ public class MappingElasticsearchConverterUnitTests {
assertThat(entity.getDontConvert()).isEqualTo("Monty Python's Flying Circus");
}
@Test // #2080
@DisplayName("should not try to call property converter on updating criteria exists")
void shouldNotTryToCallPropertyConverterOnUpdatingCriteriaExists() {
// don't care if the query makes no sense, we just add all criteria without values
Query query = new CriteriaQuery(Criteria.where("fieldWithClassBasedConverter").exists().empty().notEmpty());
mappingElasticsearchConverter.updateQuery(query, EntityWithCustomValueConverters.class);
}
@Test // #2280
@DisplayName("should read a single String into a List property")
void shouldReadASingleStringIntoAListProperty() {
String json = "{\n" + //
" \"stringList\": \"foo\"\n" + //
"}\n"; //
Document source = Document.parse(json);
EntityWithCollections entity = mappingElasticsearchConverter.read(EntityWithCollections.class, source);
assertThat(entity.getStringList()).containsExactly("foo");
}
@Test // #2280
@DisplayName("should read a String array into a List property")
void shouldReadAStringArrayIntoAListProperty() {
String json = "{\n" + //
" \"stringList\": [\"foo\", \"bar\"]\n" + //
"}\n"; //
Document source = Document.parse(json);
EntityWithCollections entity = mappingElasticsearchConverter.read(EntityWithCollections.class, source);
assertThat(entity.getStringList()).containsExactly("foo", "bar");
}
@Test // #2280
@DisplayName("should read a single String into a Set property")
void shouldReadASingleStringIntoASetProperty() {
String json = "{\n" + //
" \"stringSet\": \"foo\"\n" + //
"}\n"; //
Document source = Document.parse(json);
EntityWithCollections entity = mappingElasticsearchConverter.read(EntityWithCollections.class, source);
assertThat(entity.getStringSet()).containsExactly("foo");
}
@Test // #2280
@DisplayName("should read a String array into a Set property")
void shouldReadAStringArrayIntoASetProperty() {
String json = "{\n" + //
" \"stringSet\": [\n" + //
" \"foo\",\n" + //
" \"bar\"\n" + //
" ]\n" + //
"}\n"; //
Document source = Document.parse(json);
EntityWithCollections entity = mappingElasticsearchConverter.read(EntityWithCollections.class, source);
assertThat(entity.getStringSet()).containsExactly("foo", "bar");
}
@Test // #2280
@DisplayName("should read a single object into a List property")
void shouldReadASingleObjectIntoAListProperty() {
String json = "{\n" + //
" \"childrenList\": {\n" + //
" \"name\": \"child\"\n" + //
" }\n" + //
"}\n"; //
Document source = Document.parse(json);
EntityWithCollections entity = mappingElasticsearchConverter.read(EntityWithCollections.class, source);
assertThat(entity.getChildrenList()).hasSize(1);
// noinspection ConstantConditions
assertThat(entity.getChildrenList().get(0).getName()).isEqualTo("child");
}
@Test // #2280
@DisplayName("should read an object array into a List property")
void shouldReadAnObjectArrayIntoAListProperty() {
String json = " {\n" + //
" \"childrenList\": [\n" + //
" {\n" + //
" \"name\": \"child1\"\n" + //
" },\n" + //
" {\n" + //
" \"name\": \"child2\"\n" + //
" }\n" + //
" ]\n" + //
" }\n"; //
Document source = Document.parse(json);
EntityWithCollections entity = mappingElasticsearchConverter.read(EntityWithCollections.class, source);
assertThat(entity.getChildrenList()).hasSize(2);
// noinspection ConstantConditions
assertThat(entity.getChildrenList().get(0).getName()).isEqualTo("child1");
assertThat(entity.getChildrenList().get(1).getName()).isEqualTo("child2");
}
@Test // #2280
@DisplayName("should read a single object into a Set property")
void shouldReadASingleObjectIntoASetProperty() {
String json = "{\n" + //
" \"childrenSet\": {\n" + //
" \"name\": \"child\"\n" + //
" }\n" + //
"}\n"; //
Document source = Document.parse(json);
EntityWithCollections entity = mappingElasticsearchConverter.read(EntityWithCollections.class, source);
assertThat(entity.getChildrenSet()).hasSize(1);
// noinspection ConstantConditions
assertThat(entity.getChildrenSet().iterator().next().getName()).isEqualTo("child");
}
@Test // #2280
@DisplayName("should read an object array into a Set property")
void shouldReadAnObjectArrayIntoASetProperty() {
String json = "{\n" + //
" \"childrenSet\": [\n" + //
" {\n" + //
" \"name\": \"child1\"\n" + //
" },\n" + //
" {\n" + //
" \"name\": \"child2\"\n" + //
" }\n" + //
" ]\n" + //
"}\n"; //
Document source = Document.parse(json);
EntityWithCollections entity = mappingElasticsearchConverter.read(EntityWithCollections.class, source);
assertThat(entity.getChildrenSet()).hasSize(2);
// noinspection ConstantConditions
List<String> names = entity.getChildrenSet().stream().map(EntityWithCollections.Child::getName)
.collect(Collectors.toList());
assertThat(names).containsExactlyInAnyOrder("child1", "child2");
}
@Test // #2280
@DisplayName("should read a single String into a List property immutable")
void shouldReadASingleStringIntoAListPropertyImmutable() {
String json = "{\n" + //
" \"stringList\": \"foo\"\n" + //
"}\n"; //
Document source = Document.parse(json);
ImmutableEntityWithCollections entity = mappingElasticsearchConverter.read(ImmutableEntityWithCollections.class,
source);
assertThat(entity.getStringList()).containsExactly("foo");
}
@Test // #2280
@DisplayName("should read a String array into a List property immutable")
void shouldReadAStringArrayIntoAListPropertyImmutable() {
String json = "{\n" + //
" \"stringList\": [\n" + //
" \"foo\",\n" + //
" \"bar\"\n" + //
" ]\n" + //
"}\n"; //
Document source = Document.parse(json);
ImmutableEntityWithCollections entity = mappingElasticsearchConverter.read(ImmutableEntityWithCollections.class,
source);
assertThat(entity.getStringList()).containsExactly("foo", "bar");
}
@Test // #2280
@DisplayName("should read a single String into a Set property immutable")
void shouldReadASingleStringIntoASetPropertyImmutable() {
String json = "{\n" + //
" \"stringSet\": \"foo\"\n" + //
"}\n"; //
Document source = Document.parse(json);
ImmutableEntityWithCollections entity = mappingElasticsearchConverter.read(ImmutableEntityWithCollections.class,
source);
assertThat(entity.getStringSet()).containsExactly("foo");
}
@Test // #2280
@DisplayName("should read a String array into a Set property immutable")
void shouldReadAStringArrayIntoASetPropertyImmutable() {
String json = "{\n" + //
" \"stringSet\": [\n" + //
" \"foo\",\n" + //
" \"bar\"\n" + //
" ]\n" + //
"}\n"; //
Document source = Document.parse(json);
ImmutableEntityWithCollections entity = mappingElasticsearchConverter.read(ImmutableEntityWithCollections.class,
source);
assertThat(entity.getStringSet()).containsExactly("foo", "bar");
}
@Test // #2280
@DisplayName("should read a single object into a List property immutable")
void shouldReadASingleObjectIntoAListPropertyImmutable() {
String json = "{\n" + //
" \"childrenList\": {\n" + //
" \"name\": \"child\"\n" + //
" }\n" + //
"}\n"; //
Document source = Document.parse(json);
ImmutableEntityWithCollections entity = mappingElasticsearchConverter.read(ImmutableEntityWithCollections.class,
source);
assertThat(entity.getChildrenList()).hasSize(1);
// noinspection ConstantConditions
assertThat(entity.getChildrenList().get(0).getName()).isEqualTo("child");
}
@Test // #2280
@DisplayName("should read an object array into a List property immutable")
void shouldReadAnObjectArrayIntoAListPropertyImmutable() {
String json = "{\n" + //
" \"childrenList\": [\n" + //
" {\n" + //
" \"name\": \"child1\"\n" + //
" },\n" + //
" {\n" + //
" \"name\": \"child2\"\n" + //
" }\n" + //
" ]\n" + //
"}\n"; //
Document source = Document.parse(json);
ImmutableEntityWithCollections entity = mappingElasticsearchConverter.read(ImmutableEntityWithCollections.class,
source);
assertThat(entity.getChildrenList()).hasSize(2);
// noinspection ConstantConditions
assertThat(entity.getChildrenList().get(0).getName()).isEqualTo("child1");
assertThat(entity.getChildrenList().get(1).getName()).isEqualTo("child2");
}
@Test // #2280
@DisplayName("should read a single object into a Set property immutable")
void shouldReadASingleObjectIntoASetPropertyImmutable() {
String json = "{\n" + //
" \"childrenSet\": {\n" + //
" \"name\": \"child\"\n" + //
" }\n" + //
"}\n"; //
Document source = Document.parse(json);
ImmutableEntityWithCollections entity = mappingElasticsearchConverter.read(ImmutableEntityWithCollections.class,
source);
assertThat(entity.getChildrenSet()).hasSize(1);
// noinspection ConstantConditions
assertThat(entity.getChildrenSet().iterator().next().getName()).isEqualTo("child");
}
@Test // #2280
@DisplayName("should read an object array into a Set property immutable")
void shouldReadAnObjectArrayIntoASetPropertyImmutable() {
String json = "{\n" + //
" \"childrenSet\": [\n" + //
" {\n" + //
" \"name\": \"child1\"\n" + //
" },\n" + //
" {\n" + //
" \"name\": \"child2\"\n" + //
" }\n" + //
" ]\n" + //
"}\n"; //
Document source = Document.parse(json);
ImmutableEntityWithCollections entity = mappingElasticsearchConverter.read(ImmutableEntityWithCollections.class,
source);
assertThat(entity.getChildrenSet()).hasSize(2);
// noinspection ConstantConditions
List<String> names = entity.getChildrenSet().stream().map(ImmutableEntityWithCollections.Child::getName)
.collect(Collectors.toList());
assertThat(names).containsExactlyInAnyOrder("child1", "child2");
}
private Map<String, Object> writeToMap(Object source) {
Document sink = Document.create();
@ -1548,12 +1874,15 @@ public class MappingElasticsearchConverterUnitTests {
}
static class Person {
@Nullable @Id String id;
@Nullable
@Id String id;
@Nullable String name;
@Nullable @Field(name = "first-name") String firstName;
@Nullable @Field(name = "last-name") String lastName;
@Nullable @Field(name = "birth-date", type = FieldType.Date, format = {},
pattern = "dd.MM.uuuu") LocalDate birthDate;
@Nullable
@Field(name = "first-name") String firstName;
@Nullable
@Field(name = "last-name") String lastName;
@Nullable
@Field(name = "birth-date", type = FieldType.Date, format = {}, pattern = "dd.MM.uuuu") LocalDate birthDate;
@Nullable Gender gender;
@Nullable Address address;
@Nullable List<Person> coWorkers;
@ -1662,34 +1991,46 @@ public class MappingElasticsearchConverterUnitTests {
@Override
public boolean equals(Object o) {
if (this == o)
if (this == o) {
return true;
if (o == null || getClass() != o.getClass())
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Person person = (Person) o;
if (id != null ? !id.equals(person.id) : person.id != null)
if (id != null ? !id.equals(person.id) : person.id != null) {
return false;
if (name != null ? !name.equals(person.name) : person.name != null)
}
if (name != null ? !name.equals(person.name) : person.name != null) {
return false;
if (firstName != null ? !firstName.equals(person.firstName) : person.firstName != null)
}
if (firstName != null ? !firstName.equals(person.firstName) : person.firstName != null) {
return false;
if (lastName != null ? !lastName.equals(person.lastName) : person.lastName != null)
}
if (lastName != null ? !lastName.equals(person.lastName) : person.lastName != null) {
return false;
if (birthDate != null ? !birthDate.equals(person.birthDate) : person.birthDate != null)
}
if (birthDate != null ? !birthDate.equals(person.birthDate) : person.birthDate != null) {
return false;
if (gender != person.gender)
}
if (gender != person.gender) {
return false;
if (address != null ? !address.equals(person.address) : person.address != null)
}
if (address != null ? !address.equals(person.address) : person.address != null) {
return false;
if (coWorkers != null ? !coWorkers.equals(person.coWorkers) : person.coWorkers != null)
}
if (coWorkers != null ? !coWorkers.equals(person.coWorkers) : person.coWorkers != null) {
return false;
if (inventoryList != null ? !inventoryList.equals(person.inventoryList) : person.inventoryList != null)
}
if (inventoryList != null ? !inventoryList.equals(person.inventoryList) : person.inventoryList != null) {
return false;
}
if (shippingAddresses != null ? !shippingAddresses.equals(person.shippingAddresses)
: person.shippingAddresses != null)
: person.shippingAddresses != null) {
return false;
}
return inventoryMap != null ? inventoryMap.equals(person.inventoryMap) : person.inventoryMap == null;
}
@ -1711,8 +2052,10 @@ public class MappingElasticsearchConverterUnitTests {
}
static class LocalDatesEntity {
@Nullable @Id private String id;
@Nullable @Field(name = "dates", type = FieldType.Date, format = DateFormat.custom,
@Nullable
@Id private String id;
@Nullable
@Field(name = "dates", type = FieldType.Date, format = DateFormat.custom,
pattern = "dd.MM.uuuu") private List<LocalDate> dates;
@Nullable
@ -1774,15 +2117,18 @@ public class MappingElasticsearchConverterUnitTests {
@Override
public boolean equals(Object o) {
if (this == o)
if (this == o) {
return true;
if (o == null || getClass() != o.getClass())
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Gun gun = (Gun) o;
if (shotsPerMagazine != gun.shotsPerMagazine)
if (shotsPerMagazine != gun.shotsPerMagazine) {
return false;
}
return label.equals(gun.label);
}
@ -1808,10 +2154,12 @@ public class MappingElasticsearchConverterUnitTests {
@Override
public boolean equals(Object o) {
if (this == o)
if (this == o) {
return true;
if (!(o instanceof Grenade))
}
if (!(o instanceof Grenade)) {
return false;
}
Grenade grenade = (Grenade) o;
@ -1844,17 +2192,21 @@ public class MappingElasticsearchConverterUnitTests {
@Override
public boolean equals(Object o) {
if (this == o)
if (this == o) {
return true;
if (!(o instanceof Rifle))
}
if (!(o instanceof Rifle)) {
return false;
}
Rifle rifle = (Rifle) o;
if (Double.compare(rifle.weight, weight) != 0)
if (Double.compare(rifle.weight, weight) != 0) {
return false;
if (maxShotsPerMagazine != rifle.maxShotsPerMagazine)
}
if (maxShotsPerMagazine != rifle.maxShotsPerMagazine) {
return false;
}
return label.equals(rifle.label);
}
@ -1885,10 +2237,12 @@ public class MappingElasticsearchConverterUnitTests {
@Override
public boolean equals(Object o) {
if (this == o)
if (this == o) {
return true;
if (!(o instanceof ShotGun))
}
if (!(o instanceof ShotGun)) {
return false;
}
ShotGun shotGun = (ShotGun) o;
@ -1935,17 +2289,21 @@ public class MappingElasticsearchConverterUnitTests {
@Override
public boolean equals(Object o) {
if (this == o)
if (this == o) {
return true;
if (!(o instanceof Address))
}
if (!(o instanceof Address)) {
return false;
}
Address address = (Address) o;
if (location != null ? !location.equals(address.location) : address.location != null)
if (location != null ? !location.equals(address.location) : address.location != null) {
return false;
if (street != null ? !street.equals(address.street) : address.street != null)
}
if (street != null ? !street.equals(address.street) : address.street != null) {
return false;
}
return city != null ? city.equals(address.city) : address.city == null;
}
@ -1972,10 +2330,12 @@ public class MappingElasticsearchConverterUnitTests {
@Override
public boolean equals(Object o) {
if (this == o)
if (this == o) {
return true;
if (!(o instanceof Place))
}
if (!(o instanceof Place)) {
return false;
}
Place place = (Place) o;
@ -2111,16 +2471,20 @@ public class MappingElasticsearchConverterUnitTests {
@org.springframework.data.elasticsearch.annotations.Document(indexName = "test-index-geo-core-entity-mapper")
static class GeoEntity {
@Nullable @Id private String id;
@Nullable
@Id private String id;
// geo shape - Spring Data
@Nullable private Box box;
@Nullable private Circle circle;
@Nullable private Polygon polygon;
// geo point - Custom implementation + Spring Data
@Nullable @GeoPointField private Point pointA;
@Nullable
@GeoPointField private Point pointA;
@Nullable private GeoPoint pointB;
@Nullable @GeoPointField private String pointC;
@Nullable @GeoPointField private double[] pointD;
@Nullable
@GeoPointField private String pointC;
@Nullable
@GeoPointField private double[] pointD;
@Nullable
public String getId() {
@ -2224,7 +2588,8 @@ public class MappingElasticsearchConverterUnitTests {
}
static class EntityWithListProperty {
@Nullable @Id private String id;
@Nullable
@Id private String id;
@Nullable private List<String> values;
@Nullable
@ -2270,7 +2635,8 @@ public class MappingElasticsearchConverterUnitTests {
}
static class EntityWithObject {
@Nullable @Id private String id;
@Nullable
@Id private String id;
@Nullable private Object content;
@Nullable
@ -2293,9 +2659,12 @@ public class MappingElasticsearchConverterUnitTests {
}
static class EntityWithNullField {
@Nullable @Id private String id;
@Nullable @Field(type = FieldType.Text) private String notSaved;
@Nullable @Field(type = FieldType.Text, storeNullValue = true) private String saved;
@Nullable
@Id private String id;
@Nullable
@Field(type = FieldType.Text) private String notSaved;
@Nullable
@Field(type = FieldType.Text, storeNullValue = true) private String saved;
@Nullable
public String getId() {
@ -2328,9 +2697,12 @@ public class MappingElasticsearchConverterUnitTests {
private static class ElectricCar extends Car {}
private static class PersonWithCars {
@Id @Nullable String id;
@Field(type = FieldType.Text) @Nullable private String name;
@Field(type = FieldType.Nested) @Nullable private List<? extends Car> cars;
@Id
@Nullable String id;
@Field(type = FieldType.Text)
@Nullable private String name;
@Field(type = FieldType.Nested)
@Nullable private List<? extends Car> cars;
@Nullable
public String getId() {
@ -2361,9 +2733,12 @@ public class MappingElasticsearchConverterUnitTests {
}
private static class EntityWithCustomValueConverters {
@Nullable @Id private String id;
@Nullable @ValueConverter(ClassBasedValueConverter.class) private String fieldWithClassBasedConverter;
@Nullable @ValueConverter(EnumBasedValueConverter.class) private String fieldWithEnumBasedConverter;
@Nullable
@Id private String id;
@Nullable
@ValueConverter(ClassBasedValueConverter.class) private String fieldWithClassBasedConverter;
@Nullable
@ValueConverter(EnumBasedValueConverter.class) private String fieldWithEnumBasedConverter;
@Nullable private String dontConvert;
@Nullable
@ -2430,11 +2805,133 @@ public class MappingElasticsearchConverterUnitTests {
}
}
private static class EntityWithCollections {
@Field(type = FieldType.Keyword)
@Nullable private List<String> stringList;
@Field(type = FieldType.Keyword)
@Nullable private Set<String> stringSet;
@Field(type = FieldType.Object)
@Nullable private List<Child> childrenList;
@Field(type = FieldType.Object)
@Nullable private Set<Child> childrenSet;
@Nullable
public List<String> getStringList() {
return stringList;
}
public void setStringList(@Nullable List<String> stringList) {
this.stringList = stringList;
}
@Nullable
public Set<String> getStringSet() {
return stringSet;
}
public void setStringSet(@Nullable Set<String> stringSet) {
this.stringSet = stringSet;
}
@Nullable
public List<Child> getChildrenList() {
return childrenList;
}
public void setChildrenList(@Nullable List<Child> childrenList) {
this.childrenList = childrenList;
}
@Nullable
public Set<Child> getChildrenSet() {
return childrenSet;
}
public void setChildrenSet(@Nullable Set<Child> childrenSet) {
this.childrenSet = childrenSet;
}
public static class Child {
@Field(type = FieldType.Keyword)
@Nullable private String name;
@Nullable
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
private static final class ImmutableEntityWithCollections {
@Field(type = FieldType.Keyword)
@Nullable private List<String> stringList;
@Field(type = FieldType.Keyword)
@Nullable private Set<String> stringSet;
@Field(type = FieldType.Object)
@Nullable private List<Child> childrenList;
@Field(type = FieldType.Object)
@Nullable private Set<Child> childrenSet;
public ImmutableEntityWithCollections(@Nullable List<String> stringList, @Nullable Set<String> stringSet,
@Nullable List<Child> childrenList, @Nullable Set<Child> childrenSet) {
this.stringList = stringList;
this.stringSet = stringSet;
this.childrenList = childrenList;
this.childrenSet = childrenSet;
}
@Nullable
public List<String> getStringList() {
return stringList;
}
@Nullable
public Set<String> getStringSet() {
return stringSet;
}
@Nullable
public List<Child> getChildrenList() {
return childrenList;
}
@Nullable
public Set<Child> getChildrenSet() {
return childrenSet;
}
public static class Child {
@Field(type = FieldType.Keyword)
@Nullable private String name;
public Child(@Nullable String name) {
this.name = name;
}
@Nullable
public String getName() {
return name;
}
}
}
// endregion
private static String reverse(Object o) {
Assert.notNull(o, "o must not be null");
return new StringBuilder().append(o.toString()).reverse().toString();
}
// endregion
}

View File

@ -0,0 +1,82 @@
/*
* Copyright 2021 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 static org.assertj.core.api.Assertions.*;
import static org.junit.jupiter.params.provider.Arguments.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Stream;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Named;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.data.elasticsearch.annotations.DateFormat;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
import org.springframework.data.elasticsearch.core.mapping.PropertyValueConverter;
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchPersistentEntity;
import org.springframework.lang.Nullable;
/**
* @author Peter-Josef Meisch
*/
public class PropertyValueConvertersUnitTests {
@ParameterizedTest(name = "{0}") // #2018
@MethodSource("propertyValueConverters")
@DisplayName("should return original object on write if it cannot be converted")
void shouldReturnOriginalObjectOnWriteIfItCannotBeConverted(PropertyValueConverter converter) {
NoConverterForThisClass value = new NoConverterForThisClass();
Object written = converter.write(value);
assertThat(written).isEqualTo(value.toString());
}
static Stream<Arguments> propertyValueConverters() {
SimpleElasticsearchMappingContext context = new SimpleElasticsearchMappingContext();
SimpleElasticsearchPersistentEntity<?> persistentEntity = context
.getRequiredPersistentEntity(NoConverterForThisClass.class);
ElasticsearchPersistentProperty persistentProperty = persistentEntity.getRequiredPersistentProperty("property");
List<PropertyValueConverter> converters = new ArrayList<>();
converters.add(new DatePropertyValueConverter(persistentProperty,
Collections.singletonList(ElasticsearchDateConverter.of(DateFormat.basic_date))));
converters.add(new DateRangePropertyValueConverter(persistentProperty,
Collections.singletonList(ElasticsearchDateConverter.of(DateFormat.basic_date))));
converters.add(new NumberRangePropertyValueConverter(persistentProperty));
converters.add(new TemporalPropertyValueConverter(persistentProperty,
Collections.singletonList(ElasticsearchDateConverter.of(DateFormat.basic_date))));
converters.add(new TemporalRangePropertyValueConverter(persistentProperty,
Collections.singletonList(ElasticsearchDateConverter.of(DateFormat.basic_date))));
return converters.stream().map(propertyValueConverter -> arguments(
Named.of(propertyValueConverter.getClass().getSimpleName(), propertyValueConverter)));
}
static class NoConverterForThisClass {
@SuppressWarnings("unused")
@Nullable Long property;
}
}

View File

@ -38,10 +38,14 @@ import java.util.Objects;
import java.util.Set;
import org.assertj.core.data.Percentage;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.*;
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
@ -57,6 +61,7 @@ import org.springframework.data.elasticsearch.core.query.SeqNoPrimaryTerm;
import org.springframework.data.elasticsearch.core.suggest.Completion;
import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration;
import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest;
import org.springframework.data.elasticsearch.utils.IndexNameProvider;
import org.springframework.data.geo.Box;
import org.springframework.data.geo.Circle;
import org.springframework.data.geo.Point;
@ -79,10 +84,25 @@ import org.springframework.test.context.ContextConfiguration;
* @author Morgan Lutz
*/
@SpringIntegrationTest
@ContextConfiguration(classes = { ElasticsearchRestTemplateConfiguration.class })
@ContextConfiguration(classes = { MappingBuilderIntegrationTests.Config.class })
public class MappingBuilderIntegrationTests extends MappingContextBaseTests {
@Configuration
@Import({ ElasticsearchRestTemplateConfiguration.class })
static class Config {
@Bean
IndexNameProvider indexNameProvider() {
return new IndexNameProvider("mapping-builder");
}
}
@Autowired private ElasticsearchOperations operations;
@Autowired IndexNameProvider indexNameProvider;
@BeforeEach
public void before() {
indexNameProvider.increment();
}
@Test
@Order(java.lang.Integer.MAX_VALUE)
@ -132,8 +152,7 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests {
@Test // DATAES-76
public void shouldAddSampleInheritedEntityDocumentToIndex() {
// given
IndexCoordinates index = IndexCoordinates.of("test-index-sample-inherited-mapping-builder");
IndexOperations indexOps = operations.indexOps(index);
IndexOperations indexOps = operations.indexOps(SampleInheritedEntity.class);
// when
indexOps.create();
@ -142,11 +161,10 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests {
String message = "msg";
String id = "abc";
operations.index(new SampleInheritedEntityBuilder(id).createdDate(createdDate).message(message).buildIndex(),
index);
operations.indexOps(SampleInheritedEntity.class).refresh();
IndexCoordinates.of(indexNameProvider.indexName()));
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).build();
SearchHits<SampleInheritedEntity> result = operations.search(searchQuery, SampleInheritedEntity.class, index);
SearchHits<SampleInheritedEntity> result = operations.search(searchQuery, SampleInheritedEntity.class);
// then
assertThat(result).hasSize(1);
@ -163,7 +181,7 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests {
IndexOperations indexOpsUser = operations.indexOps(User.class);
indexOpsUser.create();
indexOpsUser.putMapping(User.class);
indexNameProvider.increment();
IndexOperations indexOpsGroup = operations.indexOps(Group.class);
indexOpsGroup.create();
indexOpsGroup.putMapping(Group.class);
@ -336,11 +354,19 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests {
}
@Test // #2024
@DisplayName("should map all field type values")
void shouldMapAllFieldTypeValues() {
operations.indexOps(EntityWithAllTypes.class).createWithMapping();
}
// region entities
@Document(indexName = "ignore-above-index")
@Document(indexName = "#{@indexNameProvider.indexName()}")
static class IgnoreAboveEntity {
@Nullable @Id private String id;
@Nullable @Field(type = FieldType.Keyword, ignoreAbove = 10) private String message;
@Nullable
@Id private String id;
@Nullable
@Field(type = FieldType.Keyword, ignoreAbove = 10) private String message;
@Nullable
public String getId() {
@ -365,57 +391,76 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests {
@Document(indexName = "fieldname-index")
static class IdEntity {
@Nullable @Id @Field("id-property") private String id;
@Nullable
@Id
@Field("id-property") private String id;
}
@Document(indexName = "fieldname-index")
@Document(indexName = "#{@indexNameProvider.indexName()}")
static class TextEntity {
@Nullable @Id @Field("id-property") private String id;
@Nullable
@Id
@Field("id-property") private String id;
@Field(name = "text-property", type = FieldType.Text) //
@Nullable private String textProperty;
}
@Document(indexName = "fieldname-index")
@Document(indexName = "#{@indexNameProvider.indexName()}")
static class MappingEntity {
@Nullable @Id @Field("id-property") private String id;
@Nullable
@Id
@Field("id-property") private String id;
@Field("mapping-property") @Mapping(mappingPath = "/mappings/test-field-analyzed-mappings.json") //
@Field("mapping-property")
@Mapping(mappingPath = "/mappings/test-field-analyzed-mappings.json") //
@Nullable private byte[] mappingProperty;
}
@Document(indexName = "fieldname-index")
@Document(indexName = "#{@indexNameProvider.indexName()}")
static class GeoPointEntity {
@Nullable @Id @Field("id-property") private String id;
@Nullable
@Id
@Field("id-property") private String id;
@Nullable @Field("geopoint-property") private GeoPoint geoPoint;
@Nullable
@Field("geopoint-property") private GeoPoint geoPoint;
}
@Document(indexName = "fieldname-index")
@Document(indexName = "#{@indexNameProvider.indexName()}")
static class CircularEntity {
@Nullable @Id @Field("id-property") private String id;
@Nullable
@Id
@Field("id-property") private String id;
@Nullable @Field(name = "circular-property", type = FieldType.Object, ignoreFields = { "circular-property" }) //
@Nullable
@Field(name = "circular-property", type = FieldType.Object, ignoreFields = { "circular-property" }) //
private CircularEntity circularProperty;
}
@Document(indexName = "fieldname-index")
@Document(indexName = "#{@indexNameProvider.indexName()}")
static class CompletionEntity {
@Nullable @Id @Field("id-property") private String id;
@Nullable
@Id
@Field("id-property") private String id;
@Nullable @Field("completion-property") @CompletionField(maxInputLength = 100) //
@Nullable
@Field("completion-property")
@CompletionField(maxInputLength = 100) //
private Completion suggest;
}
@Document(indexName = "fieldname-index")
@Document(indexName = "#{@indexNameProvider.indexName()}")
static class MultiFieldEntity {
@Nullable @Id @Field("id-property") private String id;
@Nullable
@Id
@Field("id-property") private String id;
@Nullable //
@MultiField(mainField = @Field(name = "main-field", type = FieldType.Text, analyzer = "whitespace"),
@ -425,13 +470,17 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests {
}
}
@Document(indexName = "test-index-book-mapping-builder")
@Document(indexName = "#{@indexNameProvider.indexName()}")
static class Book {
@Nullable @Id private String id;
@Nullable
@Id private String id;
@Nullable private String name;
@Nullable @Field(type = FieldType.Object) private Author author;
@Nullable @Field(type = FieldType.Nested) private Map<Integer, Collection<String>> buckets = new HashMap<>();
@Nullable @MultiField(mainField = @Field(type = FieldType.Text, analyzer = "whitespace"),
@Nullable
@Field(type = FieldType.Object) private Author author;
@Nullable
@Field(type = FieldType.Nested) private Map<Integer, Collection<String>> buckets = new HashMap<>();
@Nullable
@MultiField(mainField = @Field(type = FieldType.Text, analyzer = "whitespace"),
otherFields = { @InnerField(suffix = "prefix", type = FieldType.Text, analyzer = "stop",
searchAnalyzer = "standard") }) private String description;
@ -481,11 +530,12 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests {
}
}
@Document(indexName = "test-index-simple-recursive-mapping-builder")
@Document(indexName = "#{@indexNameProvider.indexName()}")
static class SimpleRecursiveEntity {
@Nullable @Id private String id;
@Nullable @Field(type = FieldType.Object,
ignoreFields = { "circularObject" }) private SimpleRecursiveEntity circularObject;
@Nullable
@Id private String id;
@Nullable
@Field(type = FieldType.Object, ignoreFields = { "circularObject" }) private SimpleRecursiveEntity circularObject;
@Nullable
public String getId() {
@ -506,12 +556,16 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests {
}
}
@Document(indexName = "test-copy-to-mapping-builder")
@Document(indexName = "#{@indexNameProvider.indexName()}")
static class CopyToEntity {
@Nullable @Id private String id;
@Nullable @Field(type = FieldType.Keyword, copyTo = "name") private String firstName;
@Nullable @Field(type = FieldType.Keyword, copyTo = "name") private String lastName;
@Nullable @Field(type = FieldType.Keyword) private String name;
@Nullable
@Id private String id;
@Nullable
@Field(type = FieldType.Keyword, copyTo = "name") private String firstName;
@Nullable
@Field(type = FieldType.Keyword, copyTo = "name") private String lastName;
@Nullable
@Field(type = FieldType.Keyword) private String name;
@Nullable
public String getId() {
@ -550,12 +604,15 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests {
}
}
@Document(indexName = "test-index-normalizer-mapping-builder")
@Document(indexName = "#{@indexNameProvider.indexName()}")
@Setting(settingPath = "/settings/test-normalizer.json")
static class NormalizerEntity {
@Nullable @Id private String id;
@Nullable @Field(type = FieldType.Keyword, normalizer = "lower_case_normalizer") private String name;
@Nullable @MultiField(mainField = @Field(type = FieldType.Text), otherFields = { @InnerField(suffix = "lower_case",
@Nullable
@Id private String id;
@Nullable
@Field(type = FieldType.Keyword, normalizer = "lower_case_normalizer") private String name;
@Nullable
@MultiField(mainField = @Field(type = FieldType.Text), otherFields = { @InnerField(suffix = "lower_case",
type = FieldType.Keyword, normalizer = "lower_case_normalizer") }) private String description;
@Nullable
@ -610,10 +667,11 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests {
}
}
@Document(indexName = "test-index-sample-inherited-mapping-builder")
@Document(indexName = "#{@indexNameProvider.indexName()}")
static class SampleInheritedEntity extends AbstractInheritedEntity {
@Nullable @Field(type = Text, index = false, store = true, analyzer = "standard") private String message;
@Nullable
@Field(type = Text, index = false, store = true, analyzer = "standard") private String message;
@Nullable
public String getMessage() {
@ -656,11 +714,13 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests {
}
}
@Document(indexName = "test-index-stock-mapping-builder")
@Document(indexName = "#{@indexNameProvider.indexName()}")
static class StockPrice {
@Nullable @Id private String id;
@Nullable
@Id private String id;
@Nullable private String symbol;
@Nullable @Field(type = FieldType.Double) private BigDecimal price;
@Nullable
@Field(type = FieldType.Double) private BigDecimal price;
@Nullable
public String getId() {
@ -691,8 +751,10 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests {
}
static class AbstractInheritedEntity {
@Nullable @Id private String id;
@Nullable @Field(type = FieldType.Date, format = DateFormat.date_time, index = false) private Date createdDate;
@Nullable
@Id private String id;
@Nullable
@Field(type = FieldType.Date, format = DateFormat.date_time, index = false) private Date createdDate;
@Nullable
public String getId() {
@ -713,21 +775,27 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests {
}
}
@Document(indexName = "test-index-geo-mapping-builder")
@Document(indexName = "#{@indexNameProvider.indexName()}")
static class GeoEntity {
@Nullable @Id private String id;
@Nullable
@Id private String id;
// geo shape - Spring Data
@Nullable private Box box;
@Nullable private Circle circle;
@Nullable private Polygon polygon;
// geo point - Custom implementation + Spring Data
@Nullable @GeoPointField private Point pointA;
@Nullable
@GeoPointField private Point pointA;
@Nullable private GeoPoint pointB;
@Nullable @GeoPointField private String pointC;
@Nullable @GeoPointField private double[] pointD;
@Nullable
@GeoPointField private String pointC;
@Nullable
@GeoPointField private double[] pointD;
// geo shape, until e have the classes for this, us a strng
@Nullable @GeoShapeField private String shape1;
@Nullable @GeoShapeField(coerce = true, ignoreMalformed = true, ignoreZValue = false,
@Nullable
@GeoShapeField private String shape1;
@Nullable
@GeoShapeField(coerce = true, ignoreMalformed = true, ignoreZValue = false,
orientation = GeoShapeField.Orientation.clockwise) private String shape2;
@Nullable
@ -821,17 +889,21 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests {
}
}
@Document(indexName = "test-index-user-mapping-builder")
@Document(indexName = "#{@indexNameProvider.indexName()}")
static class User {
@Nullable @Id private String id;
@Nullable
@Id private String id;
@Field(type = FieldType.Nested, ignoreFields = { "users" }) private Set<Group> groups = new HashSet<>();
}
@Document(indexName = "test-index-group-mapping-builder")
@Document(indexName = "#{@indexNameProvider.indexName()}")
static class Group {
@Nullable @Id String id;
@Nullable
@Id String id;
@Field(type = FieldType.Nested, ignoreFields = { "groups" }) private Set<User> users = new HashSet<>();
}
@ -848,11 +920,14 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests {
}
}
@Document(indexName = "completion")
@Document(indexName = "#{@indexNameProvider.indexName()}")
static class CompletionDocument {
@Nullable @Id private String id;
@Nullable @CompletionField(contexts = { @CompletionContext(name = "location",
type = CompletionContext.ContextMappingType.GEO, path = "proppath") }) private Completion suggest;
@Nullable
@Id private String id;
@Nullable
@CompletionField(contexts = { @CompletionContext(name = "location", type = CompletionContext.ContextMappingType.GEO,
path = "proppath") }) private Completion suggest;
@Nullable
public String getId() {
@ -873,9 +948,10 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests {
}
}
@Document(indexName = "test-index-entity-with-seq-no-primary-term-mapping-builder")
@Document(indexName = "#{@indexNameProvider.indexName()}")
static class EntityWithSeqNoPrimaryTerm {
@Nullable @Field(type = Object) private SeqNoPrimaryTerm seqNoPrimaryTerm;
@Nullable
@Field(type = Object) private SeqNoPrimaryTerm seqNoPrimaryTerm;
@Nullable
public SeqNoPrimaryTerm getSeqNoPrimaryTerm() {
@ -888,10 +964,14 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests {
}
static class RankFeatureEntity {
@Nullable @Id private String id;
@Nullable @Field(type = FieldType.Rank_Feature) private Integer pageRank;
@Nullable @Field(type = FieldType.Rank_Feature, positiveScoreImpact = false) private Integer urlLength;
@Nullable @Field(type = FieldType.Rank_Features) private Map<String, Integer> topics;
@Nullable
@Id private String id;
@Nullable
@Field(type = FieldType.Rank_Feature) private Integer pageRank;
@Nullable
@Field(type = FieldType.Rank_Feature, positiveScoreImpact = false) private Integer urlLength;
@Nullable
@Field(type = FieldType.Rank_Features) private Map<String, Integer> topics;
@Nullable
public String getId() {
@ -930,18 +1010,25 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests {
}
}
@Document(indexName = "termvectors-test")
@Document(indexName = "#{@indexNameProvider.indexName()}")
static class TermVectorFieldEntity {
@Nullable @Id private String id;
@Nullable @Field(type = FieldType.Text, termVector = TermVector.no) private String no;
@Nullable @Field(type = FieldType.Text, termVector = TermVector.yes) private String yes;
@Nullable @Field(type = FieldType.Text, termVector = TermVector.with_positions) private String with_positions;
@Nullable @Field(type = FieldType.Text, termVector = TermVector.with_offsets) private String with_offsets;
@Nullable @Field(type = FieldType.Text,
termVector = TermVector.with_positions_offsets) private String with_positions_offsets;
@Nullable @Field(type = FieldType.Text,
@Nullable
@Id private String id;
@Nullable
@Field(type = FieldType.Text, termVector = TermVector.no) private String no;
@Nullable
@Field(type = FieldType.Text, termVector = TermVector.yes) private String yes;
@Nullable
@Field(type = FieldType.Text, termVector = TermVector.with_positions) private String with_positions;
@Nullable
@Field(type = FieldType.Text, termVector = TermVector.with_offsets) private String with_offsets;
@Nullable
@Field(type = FieldType.Text, termVector = TermVector.with_positions_offsets) private String with_positions_offsets;
@Nullable
@Field(type = FieldType.Text,
termVector = TermVector.with_positions_payloads) private String with_positions_payloads;
@Nullable @Field(type = FieldType.Text,
@Nullable
@Field(type = FieldType.Text,
termVector = TermVector.with_positions_offsets_payloads) private String with_positions_offsets_payloads;
@Nullable
@ -1017,10 +1104,12 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests {
}
}
@Document(indexName = "wildcard-test")
@Document(indexName = "#{@indexNameProvider.indexName()}")
static class WildcardEntity {
@Nullable @Field(type = Wildcard) private String wildcardWithoutParams;
@Nullable @Field(type = Wildcard, nullValue = "WILD", ignoreAbove = 42) private String wildcardWithParams;
@Nullable
@Field(type = Wildcard) private String wildcardWithoutParams;
@Nullable
@Field(type = Wildcard, nullValue = "WILD", ignoreAbove = 42) private String wildcardWithParams;
@Nullable
public String getWildcardWithoutParams() {
@ -1041,11 +1130,13 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests {
}
}
@Document(indexName = "disabled-entity-mapping")
@Document(indexName = "#{@indexNameProvider.indexName()}")
@Mapping(enabled = false)
static class DisabledMappingEntity {
@Nullable @Id private String id;
@Nullable @Field(type = Text) private String text;
@Nullable
@Id private String id;
@Nullable
@Field(type = Text) private String text;
@Nullable
public String getId() {
@ -1068,9 +1159,13 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests {
@Document(indexName = "disabled-property-mapping")
static class DisabledMappingProperty {
@Nullable @Id private String id;
@Nullable @Field(type = Text) private String text;
@Nullable @Mapping(enabled = false) @Field(type = Object) private Object object;
@Nullable
@Id private String id;
@Nullable
@Field(type = Text) private String text;
@Nullable
@Mapping(enabled = false)
@Field(type = Object) private Object object;
@Nullable
public String getId() {
@ -1100,10 +1195,12 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests {
}
}
@Document(indexName = "densevector-test")
@Document(indexName = "#{@indexNameProvider.indexName()}")
static class DenseVectorEntity {
@Nullable @Id private String id;
@Nullable @Field(type = Dense_Vector, dims = 3) private float[] dense_vector;
@Nullable
@Id private String id;
@Nullable
@Field(type = Dense_Vector, dims = 3) private float[] dense_vector;
@Nullable
public String getId() {
@ -1124,15 +1221,19 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests {
}
}
@Document(indexName = "dynamic-mapping-annotation")
@Document(indexName = "#{@indexNameProvider.indexName()}")
@DynamicMapping(DynamicMappingValue.False)
static class DynamicMappingAnnotationEntity {
@Nullable @DynamicMapping(DynamicMappingValue.Strict) @Field(type = FieldType.Object) private Author author;
@Nullable @DynamicMapping(DynamicMappingValue.False) @Field(
type = FieldType.Object) private Map<String, Object> objectMap;
@Nullable @DynamicMapping(DynamicMappingValue.False) @Field(
type = FieldType.Nested) private List<Map<String, Object>> nestedObjectMap;
@Nullable
@DynamicMapping(DynamicMappingValue.Strict)
@Field(type = FieldType.Object) private Author author;
@Nullable
@DynamicMapping(DynamicMappingValue.False)
@Field(type = FieldType.Object) private Map<String, Object> objectMap;
@Nullable
@DynamicMapping(DynamicMappingValue.False)
@Field(type = FieldType.Nested) private List<Map<String, Object>> nestedObjectMap;
@Nullable
public Author getAuthor() {
@ -1144,57 +1245,140 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests {
}
}
@Document(indexName = "dynamic-mapping", dynamic = Dynamic.FALSE)
@Document(indexName = "#{@indexNameProvider.indexName()}")
static class DynamicMappingEntity {
@Nullable @Field(type = FieldType.Object) //
@Nullable
@Field(type = FieldType.Object) //
private Map<String, Object> objectInherit;
@Nullable @Field(type = FieldType.Object, dynamic = Dynamic.FALSE) //
@Nullable
@Field(type = FieldType.Object, dynamic = Dynamic.FALSE) //
private Map<String, Object> objectFalse;
@Nullable @Field(type = FieldType.Object, dynamic = Dynamic.TRUE) //
@Nullable
@Field(type = FieldType.Object, dynamic = Dynamic.TRUE) //
private Map<String, Object> objectTrue;
@Nullable @Field(type = FieldType.Object, dynamic = Dynamic.STRICT) //
@Nullable
@Field(type = FieldType.Object, dynamic = Dynamic.STRICT) //
private Map<String, Object> objectStrict;
@Nullable @Field(type = FieldType.Object, dynamic = Dynamic.RUNTIME) //
@Nullable
@Field(type = FieldType.Object, dynamic = Dynamic.RUNTIME) //
private Map<String, Object> objectRuntime;
@Nullable @Field(type = FieldType.Nested) //
@Nullable
@Field(type = FieldType.Nested) //
private List<Map<String, Object>> nestedObjectInherit;
@Nullable @Field(type = FieldType.Nested, dynamic = Dynamic.FALSE) //
@Nullable
@Field(type = FieldType.Nested, dynamic = Dynamic.FALSE) //
private List<Map<String, Object>> nestedObjectFalse;
@Nullable @Field(type = FieldType.Nested, dynamic = Dynamic.TRUE) //
@Nullable
@Field(type = FieldType.Nested, dynamic = Dynamic.TRUE) //
private List<Map<String, Object>> nestedObjectTrue;
@Nullable @Field(type = FieldType.Nested, dynamic = Dynamic.STRICT) //
@Nullable
@Field(type = FieldType.Nested, dynamic = Dynamic.STRICT) //
private List<Map<String, Object>> nestedObjectStrict;
@Nullable @Field(type = FieldType.Nested, dynamic = Dynamic.RUNTIME) //
@Nullable
@Field(type = FieldType.Nested, dynamic = Dynamic.RUNTIME) //
private List<Map<String, Object>> nestedObjectRuntime;
}
@Document(indexName = "dynamic-detection-mapping-true")
@Document(indexName = "#{@indexNameProvider.indexName()}")
@Mapping(dateDetection = Mapping.Detection.TRUE, numericDetection = Mapping.Detection.TRUE,
dynamicDateFormats = { "MM/dd/yyyy" })
private static class DynamicDetectionMapping {
@Id @Nullable private String id;
@Id
@Nullable private String id;
}
@Document(indexName = "runtime-fields")
@Document(indexName = "#{@indexNameProvider.indexName()}")
@Mapping(runtimeFieldsPath = "/mappings/runtime-fields.json")
private static class RuntimeFieldEntity {
@Id @Nullable private String id;
@Field(type = Date, format = DateFormat.epoch_millis, name = "@timestamp") @Nullable private Instant timestamp;
@Id
@Nullable private String id;
@Field(type = Date, format = DateFormat.epoch_millis, name = "@timestamp")
@Nullable private Instant timestamp;
}
@Document(indexName = "fields-excluded-from-source")
@Document(indexName = "#{@indexNameProvider.indexName()}")
private static class ExcludedFieldEntity {
@Id @Nullable private String id;
@Nullable @Field(name = "excluded-date", type = Date, format = DateFormat.date,
@Id
@Nullable private String id;
@Nullable
@Field(name = "excluded-date", type = Date, format = DateFormat.date,
excludeFromSource = true) private LocalDate excludedDate;
@Nullable @Field(type = Nested) private NestedExcludedFieldEntity nestedEntity;
@Nullable
@Field(type = Nested) private NestedExcludedFieldEntity nestedEntity;
}
private static class NestedExcludedFieldEntity {
@Nullable @Field(name = "excluded-text", type = Text, excludeFromSource = true) private String excludedText;
@Nullable
@Field(name = "excluded-text", type = Text, excludeFromSource = true) private String excludedText;
}
@Document(indexName = "#{@indexNameProvider.indexName()}")
private static class EntityWithAllTypes {
@Nullable
@Field(type = FieldType.Auto) String autoField;
@Nullable
@Field(type = FieldType.Text) String textField;
@Nullable
@Field(type = FieldType.Keyword) String keywordField;
@Nullable
@Field(type = FieldType.Long) String longField;
@Nullable
@Field(type = FieldType.Integer) String integerField;
@Nullable
@Field(type = FieldType.Short) String shortField;
@Nullable
@Field(type = FieldType.Byte) String byteField;
@Nullable
@Field(type = FieldType.Double) String doubleField;
@Nullable
@Field(type = FieldType.Float) String floatField;
@Nullable
@Field(type = FieldType.Half_Float) String halfFloatField;
@Nullable
@Field(type = FieldType.Scaled_Float) String scaledFloatField;
@Nullable
@Field(type = FieldType.Date) String dateField;
@Nullable
@Field(type = FieldType.Date_Nanos) String dateNanosField;
@Nullable
@Field(type = FieldType.Boolean) String booleanField;
@Nullable
@Field(type = FieldType.Binary) String binaryField;
@Nullable
@Field(type = FieldType.Integer_Range) String integerRangeField;
@Nullable
@Field(type = FieldType.Float_Range) String floatRangeField;
@Nullable
@Field(type = FieldType.Long_Range) String longRangeField;
@Nullable
@Field(type = FieldType.Double_Range) String doubleRangeField;
@Nullable
@Field(type = FieldType.Date_Range) String dateRangeField;
@Nullable
@Field(type = FieldType.Ip_Range) String ipRangeField;
@Nullable
@Field(type = FieldType.Object) String objectField;
@Nullable
@Field(type = FieldType.Nested) String nestedField;
@Nullable
@Field(type = FieldType.Ip) String ipField;
@Nullable
@Field(type = FieldType.TokenCount, analyzer = "standard") String tokenCountField;
@Nullable
@Field(type = FieldType.Percolator) String percolatorField;
@Nullable
@Field(type = FieldType.Flattened) String flattenedField;
@Nullable
@Field(type = FieldType.Search_As_You_Type) String searchAsYouTypeField;
@Nullable
@Field(type = FieldType.Rank_Feature) String rankFeatureField;
@Nullable
@Field(type = FieldType.Rank_Features) String rankFeaturesField;
@Nullable
@Field(type = FieldType.Wildcard) String wildcardField;
@Nullable
@Field(type = FieldType.Dense_Vector, dims = 1) String denseVectorField;
}
// endregion
}

View File

@ -42,6 +42,7 @@ import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Transient;
import org.springframework.data.elasticsearch.annotations.*;
import org.springframework.data.elasticsearch.core.MappingContextBaseTests;
import org.springframework.data.elasticsearch.core.Range;
import org.springframework.data.elasticsearch.core.geo.GeoPoint;
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
import org.springframework.data.elasticsearch.core.query.SeqNoPrimaryTerm;
@ -721,6 +722,29 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests {
assertEquals(expected, mapping, false);
}
@Test // #2102
@DisplayName("should write date formats for date range fields")
void shouldWriteDateFormatsForDateRangeFields() throws JSONException {
String expected = "{\n" + //
" \"properties\": {\n" + //
" \"_class\": {\n" + //
" \"type\": \"keyword\",\n" + //
" \"index\": false,\n" + //
" \"doc_values\": false\n" + //
" },\n" + //
" \"field2\": {\n" + //
" \"type\": \"date_range\",\n" + //
" \"format\": \"date\"\n" + //
" }\n" + //
" }\n" + //
"}\n"; //
String mapping = getMappingBuilder().buildPropertyMapping(DateRangeEntity.class);
assertEquals(expected, mapping, false);
}
@Test // #1454
@DisplayName("should write type hints when context is configured to do so")
void shouldWriteTypeHintsWhenContextIsConfiguredToDoSo() throws JSONException {
@ -1911,6 +1935,31 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests {
}
}
private static class DateRangeEntity {
@Nullable
@Id private String id;
@Nullable
@Field(type = Date_Range, format = DateFormat.date) private Range<LocalDateTime> field2;
@Nullable
public String getId() {
return id;
}
public void setId(@Nullable String id) {
this.id = id;
}
@Nullable
public Range<LocalDateTime> getField2() {
return field2;
}
public void setField2(@Nullable Range<LocalDateTime> field2) {
this.field2 = field2;
}
}
@Document(indexName = "magazine")
private static class Magazine {
@Id @Nullable private String id;

View File

@ -265,23 +265,30 @@ public class SimpleElasticsearchPersistentPropertyUnitTests {
// region entities
static class FieldNameProperty {
@Nullable @Field(name = "by-name") String fieldProperty;
@Nullable
@Field(name = "by-name") String fieldProperty;
}
static class FieldValueProperty {
@Nullable @Field(value = "by-value") String fieldProperty;
@Nullable
@Field(value = "by-value") String fieldProperty;
}
static class MultiFieldProperty {
@Nullable @MultiField(mainField = @Field("mainfield"),
@Nullable
@MultiField(mainField = @Field("mainfield"),
otherFields = { @InnerField(suffix = "suff", type = FieldType.Keyword) }) String mainfieldProperty;
}
static class DatesProperty {
@Nullable @Field(type = FieldType.Date, format = {}, pattern = "dd.MM.uuuu") LocalDate localDate;
@Nullable @Field(type = FieldType.Date, format = DateFormat.basic_date_time) LocalDateTime localDateTime;
@Nullable @Field(type = FieldType.Date, format = DateFormat.basic_date_time) Date legacyDate;
@Nullable @Field(type = FieldType.Date, format = {}, pattern = "dd.MM.uuuu") List<LocalDate> localDateList;
@Nullable
@Field(type = FieldType.Date, format = {}, pattern = "dd.MM.uuuu") LocalDate localDate;
@Nullable
@Field(type = FieldType.Date, format = DateFormat.basic_date_time) LocalDateTime localDateTime;
@Nullable
@Field(type = FieldType.Date, format = DateFormat.basic_date_time) Date legacyDate;
@Nullable
@Field(type = FieldType.Date, format = {}, pattern = "dd.MM.uuuu") List<LocalDate> localDateList;
}
static class SeqNoPrimaryTermProperty {
@ -344,8 +351,10 @@ public class SimpleElasticsearchPersistentPropertyUnitTests {
private static class EntityWithCustomValueConverters {
@Id private String id;
@Nullable @ValueConverter(ClassBasedValueConverter.class) private String fieldWithClassBasedConverter;
@Nullable @ValueConverter(EnumBasedValueConverter.class) private String fieldWithEnumBasedConverter;
@Nullable
@ValueConverter(ClassBasedValueConverter.class) private String fieldWithClassBasedConverter;
@Nullable
@ValueConverter(EnumBasedValueConverter.class) private String fieldWithEnumBasedConverter;
}
private static class ClassBasedValueConverter implements PropertyValueConverter {

View File

@ -1,5 +1,5 @@
/*
* Copyright 2021 the original author or authors.
* Copyright 2021-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -17,6 +17,7 @@ package org.springframework.data.elasticsearch.core.suggest;
import static org.assertj.core.api.Assertions.*;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import java.util.ArrayList;
@ -39,8 +40,8 @@ import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.IndexQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.core.suggest.response.CompletionSuggestion;
import org.springframework.data.elasticsearch.core.suggest.response.Suggest;
import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchRestTemplateConfiguration;
@ -86,13 +87,11 @@ public class ReactiveElasticsearchTemplateSuggestIntegrationTests {
@DisplayName("should find suggestions for given prefix completion")
void shouldFindSuggestionsForGivenPrefixCompletion() {
loadCompletionObjectEntities();
NativeSearchQuery query = new NativeSearchQueryBuilder().withSuggestBuilder(new SuggestBuilder()
.addSuggestion("test-suggest", SuggestBuilders.completionSuggestion("suggest").prefix("m", Fuzziness.AUTO)))
.build();
operations.suggest(query, CompletionEntity.class) //
loadCompletionObjectEntities() //
.flatMap(unused -> {
Query query = getSuggestQuery("test-suggest", "suggest", "m");
return operations.suggest(query, CompletionEntity.class);
}) //
.as(StepVerifier::create) //
.assertNext(suggest -> {
Suggest.Suggestion<? extends Suggest.Suggestion.Entry<? extends Suggest.Suggestion.Entry.Option>> suggestion = suggest
@ -105,13 +104,21 @@ public class ReactiveElasticsearchTemplateSuggestIntegrationTests {
assertThat(options).hasSize(2);
assertThat(options.get(0).getText()).isIn("Marchand", "Mohsin");
assertThat(options.get(1).getText()).isIn("Marchand", "Mohsin");
}) //
.verifyComplete();
}
protected Query getSuggestQuery(String suggestionName, String fieldName, String prefix) {
return new NativeSearchQueryBuilder() //
.withSuggestBuilder(new SuggestBuilder() //
.addSuggestion(suggestionName, //
SuggestBuilders.completionSuggestion(fieldName) //
.prefix(prefix, Fuzziness.AUTO))) //
.build(); //
}
// region helper functions
private void loadCompletionObjectEntities() {
private Mono<CompletionEntity> loadCompletionObjectEntities() {
CompletionEntity rizwan_idrees = new CompletionEntityBuilder("1").name("Rizwan Idrees")
.suggest(new String[] { "Rizwan Idrees" }).build();
@ -124,7 +131,7 @@ public class ReactiveElasticsearchTemplateSuggestIntegrationTests {
List<CompletionEntity> entities = new ArrayList<>(
Arrays.asList(rizwan_idrees, franck_marchand, mohsin_husen, artur_konczak));
IndexCoordinates index = IndexCoordinates.of(indexNameProvider.indexName());
operations.saveAll(entities, index).blockLast();
return operations.saveAll(entities, index).last();
}
// endregion
@ -132,11 +139,13 @@ public class ReactiveElasticsearchTemplateSuggestIntegrationTests {
@Document(indexName = "#{@indexNameProvider.indexName()}")
static class CompletionEntity {
@Nullable @Id private String id;
@Nullable
@Id private String id;
@Nullable private String name;
@Nullable @CompletionField(maxInputLength = 100) private Completion suggest;
@Nullable
@CompletionField(maxInputLength = 100) private Completion suggest;
private CompletionEntity() {}

View File

@ -129,6 +129,18 @@ public class ElasticsearchStringQueryUnitTests extends ElasticsearchStringQueryU
"{ 'bool' : { 'must' : { 'terms' : { 'name' : [\"hello \\\"Stranger\\\"\",\"Another string\"] } } } }");
}
@Test // #2326
@DisplayName("should escape backslashes in collection query parameters")
void shouldEscapeBackslashesInCollectionQueryParameters() throws NoSuchMethodException {
final List<String> parameters = Arrays.asList("param\\1", "param\\2");
List<String> params = new ArrayList<>(parameters);
org.springframework.data.elasticsearch.core.query.Query query = createQuery("findByNameIn", params);
assertThat(query).isInstanceOf(StringQuery.class);
assertThat(((StringQuery) query).getSource()).isEqualTo(
"{ 'bool' : { 'must' : { 'terms' : { 'name' : [\"param\\\\1\",\"param\\\\2\"] } } } }");
}
private org.springframework.data.elasticsearch.core.query.Query createQuery(String methodName, Object... args)
throws NoSuchMethodException {

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2021 the original author or authors.
* Copyright 2019-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -144,10 +144,37 @@ public class ReactiveElasticsearchStringQueryUnitTests extends ElasticsearchStri
.isEqualTo("{ 'bool' : { 'must' : { 'term' : { 'car' : 'Toyota-Prius' } } } }");
}
@Test // #2135
@DisplayName("should handle array-of-strings parameters correctly")
void shouldHandleArrayOfStringsParametersCorrectly() throws Exception {
List<String> otherNames = Arrays.asList("Wesley", "Emmett");
org.springframework.data.elasticsearch.core.query.Query query = createQuery("findByOtherNames", otherNames);
assertThat(query).isInstanceOf(StringQuery.class);
assertThat(((StringQuery) query).getSource())
.isEqualTo("{ 'bool' : { 'must' : { 'terms' : { 'otherNames' : [\"Wesley\",\"Emmett\"] } } } }");
}
@Test // #2135
@DisplayName("should handle array-of-Integers parameters correctly")
void shouldHandleArrayOfIntegerParametersCorrectly() throws Exception {
List<Integer> ages = Arrays.asList(42, 57);
org.springframework.data.elasticsearch.core.query.Query query = createQuery("findByAges", ages);
assertThat(query).isInstanceOf(StringQuery.class);
assertThat(((StringQuery) query).getSource())
.isEqualTo("{ 'bool' : { 'must' : { 'terms' : { 'ages' : [42,57] } } } }");
}
private org.springframework.data.elasticsearch.core.query.Query createQuery(String methodName, Object... args)
throws NoSuchMethodException {
Class<?>[] argTypes = Arrays.stream(args).map(Object::getClass).toArray(Class[]::new);
Class<?>[] argTypes = Arrays.stream(args).map(Object::getClass)
.map(clazz -> Collection.class.isAssignableFrom(clazz) ? List.class : clazz).toArray(Class[]::new);
ReactiveElasticsearchQueryMethod queryMethod = getQueryMethod(methodName, argTypes);
ReactiveElasticsearchStringQuery elasticsearchStringQuery = queryForMethod(queryMethod);
@ -195,6 +222,12 @@ public class ReactiveElasticsearchStringQueryUnitTests extends ElasticsearchStri
@Query("{ 'bool' : { 'must' : { 'term' : { 'car' : '?0' } } } }")
Mono<Person> findByCar(Car car);
@Query("{ 'bool' : { 'must' : { 'terms' : { 'otherNames' : ?0 } } } }")
Flux<Person> findByOtherNames(List<String> otherNames);
@Query("{ 'bool' : { 'must' : { 'terms' : { 'ages' : ?0 } } } }")
Flux<Person> findByAges(List<Integer> ages);
}
/**
@ -206,10 +239,13 @@ public class ReactiveElasticsearchStringQueryUnitTests extends ElasticsearchStri
@Document(indexName = "test-index-person-reactive-repository-string-query")
public class Person {
@Nullable @Id private String id;
@Nullable
@Id private String id;
@Nullable private String name;
@Nullable private List<String> otherNames;
@Nullable @Field(type = FieldType.Nested) private List<Car> car;
@Nullable @Field(type = FieldType.Nested, includeInParent = true) private List<Book> books;
@ -232,6 +268,15 @@ public class ReactiveElasticsearchStringQueryUnitTests extends ElasticsearchStri
this.name = name;
}
@Nullable
public List<String> getOtherNames() {
return otherNames;
}
public void setOtherNames(List<String> otherNames) {
this.otherNames = otherNames;
}
@Nullable
public List<Car> getCar() {
return car;

View File

@ -321,6 +321,17 @@ class QueryKeywordsTests {
assertThat(searchHits.getTotalHits()).isEqualTo(5);
}
@Test // #2162
@DisplayName("should run exists query")
void shouldRunExistsQuery() {
Boolean existsCaneSugar = repository.existsByText("Cane sugar");
Boolean existsSand = repository.existsByText("Sand");
assertThat(existsCaneSugar).isTrue();
assertThat(existsSand).isFalse();
}
@SuppressWarnings("unused")
@Document(indexName = "test-index-product-query-keywords")
static class Product {
@ -459,6 +470,8 @@ class QueryKeywordsTests {
SearchHits<Product> findByNameEmpty();
SearchHits<Product> findByNameNotEmpty();
Boolean existsByText(String text);
}
}

View File

@ -19,8 +19,11 @@ import static org.assertj.core.api.Assertions.*;
import static org.springframework.data.elasticsearch.annotations.FieldType.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import java.lang.Boolean;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Order;
@ -139,6 +142,21 @@ public class ReactiveQueryKeywordsIntegrationTests {
}).verifyComplete();
}
@Test // #2162
@DisplayName("should run exists query")
void shouldRunExistsQuery() {
loadEntities();
repository.existsByMessage("message") //
.as(StepVerifier::create) //
.expectNext(true) //
.verifyComplete();
repository.existsByMessage("without") //
.as(StepVerifier::create) //
.expectNext(false) //
.verifyComplete();
}
@SuppressWarnings("SpringDataMethodInconsistencyInspection")
interface SampleRepository extends ReactiveElasticsearchRepository<SampleEntity, String> {
Flux<SearchHit<SampleEntity>> findByMessageExists();
@ -150,6 +168,8 @@ public class ReactiveQueryKeywordsIntegrationTests {
Flux<SearchHit<SampleEntity>> findByMessageIsNotEmpty();
Flux<SearchHit<SampleEntity>> findByMessageIsEmpty();
Mono<Boolean> existsByMessage(String message);
}
private void loadEntities() {

View File

@ -19,6 +19,7 @@ import static org.assertj.core.api.Assertions.*;
import static org.springframework.data.elasticsearch.annotations.FieldType.*;
import static org.springframework.data.elasticsearch.core.query.Query.*;
import org.springframework.data.elasticsearch.annotations.FieldType;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
@ -91,7 +92,8 @@ class SimpleReactiveElasticsearchRepositoryTests {
operations.indexOps(IndexCoordinates.of(INDEX)).delete().block();
}
@Test // DATAES-519
@Test
// DATAES-519
void saveShouldSaveSingleEntity() {
repository.save(new SampleEntity()) //
@ -105,7 +107,8 @@ class SimpleReactiveElasticsearchRepositoryTests {
return operations.exists(id, IndexCoordinates.of(INDEX));
}
@Test // DATAES-519
@Test
// DATAES-519
void saveShouldComputeMultipleEntities() {
repository.saveAll(Arrays.asList(new SampleEntity(), new SampleEntity(), new SampleEntity()))
@ -118,46 +121,50 @@ class SimpleReactiveElasticsearchRepositoryTests {
.verifyComplete();
}
@Test // DATAES-519, DATAES-767, DATAES-822
@Test
// DATAES-519, DATAES-767, DATAES-822
void findByIdShouldErrorIfIndexDoesNotExist() {
repository.findById("id-two") //
.as(StepVerifier::create) //
.expectError(RestStatusException.class);
}
@Test // DATAES-519
@Test
// DATAES-519
void findShouldRetrieveSingleEntityById() {
bulkIndex(new SampleEntity("id-one"), //
new SampleEntity("id-two"), //
new SampleEntity("id-three")) //
.block();
.block();
repository.findById("id-two").as(StepVerifier::create)//
.consumeNextWith(it -> assertThat(it.getId()).isEqualTo("id-two")) //
.verifyComplete();
}
@Test // DATAES-519
@Test
// DATAES-519
void findByIdShouldCompleteIfNothingFound() {
bulkIndex(new SampleEntity("id-one"), //
new SampleEntity("id-two"), //
new SampleEntity("id-three")) //
.block();
.block();
repository.findById("does-not-exist").as(StepVerifier::create) //
.verifyComplete();
}
@Test // DATAES-720
@Test
// DATAES-720
void findAllShouldReturnAllElements() {
// make sure to be above the default page size of the Query interface
int count = DEFAULT_PAGE_SIZE * 2;
bulkIndex(IntStream.range(1, count + 1) //
.mapToObj(it -> new SampleEntity(String.valueOf(it))) //
.toArray(SampleEntity[]::new)) //
.block();
.block();
repository.findAll() //
.as(StepVerifier::create) //
@ -165,18 +172,20 @@ class SimpleReactiveElasticsearchRepositoryTests {
.verifyComplete();
}
@Test // DATAES-519
@Test
// DATAES-519
void findAllByIdByIdShouldCompleteIfIndexDoesNotExist() {
repository.findAllById(Arrays.asList("id-two", "id-two")).as(StepVerifier::create).verifyComplete();
}
@Test // DATAES-519
@Test
// DATAES-519
void findAllByIdShouldRetrieveMatchingDocuments() {
bulkIndex(new SampleEntity("id-one"), //
new SampleEntity("id-two"), //
new SampleEntity("id-three")) //
.block();
.block();
repository.findAllById(Arrays.asList("id-one", "id-two")) //
.as(StepVerifier::create)//
@ -185,26 +194,28 @@ class SimpleReactiveElasticsearchRepositoryTests {
.verifyComplete();
}
@Test // DATAES-519
@Test
// DATAES-519
void findAllByIdShouldCompleteWhenNothingFound() {
bulkIndex(new SampleEntity("id-one"), //
new SampleEntity("id-two"), //
new SampleEntity("id-three")) //
.block();
.block();
repository.findAllById(Arrays.asList("can't", "touch", "this")) //
.as(StepVerifier::create)//
.verifyComplete();
}
@Test // DATAES-717
@Test
// DATAES-717
void shouldReturnFluxOfSearchHit() {
bulkIndex(new SampleEntity("id-one", "message"), //
new SampleEntity("id-two", "message"), //
new SampleEntity("id-three", "message")) //
.block();
.block();
repository.queryAllByMessage("message") //
.as(StepVerifier::create) //
@ -213,13 +224,14 @@ class SimpleReactiveElasticsearchRepositoryTests {
.verifyComplete();
}
@Test // DATAES-717
@Test
// DATAES-717
void shouldReturnFluxOfSearchHitForStringQuery() {
bulkIndex(new SampleEntity("id-one", "message"), //
new SampleEntity("id-two", "message"), //
new SampleEntity("id-three", "message")) //
.block();
.block();
repository.queryByMessageWithString("message") //
.as(StepVerifier::create) //
@ -228,13 +240,14 @@ class SimpleReactiveElasticsearchRepositoryTests {
.verifyComplete();
}
@Test // DATAES-372
@Test
// DATAES-372
void shouldReturnHighlightsOnAnnotatedMethod() {
bulkIndex(new SampleEntity("id-one", "message"), //
new SampleEntity("id-two", "message"), //
new SampleEntity("id-three", "message")) //
.block();
.block();
repository.queryAllByMessage("message") //
.as(StepVerifier::create) //
@ -246,13 +259,14 @@ class SimpleReactiveElasticsearchRepositoryTests {
.verifyComplete();
}
@Test // DATAES-372
@Test
// DATAES-372
void shouldReturnHighlightsOnAnnotatedStringQueryMethod() {
bulkIndex(new SampleEntity("id-one", "message"), //
new SampleEntity("id-two", "message"), //
new SampleEntity("id-three", "message")) //
.block();
.block();
repository.queryByMessageWithString("message") //
.as(StepVerifier::create) //
@ -264,30 +278,33 @@ class SimpleReactiveElasticsearchRepositoryTests {
.verifyComplete();
}
@Test // DATAES-519, DATAES-767, DATAES-822
@Test
// DATAES-519, DATAES-767, DATAES-822
void countShouldErrorWhenIndexDoesNotExist() {
repository.count() //
.as(StepVerifier::create) //
.expectError(RestStatusException.class);
}
@Test // DATAES-519
@Test
// DATAES-519
void countShouldCountDocuments() {
bulkIndex(new SampleEntity("id-one"), //
new SampleEntity("id-two")) //
.block();
.block();
repository.count().as(StepVerifier::create).expectNext(2L).verifyComplete();
}
@Test // DATAES-519
@Test
// DATAES-519
void existsByIdShouldReturnTrueIfExists() {
bulkIndex(new SampleEntity("id-one", "message"), //
new SampleEntity("id-two", "test message"), //
new SampleEntity("id-three", "test test")) //
.block();
.block();
repository.existsById("id-two") //
.as(StepVerifier::create) //
@ -295,13 +312,14 @@ class SimpleReactiveElasticsearchRepositoryTests {
.verifyComplete();
}
@Test // DATAES-519
@Test
// DATAES-519
void existsByIdShouldReturnFalseIfNotExists() {
bulkIndex(new SampleEntity("id-one", "message"), //
new SampleEntity("id-two", "test message"), //
new SampleEntity("id-three", "test test")) //
.block();
.block();
repository.existsById("wrecking ball") //
.as(StepVerifier::create) //
@ -309,7 +327,8 @@ class SimpleReactiveElasticsearchRepositoryTests {
.verifyComplete();
}
@Test // DATAES-519
@Test
// DATAES-519
void countShouldCountMatchingDocuments() {
bulkIndex(new SampleEntity("id-one", "message"), //
@ -336,13 +355,14 @@ class SimpleReactiveElasticsearchRepositoryTests {
.verifyComplete();
}
@Test // DATAES-519
@Test
// DATAES-519
void existsShouldReturnTrueIfExists() {
bulkIndex(new SampleEntity("id-one", "message"), //
new SampleEntity("id-two", "test message"), //
new SampleEntity("id-three", "test test")) //
.block();
.block();
repository.existsAllByMessage("message") //
.as(StepVerifier::create) //
@ -350,13 +370,14 @@ class SimpleReactiveElasticsearchRepositoryTests {
.verifyComplete();
}
@Test // DATAES-519
@Test
// DATAES-519
void existsShouldReturnFalseIfNotExists() {
bulkIndex(new SampleEntity("id-one", "message"), //
new SampleEntity("id-two", "test message"), //
new SampleEntity("id-three", "test test")) //
.block();
.block();
repository.existsAllByMessage("these days") //
.as(StepVerifier::create) //
@ -364,24 +385,27 @@ class SimpleReactiveElasticsearchRepositoryTests {
.verifyComplete();
}
@Test // DATAES-519
@Test
// DATAES-519
void deleteByIdShouldCompleteIfNothingDeleted() {
bulkIndex(new SampleEntity("id-one"), //
new SampleEntity("id-two")) //
.block();
.block();
repository.deleteById("does-not-exist").as(StepVerifier::create).verifyComplete();
}
@Test // DATAES-519, DATAES-767, DATAES-822, DATAES-678
@Test
// DATAES-519, DATAES-767, DATAES-822, DATAES-678
void deleteByIdShouldCompleteWhenIndexDoesNotExist() {
repository.deleteById("does-not-exist") //
.as(StepVerifier::create) //
.verifyComplete();
}
@Test // DATAES-519
@Test
// DATAES-519
void deleteByIdShouldDeleteEntry() {
SampleEntity toBeDeleted = new SampleEntity("id-two");
@ -393,19 +417,22 @@ class SimpleReactiveElasticsearchRepositoryTests {
assertThat(documentWithIdExistsInIndex(toBeDeleted.getId()).block()).isFalse();
}
@Test // DATAES-976
@Test
// DATAES-976
void deleteAllByIdShouldDeleteEntry() {
SampleEntity toBeDeleted = new SampleEntity("id-two");
bulkIndex(new SampleEntity("id-one"), toBeDeleted) //
.block();
repository.deleteAllById(Collections.singletonList(toBeDeleted.getId())).as(StepVerifier::create).verifyComplete();
repository.deleteAllById(Collections.singletonList(toBeDeleted.getId())).as(StepVerifier::create)
.verifyComplete();
assertThat(documentWithIdExistsInIndex(toBeDeleted.getId()).block()).isFalse();
}
@Test // DATAES-519
@Test
// DATAES-519
void deleteShouldDeleteEntry() {
SampleEntity toBeDeleted = new SampleEntity("id-two");
@ -417,7 +444,8 @@ class SimpleReactiveElasticsearchRepositoryTests {
assertThat(documentWithIdExistsInIndex(toBeDeleted.getId()).block()).isFalse();
}
@Test // DATAES-519
@Test
// DATAES-519
void deleteAllShouldDeleteGivenEntries() {
SampleEntity toBeDeleted = new SampleEntity("id-one");
@ -434,13 +462,14 @@ class SimpleReactiveElasticsearchRepositoryTests {
assertThat(documentWithIdExistsInIndex(hangInThere.getId()).block()).isTrue();
}
@Test // DATAES-519
@Test
// DATAES-519
void deleteAllShouldDeleteAllEntries() {
bulkIndex(new SampleEntity("id-one"), //
new SampleEntity("id-two"), //
new SampleEntity("id-three")) //
.block();
.block();
repository.deleteAll().as(StepVerifier::create).verifyComplete();
@ -450,13 +479,14 @@ class SimpleReactiveElasticsearchRepositoryTests {
.verifyComplete();
}
@Test // DATAES-519
@Test
// DATAES-519
void derivedFinderMethodShouldBeExecutedCorrectly() {
bulkIndex(new SampleEntity("id-one", "message"), //
new SampleEntity("id-two", "test message"), //
new SampleEntity("id-three", "test test")) //
.block();
.block();
repository.findAllByMessageLike("test") //
.as(StepVerifier::create) //
@ -464,13 +494,14 @@ class SimpleReactiveElasticsearchRepositoryTests {
.verifyComplete();
}
@Test // DATAES-519
@Test
// DATAES-519
void derivedFinderMethodShouldBeExecutedCorrectlyWhenGivenPublisher() {
bulkIndex(new SampleEntity("id-one", "message"), //
new SampleEntity("id-two", "test message"), //
new SampleEntity("id-three", "test test")) //
.block();
.block();
repository.findAllByMessage(Mono.just("test")) //
.as(StepVerifier::create) //
@ -478,13 +509,14 @@ class SimpleReactiveElasticsearchRepositoryTests {
.verifyComplete();
}
@Test // DATAES-519
@Test
// DATAES-519
void derivedFinderWithDerivedSortMethodShouldBeExecutedCorrectly() {
bulkIndex(new SampleEntity("id-one", "test", 3), //
new SampleEntity("id-two", "test test", 1), //
new SampleEntity("id-three", "test test", 2)) //
.block();
.block();
repository.findAllByMessageLikeOrderByRate("test") //
.as(StepVerifier::create) //
@ -494,13 +526,14 @@ class SimpleReactiveElasticsearchRepositoryTests {
.verifyComplete();
}
@Test // DATAES-519
@Test
// DATAES-519
void derivedFinderMethodWithSortParameterShouldBeExecutedCorrectly() {
bulkIndex(new SampleEntity("id-one", "test", 3), //
new SampleEntity("id-two", "test test", 1), //
new SampleEntity("id-three", "test test", 2)) //
.block();
.block();
repository.findAllByMessage("test", Sort.by(Order.asc("rate"))) //
.as(StepVerifier::create) //
@ -510,13 +543,14 @@ class SimpleReactiveElasticsearchRepositoryTests {
.verifyComplete();
}
@Test // DATAES-519
@Test
// DATAES-519
void derivedFinderMethodWithPageableParameterShouldBeExecutedCorrectly() {
bulkIndex(new SampleEntity("id-one", "test", 3), //
new SampleEntity("id-two", "test test", 1), //
new SampleEntity("id-three", "test test", 2)) //
.block();
.block();
repository.findAllByMessage("test", PageRequest.of(0, 2, Sort.by(Order.asc("rate")))) //
.as(StepVerifier::create) //
@ -525,13 +559,14 @@ class SimpleReactiveElasticsearchRepositoryTests {
.verifyComplete();
}
@Test // DATAES-519
@Test
// DATAES-519
void derivedFinderMethodReturningMonoShouldBeExecutedCorrectly() {
bulkIndex(new SampleEntity("id-one", "message"), //
new SampleEntity("id-two", "test message"), //
new SampleEntity("id-three", "test test")) //
.block();
.block();
repository.findFirstByMessageLike("test") //
.as(StepVerifier::create) //
@ -539,7 +574,8 @@ class SimpleReactiveElasticsearchRepositoryTests {
.verifyComplete();
}
@Test // DATAES-519
@Test
// DATAES-519
void annotatedFinderMethodShouldBeExecutedCorrectly() {
int count = 30;
@ -555,7 +591,8 @@ class SimpleReactiveElasticsearchRepositoryTests {
.verifyComplete();
}
@Test // #1917
@Test
// #1917
void annotatedFinderMethodPagedShouldBeExecutedCorrectly() {
int count = 30;
@ -575,13 +612,14 @@ class SimpleReactiveElasticsearchRepositoryTests {
.verifyComplete();
}
@Test // DATAES-519
@Test
// DATAES-519
void derivedDeleteMethodShouldBeExecutedCorrectly() {
bulkIndex(new SampleEntity("id-one", "message"), //
new SampleEntity("id-two", "test message"), //
new SampleEntity("id-three", "test test")) //
.block();
.block();
repository.deleteAllByMessage("message") //
.as(StepVerifier::create) //
@ -593,6 +631,56 @@ class SimpleReactiveElasticsearchRepositoryTests {
assertThat(documentWithIdExistsInIndex("id-three").block()).isTrue();
}
@Test
// #2135
void FluxOfSearchHitForArrayQuery() {
bulkIndex(new SampleEntity("id-one", "message1"), //
new SampleEntity("id-two", "message2"), //
new SampleEntity("id-three", "message3")) //
.block();
repository.findAllViaAnnotatedQueryByMessageIn(Arrays.asList("message1", "message3")) //
.as(StepVerifier::create) //
.consumeNextWith(it -> assertThat(it.getId()).isEqualTo("id-one")) //
.consumeNextWith(it -> assertThat(it.getId()).isEqualTo("id-three")) //
.verifyComplete();
}
@Test
// #2135
void FluxOfSearchHitForIntegerArrayQuery() {
bulkIndex(new SampleEntity("id-one", "test", 3), //
new SampleEntity("id-two", "test test", 1), //
new SampleEntity("id-three", "test test", 2)) //
.block();
repository.findAllViaAnnotatedQueryByRatesIn(Arrays.asList(2, 3)) //
.as(StepVerifier::create) //
.consumeNextWith(it -> assertThat(it.getId()).isEqualTo("id-one")) //
.consumeNextWith(it -> assertThat(it.getId()).isEqualTo("id-three")) //
.verifyComplete();
}
@Test
// #2135
void FluxOfSearchHitForStringAndIntegerArrayQuery() {
bulkIndex(new SampleEntity("id-one", "message1", 1), //
new SampleEntity("id-two", "message2", 2), //
new SampleEntity("id-three", "message3", 3), //
new SampleEntity("id-four", "message4", 4), //
new SampleEntity("id-five", "message5", 5)) //
.block();
repository.findAllViaAnnotatedQueryByMessageInAndRatesIn(Arrays.asList("message5", "message3"), Arrays.asList(2,
3)) //
.as(StepVerifier::create) //
.consumeNextWith(it -> assertThat(it.getId()).isEqualTo("id-three")) //
.verifyComplete();
}
Mono<Void> bulkIndex(SampleEntity... entities) {
return operations.saveAll(Arrays.asList(entities), IndexCoordinates.of(INDEX)).then();
}
@ -609,11 +697,11 @@ class SimpleReactiveElasticsearchRepositoryTests {
Flux<SampleEntity> findAllByMessage(Publisher<String> message);
@Highlight(fields = { @HighlightField(name = "message") })
@Highlight(fields = {@HighlightField(name = "message")})
Flux<SearchHit<SampleEntity>> queryAllByMessage(String message);
@Query("{\"bool\": {\"must\": [{\"term\": {\"message\": \"?0\"}}]}}")
@Highlight(fields = { @HighlightField(name = "message") })
@Highlight(fields = {@HighlightField(name = "message")})
Flux<SearchHit<SampleEntity>> queryByMessageWithString(String message);
@Query("{ \"bool\" : { \"must\" : { \"term\" : { \"message\" : \"?0\" } } } }")
@ -632,18 +720,39 @@ class SimpleReactiveElasticsearchRepositoryTests {
@CountQuery(value = "{\"bool\": {\"must\": [{\"term\": {\"message\": \"?0\"}}]}}")
Mono<Long> retrieveCountByText(String message);
@Query("{ \"terms\": { \"message\": ?0 } }")
Flux<SampleEntity> findAllViaAnnotatedQueryByMessageIn(List<String> messages);
@Query("{ \"terms\": { \"rate\": ?0 } }")
Flux<SampleEntity> findAllViaAnnotatedQueryByRatesIn(List<Integer> rates);
@Query("{\"bool\": {\"must\": [{ \"terms\": { \"message\": ?0 } }, { \"terms\": { \"rate\": ?1 } }] } }")
Flux<SampleEntity> findAllViaAnnotatedQueryByMessageInAndRatesIn(List<String> messages, List<Integer> rates);
}
@Document(indexName = INDEX)
static class SampleEntity {
@Nullable @Id private String id;
@Nullable @Field(type = Text, store = true, fielddata = true) private String type;
@Nullable @Field(type = Text, store = true, fielddata = true) private String message;
@Nullable private int rate;
@Nullable private boolean available;
@Nullable @Version private Long version;
@Nullable
@Id
private String id;
@Nullable
@Field(type = FieldType.Text, store = true, fielddata = true)
private String type;
@Nullable
@Field(type = FieldType.Text, store = true, fielddata = true)
private String message;
@Nullable
private int rate;
@Nullable
private boolean available;
@Nullable
@Version
private Long version;
public SampleEntity() {}
public SampleEntity() {
}
public SampleEntity(@Nullable String id) {
this.id = id;