Compare commits
42 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 033b3fbf55 | |||
| b9c8c2ef7e | |||
| 37c78e4289 | |||
| 0122f61ec0 | |||
| b775357524 | |||
| c9fe8a29b9 | |||
| c444bbd65f | |||
| 9c1b001da1 | |||
| ea234c7b68 | |||
| 42af6e375c | |||
| 5dfd05992f | |||
| 64cf9566d9 | |||
| 2c857178f4 | |||
| f087d5aac3 | |||
| fbcb76f8ad | |||
| ba06741c93 | |||
| a228629c7d | |||
| 6fd688f3a2 | |||
| 7d85f0bdd8 | |||
| c7534fa8b9 | |||
| fe9e0b5d0c | |||
| d26d01bab1 | |||
| 026be264fe | |||
| fd1ba5869d | |||
| 7f3035dab5 | |||
| a5c4867684 | |||
| 572cc7ffea | |||
| 4e7bcac5f5 | |||
| 4628908e84 | |||
| 922f4b1760 | |||
| 4614c62bb5 | |||
| 063020f8b3 | |||
| 0c98c419c9 | |||
| 3f085b2675 | |||
| 42aeb48b43 | |||
| 449910b4f7 | |||
| 2e99f5b2e0 | |||
| 9b9136d852 | |||
| a266d7c46a | |||
| c045a8ae29 | |||
| 11c87a1251 | |||
| c9e9bf757e |
+2
-2
@@ -1,3 +1,3 @@
|
||||
#Mon Jul 03 09:48:29 CEST 2023
|
||||
#Thu Dec 14 08:37:40 CET 2023
|
||||
wrapperUrl=https\://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar
|
||||
distributionUrl=https\://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.3/apache-maven-3.9.3-bin.zip
|
||||
distributionUrl=https\://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.6/apache-maven-3.9.6-bin.zip
|
||||
|
||||
Vendored
+9
-10
@@ -38,8 +38,8 @@ pipeline {
|
||||
steps {
|
||||
script {
|
||||
docker.image(p['docker.java.main.image']).inside(p['docker.java.inside.docker']) {
|
||||
sh 'PROFILE=none ci/verify.sh'
|
||||
sh "ci/clean.sh"
|
||||
sh "PROFILE=none JENKINS_USER_NAME=${p['jenkins.user.name']} ci/verify.sh"
|
||||
sh "JENKINS_USER_NAME=${p['jenkins.user.name']} ci/clean.sh"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -65,8 +65,8 @@ pipeline {
|
||||
steps {
|
||||
script {
|
||||
docker.image(p['docker.java.next.image']).inside(p['docker.java.inside.docker']) {
|
||||
sh 'PROFILE=none ci/verify.sh'
|
||||
sh "ci/clean.sh"
|
||||
sh "PROFILE=none JENKINS_USER_NAME=${p['jenkins.user.name']} ci/verify.sh"
|
||||
sh "JENKINS_USER_NAME=${p['jenkins.user.name']} ci/clean.sh"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -86,22 +86,21 @@ pipeline {
|
||||
label 'data'
|
||||
}
|
||||
options { timeout(time: 20, unit: 'MINUTES') }
|
||||
|
||||
environment {
|
||||
ARTIFACTORY = credentials("${p['artifactory.credentials']}")
|
||||
}
|
||||
|
||||
steps {
|
||||
script {
|
||||
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 ' +
|
||||
sh 'MAVEN_OPTS="-Duser.name=' + "${p['jenkins.user.name']}" + ' -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=${p['artifactory.url']} " +
|
||||
"-Dartifactory.username=${ARTIFACTORY_USR} " +
|
||||
"-Dartifactory.password=${ARTIFACTORY_PSW} " +
|
||||
"-Dartifactory.staging-repository=libs-snapshot-local " +
|
||||
"-Dartifactory.staging-repository=${p['artifactory.repository.snapshot']} " +
|
||||
"-Dartifactory.build-name=spring-data-elasticsearch " +
|
||||
"-Dartifactory.build-number=${BUILD_NUMBER} " +
|
||||
'-Dmaven.test.skip=true clean deploy -U -B'
|
||||
"-Dmaven.test.skip=true clean deploy -U -B"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-1
@@ -2,5 +2,7 @@
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" \
|
||||
export JENKINS_USER=${JENKINS_USER_NAME}
|
||||
|
||||
MAVEN_OPTS="-Duser.name=${JENKINS_USER} -Duser.home=/tmp/jenkins-home" \
|
||||
./mvnw -s settings.xml clean -Dmaven.repo.local=/tmp/jenkins-home/.m2/spring-data-elasticsearch
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Java versions
|
||||
java.main.tag=17.0.7_7-jdk-focal
|
||||
java.main.tag=17.0.9_9-jdk-focal
|
||||
java.next.tag=20-jdk-jammy
|
||||
|
||||
# Docker container images - standard
|
||||
@@ -7,15 +7,15 @@ docker.java.main.image=harbor-repo.vmware.com/dockerhub-proxy-cache/library/ecli
|
||||
docker.java.next.image=harbor-repo.vmware.com/dockerhub-proxy-cache/library/eclipse-temurin:${java.next.tag}
|
||||
|
||||
# Supported versions of MongoDB
|
||||
docker.mongodb.4.4.version=4.4.22
|
||||
docker.mongodb.5.0.version=5.0.18
|
||||
docker.mongodb.6.0.version=6.0.7
|
||||
docker.mongodb.4.4.version=4.4.25
|
||||
docker.mongodb.5.0.version=5.0.21
|
||||
docker.mongodb.6.0.version=6.0.10
|
||||
|
||||
# Supported versions of Redis
|
||||
docker.redis.6.version=6.2.12
|
||||
docker.redis.6.version=6.2.13
|
||||
|
||||
# Supported versions of Cassandra
|
||||
docker.cassandra.3.version=3.11.15
|
||||
docker.cassandra.3.version=3.11.16
|
||||
|
||||
# Docker environment settings
|
||||
docker.java.inside.basic=-v $HOME:/tmp/jenkins-home
|
||||
@@ -25,3 +25,6 @@ docker.java.inside.docker=-u root -v /var/run/docker.sock:/var/run/docker.sock -
|
||||
docker.registry=
|
||||
docker.credentials=hub.docker.com-springbuildmaster
|
||||
artifactory.credentials=02bd1690-b54f-4c9f-819d-a77cb7a9822c
|
||||
artifactory.url=https://repo.spring.io
|
||||
artifactory.repository.snapshot=libs-snapshot-local
|
||||
jenkins.user.name=spring-builds+jenkins
|
||||
|
||||
+4
-2
@@ -5,6 +5,8 @@ set -euo pipefail
|
||||
mkdir -p /tmp/jenkins-home/.m2/spring-data-elasticsearch
|
||||
chown -R 1001:1001 .
|
||||
|
||||
MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" \
|
||||
export JENKINS_USER=${JENKINS_USER_NAME}
|
||||
|
||||
MAVEN_OPTS="-Duser.name=${JENKINS_USER} -Duser.home=/tmp/jenkins-home" \
|
||||
./mvnw -s settings.xml \
|
||||
-P${PROFILE} clean dependency:list verify -Dsort -U -B -Dmaven.repo.local=/tmp/jenkins-home/.m2/spring-data-elasticsearch
|
||||
-P${PROFILE} clean dependency:list verify -Dsort -U -B -Dmaven.repo.local=/tmp/jenkins-home/.m2/spring-data-elasticsearch
|
||||
|
||||
@@ -5,12 +5,12 @@
|
||||
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-elasticsearch</artifactId>
|
||||
<version>5.1.2</version>
|
||||
<version>5.1.7</version>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.data.build</groupId>
|
||||
<artifactId>spring-data-parent</artifactId>
|
||||
<version>3.1.2</version>
|
||||
<version>3.1.7</version>
|
||||
</parent>
|
||||
|
||||
<name>Spring Data Elasticsearch</name>
|
||||
@@ -18,10 +18,10 @@
|
||||
<url>https://github.com/spring-projects/spring-data-elasticsearch</url>
|
||||
|
||||
<properties>
|
||||
<springdata.commons>3.1.2</springdata.commons>
|
||||
<springdata.commons>3.1.7</springdata.commons>
|
||||
|
||||
<!-- version of the RestHighLevelClient -->
|
||||
<elasticsearch-rhlc>7.17.11</elasticsearch-rhlc>
|
||||
<elasticsearch-rhlc>7.17.15</elasticsearch-rhlc>
|
||||
<!-- version of the new ElasticsearchClient -->
|
||||
<elasticsearch-java>8.7.1</elasticsearch-java>
|
||||
|
||||
|
||||
+1
-1
@@ -138,7 +138,7 @@ final class DocumentAdapters {
|
||||
document.setPrimaryTerm(hit.primaryTerm() != null && hit.primaryTerm() > 0 ? hit.primaryTerm() : 0);
|
||||
|
||||
float score = hit.score() != null ? hit.score().floatValue() : Float.NaN;
|
||||
return new SearchDocumentAdapter(document, score, hit.sort().stream().map(TypeUtils::toString).toArray(),
|
||||
return new SearchDocumentAdapter(document, score, hit.sort().stream().map(TypeUtils::toObject).toArray(),
|
||||
documentFields, highlightFields, innerHits, nestedMetaData, explanation, matchedQueries, hit.routing());
|
||||
}
|
||||
|
||||
|
||||
+18
-23
@@ -19,7 +19,6 @@ import static org.springframework.data.elasticsearch.client.elc.TypeUtils.*;
|
||||
import static org.springframework.util.CollectionUtils.*;
|
||||
|
||||
import co.elastic.clients.elasticsearch._types.Conflicts;
|
||||
import co.elastic.clients.elasticsearch._types.FieldValue;
|
||||
import co.elastic.clients.elasticsearch._types.InlineScript;
|
||||
import co.elastic.clients.elasticsearch._types.OpType;
|
||||
import co.elastic.clients.elasticsearch._types.SortOptions;
|
||||
@@ -63,7 +62,6 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
@@ -1001,7 +999,7 @@ class RequestConverter {
|
||||
.docAsUpsert(query.getDocAsUpsert()) //
|
||||
.ifSeqNo(query.getIfSeqNo() != null ? Long.valueOf(query.getIfSeqNo()) : null) //
|
||||
.ifPrimaryTerm(query.getIfPrimaryTerm() != null ? Long.valueOf(query.getIfPrimaryTerm()) : null) //
|
||||
.refresh(refresh(refreshPolicy)) //
|
||||
.refresh(query.getRefreshPolicy() != null ? refresh(query.getRefreshPolicy()) : refresh(refreshPolicy)) //
|
||||
.retryOnConflict(query.getRetryOnConflict()) //
|
||||
;
|
||||
|
||||
@@ -1188,10 +1186,9 @@ class RequestConverter {
|
||||
}
|
||||
|
||||
if (!isEmpty(query.getFields())) {
|
||||
bb.fields(fb -> {
|
||||
query.getFields().forEach(fb::field);
|
||||
return fb;
|
||||
});
|
||||
var fieldAndFormats = query.getFields().stream()
|
||||
.map(field -> FieldAndFormat.of(b -> b.field(field))).toList();
|
||||
bb.fields(fieldAndFormats);
|
||||
}
|
||||
|
||||
if (!isEmpty(query.getStoredFields())) {
|
||||
@@ -1221,8 +1218,7 @@ class RequestConverter {
|
||||
}
|
||||
|
||||
if (!isEmpty(query.getSearchAfter())) {
|
||||
bb.searchAfter(query.getSearchAfter().stream().map(it -> FieldValue.of(it.toString()))
|
||||
.collect(Collectors.toList()));
|
||||
bb.searchAfter(query.getSearchAfter().stream().map(TypeUtils::toFieldValue).toList());
|
||||
}
|
||||
|
||||
query.getRescorerQueries().forEach(rescorerQuery -> bb.rescore(getRescore(rescorerQuery)));
|
||||
@@ -1324,10 +1320,9 @@ class RequestConverter {
|
||||
}
|
||||
|
||||
if (!isEmpty(query.getFields())) {
|
||||
builder.fields(fb -> {
|
||||
query.getFields().forEach(fb::field);
|
||||
return fb;
|
||||
});
|
||||
var fieldAndFormats = query.getFields().stream()
|
||||
.map(field -> FieldAndFormat.of(b -> b.field(field))).toList();
|
||||
builder.fields(fieldAndFormats);
|
||||
}
|
||||
|
||||
if (!isEmpty(query.getStoredFields())) {
|
||||
@@ -1346,14 +1341,6 @@ class RequestConverter {
|
||||
builder.minScore((double) query.getMinScore());
|
||||
}
|
||||
|
||||
if (query.getSort() != null) {
|
||||
List<SortOptions> sortOptions = getSortOptions(query.getSort(), persistentEntity);
|
||||
|
||||
if (!sortOptions.isEmpty()) {
|
||||
builder.sort(sortOptions);
|
||||
}
|
||||
}
|
||||
|
||||
addHighlight(query, builder);
|
||||
|
||||
query.getScriptedFields().forEach(scriptedField -> builder.scriptFields(scriptedField.getFieldName(),
|
||||
@@ -1362,6 +1349,15 @@ class RequestConverter {
|
||||
if (query instanceof NativeQuery) {
|
||||
prepareNativeSearch((NativeQuery) query, builder);
|
||||
}
|
||||
// query.getSort() must be checked after prepareNativeSearch as this already might hav a sort set that must have
|
||||
// higher priority
|
||||
if (query.getSort() != null) {
|
||||
List<SortOptions> sortOptions = getSortOptions(query.getSort(), persistentEntity);
|
||||
|
||||
if (!sortOptions.isEmpty()) {
|
||||
builder.sort(sortOptions);
|
||||
}
|
||||
}
|
||||
|
||||
if (query.getTrackTotalHits() != null) {
|
||||
// logic from the RHLC, choose between -1 and Integer.MAX_VALUE
|
||||
@@ -1376,8 +1372,7 @@ class RequestConverter {
|
||||
}
|
||||
|
||||
if (!isEmpty(query.getSearchAfter())) {
|
||||
builder.searchAfter(
|
||||
query.getSearchAfter().stream().map(it -> FieldValue.of(it.toString())).collect(Collectors.toList()));
|
||||
builder.searchAfter(query.getSearchAfter().stream().map(TypeUtils::toFieldValue).toList());
|
||||
}
|
||||
|
||||
query.getRescorerQueries().forEach(rescorerQuery -> builder.rescore(getRescore(rescorerQuery)));
|
||||
|
||||
+2
-1
@@ -219,7 +219,8 @@ class SearchDocumentResponseBuilder {
|
||||
var phraseSuggestOptions = phraseSuggest.options();
|
||||
List<PhraseSuggestion.Entry.Option> options = new ArrayList<>();
|
||||
phraseSuggestOptions.forEach(optionES -> options
|
||||
.add(new PhraseSuggestion.Entry.Option(optionES.text(), optionES.highlighted(), null, null)));
|
||||
.add(new PhraseSuggestion.Entry.Option(optionES.text(), optionES.highlighted(), optionES.score(),
|
||||
optionES.collateMatch())));
|
||||
entries.add(new PhraseSuggestion.Entry(phraseSuggest.text(), phraseSuggest.offset(), phraseSuggest.length(),
|
||||
options, null));
|
||||
});
|
||||
|
||||
@@ -18,8 +18,15 @@ package org.springframework.data.elasticsearch.client.elc;
|
||||
import co.elastic.clients.elasticsearch._types.*;
|
||||
import co.elastic.clients.elasticsearch._types.mapping.FieldType;
|
||||
import co.elastic.clients.elasticsearch._types.mapping.TypeMapping;
|
||||
import co.elastic.clients.elasticsearch.core.search.*;
|
||||
import co.elastic.clients.elasticsearch.core.search.BoundaryScanner;
|
||||
import co.elastic.clients.elasticsearch.core.search.HighlighterEncoder;
|
||||
import co.elastic.clients.elasticsearch.core.search.HighlighterFragmenter;
|
||||
import co.elastic.clients.elasticsearch.core.search.HighlighterOrder;
|
||||
import co.elastic.clients.elasticsearch.core.search.HighlighterTagsSchema;
|
||||
import co.elastic.clients.elasticsearch.core.search.HighlighterType;
|
||||
import co.elastic.clients.elasticsearch.core.search.ScoreMode;
|
||||
import co.elastic.clients.elasticsearch.indices.IndexSettings;
|
||||
import co.elastic.clients.json.JsonData;
|
||||
|
||||
import java.io.StringReader;
|
||||
import java.time.Duration;
|
||||
@@ -31,8 +38,13 @@ import java.util.stream.Collectors;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.elasticsearch.core.RefreshPolicy;
|
||||
import org.springframework.data.elasticsearch.core.document.Document;
|
||||
import org.springframework.data.elasticsearch.core.query.*;
|
||||
import org.springframework.data.elasticsearch.core.query.GeoDistanceOrder;
|
||||
import org.springframework.data.elasticsearch.core.query.IndexQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.IndicesOptions;
|
||||
import org.springframework.data.elasticsearch.core.query.Order;
|
||||
import org.springframework.data.elasticsearch.core.query.Query;
|
||||
import org.springframework.data.elasticsearch.core.query.RescorerQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.UpdateResponse;
|
||||
import org.springframework.data.elasticsearch.core.reindex.ReindexRequest;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
@@ -156,6 +168,40 @@ final class TypeUtils {
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static FieldValue toFieldValue(@Nullable Object fieldValue) {
|
||||
|
||||
if (fieldValue == null) {
|
||||
return FieldValue.NULL;
|
||||
}
|
||||
|
||||
if (fieldValue instanceof Boolean b) {
|
||||
return b ? FieldValue.TRUE : FieldValue.FALSE;
|
||||
}
|
||||
|
||||
if (fieldValue instanceof String s) {
|
||||
return FieldValue.of(s);
|
||||
}
|
||||
|
||||
if (fieldValue instanceof Long l) {
|
||||
return FieldValue.of(l);
|
||||
}
|
||||
|
||||
if (fieldValue instanceof Integer i) {
|
||||
return FieldValue.of((long) i);
|
||||
}
|
||||
|
||||
if (fieldValue instanceof Double d) {
|
||||
return FieldValue.of(d);
|
||||
}
|
||||
|
||||
if (fieldValue instanceof Float f) {
|
||||
return FieldValue.of((double) f);
|
||||
}
|
||||
|
||||
return FieldValue.of(JsonData.of(fieldValue));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static GeoDistanceType geoDistanceType(GeoDistanceOrder.DistanceType distanceType) {
|
||||
|
||||
|
||||
+6
@@ -748,6 +748,12 @@ abstract public class AbstractReactiveElasticsearchTemplate
|
||||
|
||||
public abstract Mono<String> getClusterVersion();
|
||||
|
||||
@Nullable
|
||||
public String getEntityRouting(Object entity) {
|
||||
return entityOperations.forEntity(entity, converter.getConversionService(), routingResolver)
|
||||
.getRouting();
|
||||
}
|
||||
|
||||
/**
|
||||
* Value class to capture client independent information from a response to an index request.
|
||||
*/
|
||||
|
||||
+11
@@ -117,6 +117,17 @@ public interface ReactiveElasticsearchOperations
|
||||
ReactiveClusterOperations cluster();
|
||||
|
||||
// region routing
|
||||
/**
|
||||
* gets the routing for an entity.
|
||||
*
|
||||
* @param entity the entity
|
||||
* @return the routing, may be null if not set.
|
||||
* @since 5.2
|
||||
*/
|
||||
@Nullable
|
||||
String getEntityRouting(Object entity);
|
||||
|
||||
// region customizations
|
||||
/**
|
||||
* Returns a copy of this instance with the same configuration, but that uses a different {@link RoutingResolver} to
|
||||
* obtain routing information.
|
||||
|
||||
+11
-5
@@ -32,9 +32,19 @@ public abstract class AbstractRangePropertyValueConverter<T> extends AbstractPro
|
||||
protected static final String LTE_FIELD = "lte";
|
||||
protected static final String GT_FIELD = "gt";
|
||||
protected static final String GTE_FIELD = "gte";
|
||||
private final Class<?> genericType;
|
||||
|
||||
public AbstractRangePropertyValueConverter(PersistentProperty<?> property) {
|
||||
/**
|
||||
* @param property the property this convertrer belongs to
|
||||
* @param genericType the generic type of the Range
|
||||
*/
|
||||
public AbstractRangePropertyValueConverter(PersistentProperty<?> property, Class<?> genericType) {
|
||||
super(property);
|
||||
this.genericType = genericType;
|
||||
}
|
||||
|
||||
public Class<?> getGenericType() {
|
||||
return genericType;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -117,10 +127,6 @@ public abstract class AbstractRangePropertyValueConverter<T> extends AbstractPro
|
||||
|
||||
protected abstract String format(T value);
|
||||
|
||||
protected Class<?> getGenericType() {
|
||||
return getProperty().getTypeInformation().getTypeArguments().get(0).getType();
|
||||
}
|
||||
|
||||
protected abstract T parse(String value);
|
||||
|
||||
}
|
||||
|
||||
+2
-2
@@ -33,9 +33,9 @@ public class DateRangePropertyValueConverter extends AbstractRangePropertyValueC
|
||||
private final List<ElasticsearchDateConverter> dateConverters;
|
||||
|
||||
public DateRangePropertyValueConverter(PersistentProperty<?> property,
|
||||
List<ElasticsearchDateConverter> dateConverters) {
|
||||
Class<?> genericType, List<ElasticsearchDateConverter> dateConverters) {
|
||||
|
||||
super(property);
|
||||
super(property, genericType);
|
||||
this.dateConverters = dateConverters;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -919,7 +919,7 @@ public class MappingElasticsearchConverter
|
||||
|
||||
Class<?> elementType = element == null ? null : element.getClass();
|
||||
|
||||
if (elementType == null || conversions.isSimpleType(elementType)) {
|
||||
if (elementType == null || isSimpleType(elementType)) {
|
||||
collection.add(getPotentiallyConvertedSimpleWrite(element,
|
||||
componentType != null ? componentType.getType() : Object.class));
|
||||
} else if (element instanceof Collection || elementType.isArray()) {
|
||||
|
||||
+6
-2
@@ -23,8 +23,12 @@ import org.springframework.data.mapping.PersistentProperty;
|
||||
*/
|
||||
public class NumberRangePropertyValueConverter extends AbstractRangePropertyValueConverter<Number> {
|
||||
|
||||
public NumberRangePropertyValueConverter(PersistentProperty<?> property) {
|
||||
super(property);
|
||||
/**
|
||||
* @param property the property this convertrer belongs to
|
||||
* @param genericType the generic type of the Range
|
||||
*/
|
||||
public NumberRangePropertyValueConverter(PersistentProperty<?> property, Class<?> genericType) {
|
||||
super(property, genericType);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+2
-2
@@ -34,9 +34,9 @@ public class TemporalRangePropertyValueConverter extends AbstractRangePropertyVa
|
||||
private final List<ElasticsearchDateConverter> dateConverters;
|
||||
|
||||
public TemporalRangePropertyValueConverter(PersistentProperty<?> property,
|
||||
List<ElasticsearchDateConverter> dateConverters) {
|
||||
Class<?> genericType, List<ElasticsearchDateConverter> dateConverters) {
|
||||
|
||||
super(property);
|
||||
super(property, genericType);
|
||||
|
||||
Assert.notEmpty(dateConverters, "dateConverters must not be empty.");
|
||||
this.dateConverters = dateConverters;
|
||||
|
||||
@@ -332,6 +332,8 @@ public final class MappingParameters {
|
||||
|
||||
if (!Similarity.Default.equals(similarity)) {
|
||||
objectNode.put(FIELD_PARAM_SIMILARITY, similarity);
|
||||
// similarity must have index explicitly set, otherwise Elasticsearch returns an error
|
||||
objectNode.put(FIELD_PARAM_INDEX, index);
|
||||
}
|
||||
|
||||
if (termVector != TermVector.none) {
|
||||
|
||||
+19
-5
@@ -20,6 +20,7 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
@@ -56,6 +57,7 @@ import org.springframework.data.mapping.model.FieldNamingStrategy;
|
||||
import org.springframework.data.mapping.model.Property;
|
||||
import org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy;
|
||||
import org.springframework.data.mapping.model.SimpleTypeHolder;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@@ -168,6 +170,18 @@ public class SimpleElasticsearchPersistentProperty extends
|
||||
return;
|
||||
}
|
||||
|
||||
Supplier<Class<?>> getGenericType = () -> {
|
||||
TypeInformation<?> typeInformation = getTypeInformation();
|
||||
|
||||
if (typeInformation.isCollectionLike()) {
|
||||
// we have a collection of Range<?>
|
||||
typeInformation = typeInformation.getComponentType();
|
||||
}
|
||||
|
||||
Class<?> genericType = typeInformation.getTypeArguments().get(0).getType();
|
||||
return genericType;
|
||||
};
|
||||
|
||||
switch (field.type()) {
|
||||
case Date:
|
||||
case Date_Nanos: {
|
||||
@@ -197,11 +211,11 @@ public class SimpleElasticsearchPersistentProperty extends
|
||||
return;
|
||||
}
|
||||
|
||||
Class<?> genericType = getTypeInformation().getTypeArguments().get(0).getType();
|
||||
var genericType = getGenericType.get();
|
||||
if (TemporalAccessor.class.isAssignableFrom(genericType)) {
|
||||
propertyValueConverter = new TemporalRangePropertyValueConverter(this, dateConverters);
|
||||
propertyValueConverter = new TemporalRangePropertyValueConverter(this, genericType, dateConverters);
|
||||
} else if (Date.class.isAssignableFrom(genericType)) {
|
||||
propertyValueConverter = new DateRangePropertyValueConverter(this, dateConverters);
|
||||
propertyValueConverter = new DateRangePropertyValueConverter(this, genericType, dateConverters);
|
||||
} else {
|
||||
LOGGER.warn(
|
||||
String.format("Unsupported generic type '{%s' for date range property '%s'.", genericType, getName()));
|
||||
@@ -216,7 +230,7 @@ public class SimpleElasticsearchPersistentProperty extends
|
||||
return;
|
||||
}
|
||||
|
||||
Class<?> genericType = getTypeInformation().getTypeArguments().get(0).getType();
|
||||
var genericType = getGenericType.get();
|
||||
if ((field.type() == FieldType.Integer_Range && !Integer.class.isAssignableFrom(genericType))
|
||||
|| (field.type() == FieldType.Float_Range && !Float.class.isAssignableFrom(genericType))
|
||||
|| (field.type() == FieldType.Long_Range && !Long.class.isAssignableFrom(genericType))
|
||||
@@ -226,7 +240,7 @@ public class SimpleElasticsearchPersistentProperty extends
|
||||
return;
|
||||
}
|
||||
|
||||
propertyValueConverter = new NumberRangePropertyValueConverter(this);
|
||||
propertyValueConverter = new NumberRangePropertyValueConverter(this, genericType);
|
||||
break;
|
||||
}
|
||||
case Ip_Range: {
|
||||
|
||||
+9
-4
@@ -39,6 +39,7 @@ import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
import org.springframework.data.elasticsearch.core.query.BaseQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.MoreLikeThisQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.Query;
|
||||
import org.springframework.data.elasticsearch.core.routing.RoutingResolver;
|
||||
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
|
||||
import org.springframework.data.util.StreamUtils;
|
||||
import org.springframework.data.util.Streamable;
|
||||
@@ -223,7 +224,7 @@ public class SimpleElasticsearchRepository<T, ID> implements ElasticsearchReposi
|
||||
|
||||
Assert.notNull(id, "Cannot delete entity with id 'null'.");
|
||||
|
||||
doDelete(id, getIndexCoordinates());
|
||||
doDelete(id, null, getIndexCoordinates());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -231,7 +232,7 @@ public class SimpleElasticsearchRepository<T, ID> implements ElasticsearchReposi
|
||||
|
||||
Assert.notNull(entity, "Cannot delete 'null' entity.");
|
||||
|
||||
doDelete(extractIdFromBean(entity), getIndexCoordinates());
|
||||
doDelete(extractIdFromBean(entity), operations.getEntityRouting(entity), getIndexCoordinates());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -271,10 +272,14 @@ public class SimpleElasticsearchRepository<T, ID> implements ElasticsearchReposi
|
||||
deleteAllById(ids);
|
||||
}
|
||||
|
||||
private void doDelete(@Nullable ID id, IndexCoordinates indexCoordinates) {
|
||||
private void doDelete(@Nullable ID id, @Nullable String routing, IndexCoordinates indexCoordinates) {
|
||||
|
||||
if (id != null) {
|
||||
executeAndRefresh(operations -> operations.delete(stringIdRepresentation(id), indexCoordinates));
|
||||
executeAndRefresh(operations -> {
|
||||
var ops = routing != null ? operations.withRouting(RoutingResolver.just(routing)) : operations;
|
||||
// noinspection DataFlowIssue
|
||||
return ops.delete(stringIdRepresentation(id), indexCoordinates);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+5
-1
@@ -31,6 +31,7 @@ import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersiste
|
||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
import org.springframework.data.elasticsearch.core.query.BaseQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.Query;
|
||||
import org.springframework.data.elasticsearch.core.routing.RoutingResolver;
|
||||
import org.springframework.data.elasticsearch.repository.ReactiveElasticsearchRepository;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -196,7 +197,10 @@ public class SimpleReactiveElasticsearchRepository<T, ID> implements ReactiveEla
|
||||
public Mono<Void> delete(T entity) {
|
||||
|
||||
Assert.notNull(entity, "Entity must not be null!");
|
||||
return operations.delete(entity, entityInformation.getIndexCoordinates()) //
|
||||
|
||||
var routing = operations.getEntityRouting(entity);
|
||||
var ops = routing != null ? operations.withRouting(RoutingResolver.just(routing)) : operations;
|
||||
return ops.delete(entity, entityInformation.getIndexCoordinates()) //
|
||||
.then(doRefresh());
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Spring Data Elasticsearch 5.1.2 (2023.0.2)
|
||||
Spring Data Elasticsearch 5.1.7 (2023.0.7)
|
||||
Copyright (c) [2013-2022] Pivotal Software, Inc.
|
||||
|
||||
This product is licensed to you under the Apache License, Version 2.0 (the "License").
|
||||
@@ -15,3 +15,8 @@ conditions of the subcomponent's license, as noted in the LICENSE file.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright 2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.client.elc;
|
||||
|
||||
import co.elastic.clients.elasticsearch.core.search.HitsMetadata;
|
||||
import co.elastic.clients.elasticsearch.core.search.Suggestion;
|
||||
import co.elastic.clients.elasticsearch.core.search.TotalHitsRelation;
|
||||
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.assertj.core.api.SoftAssertions;
|
||||
import org.json.JSONException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
|
||||
/**
|
||||
* Tests for the factory class to create {@link SearchDocumentResponse} instances.
|
||||
*
|
||||
* @author Sébastien Comeau
|
||||
* @since 5.2
|
||||
*/
|
||||
class SearchDocumentResponseBuilderUnitTests {
|
||||
|
||||
private JacksonJsonpMapper jsonpMapper = new JacksonJsonpMapper();
|
||||
|
||||
@Test // #2681
|
||||
void shouldGetPhraseSuggestion() throws JSONException {
|
||||
// arrange
|
||||
final var hitsMetadata = new HitsMetadata.Builder<EntityAsMap>()
|
||||
.total(total -> total
|
||||
.value(0)
|
||||
.relation(TotalHitsRelation.Eq))
|
||||
.hits(new ArrayList<>())
|
||||
.build();
|
||||
|
||||
final var suggestionTest = new Suggestion.Builder<EntityAsMap>()
|
||||
.phrase(phrase -> phrase
|
||||
.text("National")
|
||||
.offset(0)
|
||||
.length(8)
|
||||
.options(option -> option
|
||||
.text("nations")
|
||||
.highlighted("highlighted-nations")
|
||||
.score(0.11480146)
|
||||
.collateMatch(false))
|
||||
.options(option -> option
|
||||
.text("national")
|
||||
.highlighted("highlighted-national")
|
||||
.score(0.08063514)
|
||||
.collateMatch(false)))
|
||||
.build();
|
||||
|
||||
final var sortProperties = ImmutableMap.<String, List<Suggestion<EntityAsMap>>> builder()
|
||||
.put("suggestionTest", ImmutableList.of(suggestionTest))
|
||||
.build();
|
||||
|
||||
// act
|
||||
final var actual = SearchDocumentResponseBuilder.from(hitsMetadata, null, null, null, sortProperties, null,
|
||||
jsonpMapper);
|
||||
|
||||
// assert
|
||||
SoftAssertions softly = new SoftAssertions();
|
||||
|
||||
softly.assertThat(actual).isNotNull();
|
||||
softly.assertThat(actual.getSuggest()).isNotNull();
|
||||
softly.assertThat(actual.getSuggest().getSuggestions()).isNotNull().hasSize(1);
|
||||
|
||||
final var actualSuggestion = actual.getSuggest().getSuggestions().get(0);
|
||||
softly.assertThat(actualSuggestion.getName()).isEqualTo("suggestionTest");
|
||||
softly.assertThat(actualSuggestion.getEntries()).isNotNull().hasSize(1);
|
||||
|
||||
final var actualEntry = actualSuggestion.getEntries().get(0);
|
||||
softly.assertThat(actualEntry).isNotNull();
|
||||
softly.assertThat(actualEntry.getText()).isEqualTo("National");
|
||||
softly.assertThat(actualEntry.getOffset()).isEqualTo(0);
|
||||
softly.assertThat(actualEntry.getLength()).isEqualTo(8);
|
||||
softly.assertThat(actualEntry.getOptions()).isNotNull().hasSize(2);
|
||||
|
||||
final var actualOption1 = actualEntry.getOptions().get(0);
|
||||
softly.assertThat(actualOption1.getText()).isEqualTo("nations");
|
||||
softly.assertThat(actualOption1.getHighlighted()).isEqualTo("highlighted-nations");
|
||||
softly.assertThat(actualOption1.getScore()).isEqualTo(0.11480146);
|
||||
softly.assertThat(actualOption1.getCollateMatch()).isEqualTo(false);
|
||||
|
||||
final var actualOption2 = actualEntry.getOptions().get(1);
|
||||
softly.assertThat(actualOption2.getText()).isEqualTo("national");
|
||||
softly.assertThat(actualOption2.getHighlighted()).isEqualTo("highlighted-national");
|
||||
softly.assertThat(actualOption2.getScore()).isEqualTo(0.08063514);
|
||||
softly.assertThat(actualOption2.getCollateMatch()).isEqualTo(false);
|
||||
|
||||
softly.assertAll();
|
||||
}
|
||||
}
|
||||
+49
@@ -18,6 +18,7 @@ package org.springframework.data.elasticsearch.core;
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.springframework.data.elasticsearch.client.elc.Queries.*;
|
||||
|
||||
import co.elastic.clients.elasticsearch._types.SortOrder;
|
||||
import co.elastic.clients.elasticsearch._types.aggregations.Aggregate;
|
||||
import co.elastic.clients.elasticsearch._types.aggregations.Buckets;
|
||||
import co.elastic.clients.elasticsearch._types.aggregations.StringTermsAggregate;
|
||||
@@ -27,9 +28,12 @@ import co.elastic.clients.elasticsearch.core.search.FieldCollapse;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.elasticsearch.client.elc.Aggregation;
|
||||
import org.springframework.data.elasticsearch.client.elc.ElasticsearchAggregation;
|
||||
import org.springframework.data.elasticsearch.client.elc.NativeQuery;
|
||||
@@ -48,6 +52,51 @@ import org.springframework.test.context.ContextConfiguration;
|
||||
@ContextConfiguration(classes = ReactiveElasticsearchELCIntegrationTests.Config.class)
|
||||
public class ReactiveElasticsearchELCIntegrationTests extends ReactiveElasticsearchIntegrationTests {
|
||||
|
||||
@Test // #2745
|
||||
@DisplayName("should use sort defined in native unbounded query")
|
||||
void shouldUseSortDefinedInNativeUnboundedQuery() {
|
||||
var entity1 = randomEntity(null);
|
||||
entity1.setRate(7);
|
||||
var entity2 = randomEntity(null);
|
||||
entity2.setRate(5);
|
||||
var entity3 = randomEntity(null);
|
||||
entity3.setRate(11);
|
||||
|
||||
operations.saveAll(List.of(entity1, entity2, entity3), SampleEntity.class).blockLast();
|
||||
|
||||
var query = NativeQuery.builder()
|
||||
.withQuery(qb -> qb
|
||||
.matchAll(m -> m))
|
||||
.withSort(sob -> sob
|
||||
.field(f -> f
|
||||
.field("rate")
|
||||
.order(SortOrder.Asc)))
|
||||
.withPageable(Pageable.unpaged())
|
||||
.build();
|
||||
|
||||
var rates = operations.search(query, SampleEntity.class)
|
||||
.map(SearchHit::getContent)
|
||||
.map(SampleEntity::getRate)
|
||||
.collectList().block();
|
||||
assertThat(rates).containsExactly(5, 7, 11);
|
||||
|
||||
query = NativeQuery.builder()
|
||||
.withQuery(qb -> qb
|
||||
.matchAll(m -> m))
|
||||
.withSort(sob -> sob
|
||||
.field(f -> f
|
||||
.field("rate")
|
||||
.order(SortOrder.Desc)))
|
||||
.withPageable(Pageable.unpaged())
|
||||
.build();
|
||||
|
||||
rates = operations.search(query, SampleEntity.class)
|
||||
.map(SearchHit::getContent)
|
||||
.map(SampleEntity::getRate)
|
||||
.collectList().block();
|
||||
assertThat(rates).containsExactly(11, 7, 5);
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@Import({ ReactiveElasticsearchTemplateConfiguration.class })
|
||||
static class Config {
|
||||
|
||||
+2
-2
@@ -99,7 +99,7 @@ import org.springframework.util.StringUtils;
|
||||
@SpringIntegrationTest
|
||||
public abstract class ReactiveElasticsearchIntegrationTests {
|
||||
|
||||
@Autowired private ReactiveElasticsearchOperations operations;
|
||||
@Autowired protected ReactiveElasticsearchOperations operations;
|
||||
@Autowired private IndexNameProvider indexNameProvider;
|
||||
|
||||
// region Setup
|
||||
@@ -1205,7 +1205,7 @@ public abstract class ReactiveElasticsearchIntegrationTests {
|
||||
// endregion
|
||||
|
||||
// region Helper functions
|
||||
private SampleEntity randomEntity(String message) {
|
||||
protected SampleEntity randomEntity(@Nullable String message) {
|
||||
|
||||
SampleEntity entity = new SampleEntity();
|
||||
entity.setId(UUID.randomUUID().toString());
|
||||
|
||||
+83
@@ -31,6 +31,7 @@ import org.springframework.data.elasticsearch.annotations.Document;
|
||||
import org.springframework.data.elasticsearch.annotations.Field;
|
||||
import org.springframework.data.elasticsearch.annotations.FieldType;
|
||||
import org.springframework.data.elasticsearch.annotations.Mapping;
|
||||
import org.springframework.data.elasticsearch.annotations.ScriptedField;
|
||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
import org.springframework.data.elasticsearch.core.query.Criteria;
|
||||
import org.springframework.data.elasticsearch.core.query.CriteriaQuery;
|
||||
@@ -95,6 +96,88 @@ public abstract class RuntimeFieldsIntegrationTests {
|
||||
assertThat(searchHits.getSearchHit(0).getId()).isEqualTo("1");
|
||||
}
|
||||
|
||||
@Test // #2727
|
||||
@DisplayName("should return runtime fields values")
|
||||
void shouldReturnRuntimeFieldsValues() {
|
||||
|
||||
var entity = new SAREntity();
|
||||
entity.setId("42");
|
||||
entity.setValue(3);
|
||||
|
||||
operations.save(entity);
|
||||
|
||||
var runtimeField1 = getRuntimeField("scriptedValue1", 2);
|
||||
var runtimeField2 = getRuntimeField("scriptedValue2", 3);
|
||||
|
||||
var query = CriteriaQuery.builder(Criteria.where("value").is(3)).build();
|
||||
query.addRuntimeField(runtimeField1);
|
||||
query.addRuntimeField(runtimeField2);
|
||||
query.addSourceFilter(new FetchSourceFilterBuilder().withIncludes("*").build());
|
||||
query.addFields("scriptedValue1", "scriptedValue2");
|
||||
var searchHits = operations.search(query, SAREntity.class);
|
||||
|
||||
assertThat(searchHits.getTotalHits()).isEqualTo(1);
|
||||
var foundEntity = searchHits.getSearchHit(0).getContent();
|
||||
assertThat(foundEntity.value).isEqualTo(3);
|
||||
assertThat(foundEntity.getScriptedValue1()).isEqualTo(6);
|
||||
assertThat(foundEntity.getScriptedValue2()).isEqualTo(9);
|
||||
}
|
||||
|
||||
@Document(indexName = "#{@indexNameProvider.indexName()}-sar")
|
||||
public static class SAREntity {
|
||||
@Nullable private String id;
|
||||
@Field(type = FieldType.Integer)
|
||||
@Nullable Integer value;
|
||||
@ScriptedField
|
||||
@Nullable Integer scriptedValue1;
|
||||
@ScriptedField
|
||||
@Nullable Integer scriptedValue2;
|
||||
// getter and setter omitted
|
||||
|
||||
@Nullable
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(@Nullable String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(@Nullable Integer value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getScriptedValue1() {
|
||||
return scriptedValue1;
|
||||
}
|
||||
|
||||
public void setScriptedValue1(@Nullable Integer scriptedValue1) {
|
||||
this.scriptedValue1 = scriptedValue1;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getScriptedValue2() {
|
||||
return scriptedValue2;
|
||||
}
|
||||
|
||||
public void setScriptedValue2(@Nullable Integer scriptedValue2) {
|
||||
this.scriptedValue2 = scriptedValue2;
|
||||
}
|
||||
}
|
||||
|
||||
private static RuntimeField getRuntimeField(String fieldName, int factor) {
|
||||
return new RuntimeField(
|
||||
fieldName,
|
||||
"long",
|
||||
String.format("emit(doc['value'].size() > 0 ? doc['value'].value * %d : 0)", factor));
|
||||
}
|
||||
|
||||
@Test // #2431
|
||||
@DisplayName("should return value from runtime field defined in mapping")
|
||||
void shouldReturnValueFromRuntimeFieldDefinedInMapping() {
|
||||
|
||||
+118
-21
@@ -909,22 +909,109 @@ public class MappingElasticsearchConverterUnitTests {
|
||||
assertEquals(expected, document.toJson(), false);
|
||||
}
|
||||
|
||||
@Test // #2627
|
||||
@DisplayName("should write Map containing collection containing map")
|
||||
void shouldWriteMapContainingCollectionContainingMap() throws JSONException {
|
||||
|
||||
class EntityWithMapCollectionMap {
|
||||
Map<String, Object> map;
|
||||
}
|
||||
class InnerEntity {
|
||||
String prop1;
|
||||
|
||||
String prop2;
|
||||
|
||||
public InnerEntity() {}
|
||||
|
||||
public InnerEntity(String prop1, String prop2) {
|
||||
this.prop1 = prop1;
|
||||
this.prop2 = prop2;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var entity = new EntityWithMapCollectionMap();
|
||||
entity.map = Collections.singletonMap("collection",
|
||||
Collections.singletonList(Collections.singletonMap("destination", new InnerEntity("prop1", "prop2"))));
|
||||
|
||||
var expected = """
|
||||
{
|
||||
"_class": "org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverterUnitTests$1EntityWithMapCollectionMap",
|
||||
"map": {
|
||||
"collection": [
|
||||
{
|
||||
"destination": {
|
||||
"_class": "org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverterUnitTests$1InnerEntity",
|
||||
"prop1": "prop1",
|
||||
"prop2": "prop2"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
Document document = Document.create();
|
||||
|
||||
mappingElasticsearchConverter.write(entity, document);
|
||||
|
||||
assertEquals(expected, document.toJson(), false);
|
||||
}
|
||||
|
||||
@Nested
|
||||
class RangeTests {
|
||||
|
||||
static final String JSON = "{"
|
||||
+ "\"_class\":\"org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverterUnitTests$RangeTests$RangeEntity\","
|
||||
+ "\"integerRange\":{\"gt\":\"1\",\"lt\":\"10\"}," //
|
||||
+ "\"floatRange\":{\"gte\":\"1.2\",\"lte\":\"2.5\"}," //
|
||||
+ "\"longRange\":{\"gt\":\"2\",\"lte\":\"5\"}," //
|
||||
+ "\"doubleRange\":{\"gte\":\"3.2\",\"lt\":\"7.4\"}," //
|
||||
+ "\"dateRange\":{\"gte\":\"1970-01-01T00:00:00.000Z\",\"lte\":\"1970-01-01T01:00:00.000Z\"}," //
|
||||
+ "\"localDateRange\":{\"gte\":\"2021-07-06\"}," //
|
||||
+ "\"localTimeRange\":{\"gte\":\"00:30:00.000\",\"lt\":\"02:30:00.000\"}," //
|
||||
+ "\"localDateTimeRange\":{\"gt\":\"2021-01-01T00:30:00.000\",\"lt\":\"2021-01-01T02:30:00.000\"}," //
|
||||
+ "\"offsetTimeRange\":{\"gte\":\"00:30:00.000+02:00\",\"lt\":\"02:30:00.000+02:00\"}," //
|
||||
+ "\"zonedDateTimeRange\":{\"gte\":\"2021-01-01T00:30:00.000+02:00\",\"lte\":\"2021-01-01T00:30:00.000+02:00\"}," //
|
||||
+ "\"nullRange\":null}";
|
||||
static final String JSON = """
|
||||
{
|
||||
"_class": "org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverterUnitTests$RangeTests$RangeEntity",
|
||||
"integerRange": {
|
||||
"gt": "1",
|
||||
"lt": "10"
|
||||
},
|
||||
"floatRange": {
|
||||
"gte": "1.2",
|
||||
"lte": "2.5"
|
||||
},
|
||||
"longRange": {
|
||||
"gt": "2",
|
||||
"lte": "5"
|
||||
},
|
||||
"doubleRange": {
|
||||
"gte": "3.2",
|
||||
"lt": "7.4"
|
||||
},
|
||||
"dateRange": {
|
||||
"gte": "1970-01-01T00:00:00.000Z",
|
||||
"lte": "1970-01-01T01:00:00.000Z"
|
||||
},
|
||||
"localDateRange": {
|
||||
"gte": "2021-07-06"
|
||||
},
|
||||
"localTimeRange": {
|
||||
"gte": "00:30:00.000",
|
||||
"lt": "02:30:00.000"
|
||||
},
|
||||
"localDateTimeRange": {
|
||||
"gt": "2021-01-01T00:30:00.000",
|
||||
"lt": "2021-01-01T02:30:00.000"
|
||||
},
|
||||
"offsetTimeRange": {
|
||||
"gte": "00:30:00.000+02:00",
|
||||
"lt": "02:30:00.000+02:00"
|
||||
},
|
||||
"zonedDateTimeRange": {
|
||||
"gte": "2021-01-01T00:30:00.000+02:00",
|
||||
"lte": "2021-01-01T00:30:00.000+02:00"
|
||||
},
|
||||
"nullRange": null,
|
||||
"integerRangeList": [
|
||||
{
|
||||
"gte": "2",
|
||||
"lte": "5"
|
||||
}
|
||||
]
|
||||
}
|
||||
""";
|
||||
|
||||
@Test
|
||||
public void shouldReadRanges() throws JSONException {
|
||||
@@ -955,6 +1042,7 @@ public class MappingElasticsearchConverterUnitTests {
|
||||
assertThat(e.getZonedDateTimeRange()).isEqualTo(
|
||||
Range.just(ZonedDateTime.of(LocalDate.of(2021, 1, 1), LocalTime.of(0, 30), ZoneOffset.ofHours(2))));
|
||||
assertThat(e.getNullRange()).isNull();
|
||||
assertThat(e.getIntegerRangeList()).containsExactly(Range.closed(2, 5));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -978,8 +1066,7 @@ public class MappingElasticsearchConverterUnitTests {
|
||||
entity.setZonedDateTimeRange(
|
||||
Range.just(ZonedDateTime.of(LocalDate.of(2021, 1, 1), LocalTime.of(0, 30), ZoneOffset.ofHours(2))));
|
||||
entity.setNullRange(null);
|
||||
|
||||
// when
|
||||
entity.setIntegerRangeList(List.of(Range.closed(2, 5)));
|
||||
Document document = mappingElasticsearchConverter.mapObject(entity);
|
||||
|
||||
// then
|
||||
@@ -1004,6 +1091,8 @@ public class MappingElasticsearchConverterUnitTests {
|
||||
@Field(type = FieldType.Date_Range) private Range<ZonedDateTime> zonedDateTimeRange;
|
||||
@Field(type = FieldType.Date_Range, storeNullValue = true) private Range<ZonedDateTime> nullRange;
|
||||
|
||||
@Field(type = FieldType.Integer_Range) private List<Range<Integer>> integerRangeList;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
@@ -1100,6 +1189,13 @@ public class MappingElasticsearchConverterUnitTests {
|
||||
this.nullRange = nullRange;
|
||||
}
|
||||
|
||||
public List<Range<Integer>> getIntegerRangeList() {
|
||||
return integerRangeList;
|
||||
}
|
||||
|
||||
public void setIntegerRangeList(List<Range<Integer>> integerRangeList) {
|
||||
this.integerRangeList = integerRangeList;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1953,12 +2049,12 @@ public class MappingElasticsearchConverterUnitTests {
|
||||
|
||||
@Language("JSON")
|
||||
var expected = """
|
||||
{
|
||||
"_class": "org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverterUnitTests$FieldNameDotsEntity",
|
||||
"id": "42",
|
||||
"dotted.field": "dotted field"
|
||||
}
|
||||
""";
|
||||
{
|
||||
"_class": "org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverterUnitTests$FieldNameDotsEntity",
|
||||
"id": "42",
|
||||
"dotted.field": "dotted field"
|
||||
}
|
||||
""";
|
||||
var entity = new FieldNameDotsEntity();
|
||||
entity.setId("42");
|
||||
entity.setDottedField("dotted field");
|
||||
@@ -3192,6 +3288,7 @@ public class MappingElasticsearchConverterUnitTests {
|
||||
this.mapToNotWriteWhenEmpty = mapToNotWriteWhenEmpty;
|
||||
}
|
||||
}
|
||||
|
||||
static class FieldNameDotsEntity {
|
||||
@Id
|
||||
@Nullable private String id;
|
||||
|
||||
+4
-3
@@ -63,13 +63,14 @@ public class PropertyValueConvertersUnitTests {
|
||||
|
||||
converters.add(new DatePropertyValueConverter(persistentProperty,
|
||||
Collections.singletonList(ElasticsearchDateConverter.of(DateFormat.basic_date))));
|
||||
Class<?> genericType = Object.class;
|
||||
converters.add(new DateRangePropertyValueConverter(persistentProperty,
|
||||
Collections.singletonList(ElasticsearchDateConverter.of(DateFormat.basic_date))));
|
||||
converters.add(new NumberRangePropertyValueConverter(persistentProperty));
|
||||
genericType, Collections.singletonList(ElasticsearchDateConverter.of(DateFormat.basic_date))));
|
||||
converters.add(new NumberRangePropertyValueConverter(persistentProperty, genericType));
|
||||
converters.add(new TemporalPropertyValueConverter(persistentProperty,
|
||||
Collections.singletonList(ElasticsearchDateConverter.of(DateFormat.basic_date))));
|
||||
converters.add(new TemporalRangePropertyValueConverter(persistentProperty,
|
||||
Collections.singletonList(ElasticsearchDateConverter.of(DateFormat.basic_date))));
|
||||
genericType, Collections.singletonList(ElasticsearchDateConverter.of(DateFormat.basic_date))));
|
||||
|
||||
return converters.stream().map(propertyValueConverter -> arguments(
|
||||
Named.of(propertyValueConverter.getClass().getSimpleName(), propertyValueConverter)));
|
||||
|
||||
+15
@@ -287,6 +287,12 @@ public abstract class MappingBuilderIntegrationTests extends MappingContextBaseT
|
||||
indexOps.createWithMapping();
|
||||
}
|
||||
|
||||
@Test // #2659
|
||||
@DisplayName("should write correct mapping for dense vector property")
|
||||
void shouldWriteCorrectMappingForDenseVectorProperty() {
|
||||
operations.indexOps(SimilarityEntity.class).createWithMapping();
|
||||
}
|
||||
|
||||
// region Entities
|
||||
@Document(indexName = "#{@indexNameProvider.indexName()}")
|
||||
static class Book {
|
||||
@@ -916,5 +922,14 @@ public abstract class MappingBuilderIntegrationTests extends MappingContextBaseT
|
||||
@Nullable
|
||||
@Field(name = "dotted.field", type = Text) private String dottedField;
|
||||
}
|
||||
|
||||
@Document(indexName = "#{@indexNameProvider.indexName()}")
|
||||
static class SimilarityEntity {
|
||||
@Nullable
|
||||
@Id private String id;
|
||||
|
||||
@Field(type = FieldType.Dense_Vector, dims = 42, similarity = "cosine") private double[] denseVector;
|
||||
}
|
||||
|
||||
// endregion
|
||||
}
|
||||
|
||||
+81
-14
@@ -62,7 +62,7 @@ public abstract class SearchAfterIntegrationTests {
|
||||
@Test
|
||||
@Order(java.lang.Integer.MAX_VALUE)
|
||||
void cleanup() {
|
||||
operations.indexOps(IndexCoordinates.of(indexNameProvider.getPrefix() + "*")).delete();
|
||||
operations.indexOps(IndexCoordinates.of(indexNameProvider.getPrefix() + '*')).delete();
|
||||
}
|
||||
|
||||
@Test // #1143
|
||||
@@ -85,11 +85,11 @@ public abstract class SearchAfterIntegrationTests {
|
||||
query.setSearchAfter(searchAfter);
|
||||
SearchHits<Entity> searchHits = operations.search(query, Entity.class);
|
||||
|
||||
if (searchHits.getSearchHits().size() == 0) {
|
||||
if (searchHits.getSearchHits().isEmpty()) {
|
||||
break;
|
||||
}
|
||||
foundEntities.addAll(searchHits.stream().map(SearchHit::getContent).collect(Collectors.toList()));
|
||||
searchAfter = searchHits.getSearchHit((int) (searchHits.getSearchHits().size() - 1)).getSortValues();
|
||||
foundEntities.addAll(searchHits.stream().map(SearchHit::getContent).toList());
|
||||
searchAfter = searchHits.getSearchHit(searchHits.getSearchHits().size() - 1).getSortValues();
|
||||
|
||||
if (++loop > 10) {
|
||||
fail("loop not terminating");
|
||||
@@ -99,16 +99,69 @@ public abstract class SearchAfterIntegrationTests {
|
||||
assertThat(foundEntities).containsExactlyElementsOf(entities);
|
||||
}
|
||||
|
||||
@Test // #2678
|
||||
@DisplayName("should be able to handle different search after type values including null")
|
||||
void shouldBeAbleToHandleDifferentSearchAfterTypeValuesIncludingNull() {
|
||||
|
||||
List<Entity> entities = IntStream.rangeClosed(1, 10)
|
||||
.mapToObj(i -> {
|
||||
var message = (i % 2 == 0) ? null : "message " + i;
|
||||
var value = (i % 3 == 0) ? null : (long) i;
|
||||
return new Entity((long) i, message, value);
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
operations.save(entities);
|
||||
|
||||
Query query = Query.findAll();
|
||||
query.setPageable(PageRequest.of(0, 3));
|
||||
query.addSort(Sort.by(Sort.Direction.ASC, "id"));
|
||||
query.addSort(Sort.by(Sort.Direction.ASC, "keyword"));
|
||||
query.addSort(Sort.by(Sort.Direction.ASC, "value"));
|
||||
|
||||
List<Object> searchAfter = null;
|
||||
List<Entity> foundEntities = new ArrayList<>();
|
||||
|
||||
int loop = 0;
|
||||
do {
|
||||
query.setSearchAfter(searchAfter);
|
||||
SearchHits<Entity> searchHits = operations.search(query, Entity.class);
|
||||
|
||||
if (searchHits.getSearchHits().isEmpty()) {
|
||||
break;
|
||||
}
|
||||
foundEntities.addAll(searchHits.stream().map(SearchHit::getContent).toList());
|
||||
searchAfter = searchHits.getSearchHit(searchHits.getSearchHits().size() - 1).getSortValues();
|
||||
|
||||
if (++loop > 10) {
|
||||
fail("loop not terminating");
|
||||
}
|
||||
} while (true);
|
||||
|
||||
assertThat(foundEntities).containsExactlyElementsOf(entities);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Document(indexName = "#{@indexNameProvider.indexName()}")
|
||||
private static class Entity {
|
||||
@Nullable
|
||||
@Id private Long id;
|
||||
@Nullable
|
||||
@Field(type = FieldType.Text) private String message;
|
||||
@Field(type = FieldType.Keyword) private String keyword;
|
||||
|
||||
public Entity(@Nullable Long id, @Nullable String message) {
|
||||
@Nullable
|
||||
@Field(type = FieldType.Long) private Long value;
|
||||
|
||||
public Entity() {}
|
||||
|
||||
public Entity(@Nullable Long id, @Nullable String keyword) {
|
||||
this.id = id;
|
||||
this.message = message;
|
||||
this.keyword = keyword;
|
||||
}
|
||||
|
||||
public Entity(@Nullable Long id, @Nullable String keyword, @Nullable Long value) {
|
||||
this.id = id;
|
||||
this.keyword = keyword;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -121,30 +174,44 @@ public abstract class SearchAfterIntegrationTests {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getMessage() {
|
||||
return message;
|
||||
public String getKeyword() {
|
||||
return keyword;
|
||||
}
|
||||
|
||||
public void setMessage(@Nullable String message) {
|
||||
this.message = message;
|
||||
public void setKeyword(@Nullable String keyword) {
|
||||
this.keyword = keyword;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Long getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(@Nullable Long value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o)
|
||||
return true;
|
||||
if (!(o instanceof Entity entity))
|
||||
if (o == null || getClass() != o.getClass())
|
||||
return false;
|
||||
|
||||
Entity entity = (Entity) o;
|
||||
|
||||
if (!Objects.equals(id, entity.id))
|
||||
return false;
|
||||
return Objects.equals(message, entity.message);
|
||||
if (!Objects.equals(keyword, entity.keyword))
|
||||
return false;
|
||||
return Objects.equals(value, entity.value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = id != null ? id.hashCode() : 0;
|
||||
result = 31 * result + (message != null ? message.hashCode() : 0);
|
||||
result = 31 * result + (keyword != null ? keyword.hashCode() : 0);
|
||||
result = 31 * result + (value != null ? value.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user