Compare commits

...

5 Commits

Author SHA1 Message Date
Christoph Strobl
532b460067 Release version 4.1 GA (2023.0.0).
See #4369
2023-05-12 14:14:38 +02:00
Christoph Strobl
af846a962a Prepare 4.1 GA (2023.0.0).
See #4369
2023-05-12 14:14:05 +02:00
Mark Paluch
776dadeac8 Polishing.
Introduce has…() and getRequired…() methods for comment and max time limit to remove code duplications.

See #4374
Original pull request: #4378
2023-05-11 10:13:56 +02:00
Christoph Strobl
629dfc187e Fix missing query options when calling MongoOperations#count.
This commit makes sure to forward maxTimeMsec and comment options from the query to the CountOptions.

Closes: #4374
Original pull request: #4378
2023-05-10 15:19:17 +02:00
Christoph Strobl
289438b1e4 Fix regression in value to String mapping.
Previous versions allow arbitrary values to be mapped to an string property by calling the ObjectToString converter. This behaviour got lost and is not reestablished.

Closes #4371
Original pull request #4373
2023-05-10 14:52:53 +02:00
15 changed files with 171 additions and 41 deletions

10
pom.xml
View File

@@ -5,7 +5,7 @@
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>4.1.0-SNAPSHOT</version>
<version>4.1.0</version>
<packaging>pom</packaging>
<name>Spring Data MongoDB</name>
@@ -15,7 +15,7 @@
<parent>
<groupId>org.springframework.data.build</groupId>
<artifactId>spring-data-parent</artifactId>
<version>3.1.0-SNAPSHOT</version>
<version>3.1.0</version>
</parent>
<modules>
@@ -26,7 +26,7 @@
<properties>
<project.type>multi</project.type>
<dist.id>spring-data-mongodb</dist.id>
<springdata.commons>3.1.0-SNAPSHOT</springdata.commons>
<springdata.commons>3.1.0</springdata.commons>
<mongo>4.9.1</mongo>
<mongo.reactivestreams>${mongo}</mongo.reactivestreams>
<jmh.version>1.19</jmh.version>
@@ -145,8 +145,8 @@
<repositories>
<repository>
<id>spring-libs-snapshot</id>
<url>https://repo.spring.io/libs-snapshot</url>
<id>spring-libs-release</id>
<url>https://repo.spring.io/libs-release</url>
<snapshots>
<enabled>true</enabled>
</snapshots>

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>4.1.0-SNAPSHOT</version>
<version>4.1.0</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@@ -15,7 +15,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>4.1.0-SNAPSHOT</version>
<version>4.1.0</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@@ -13,7 +13,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>4.1.0-SNAPSHOT</version>
<version>4.1.0</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@@ -1927,7 +1927,7 @@ public class MongoTemplate
if (query.getLimit() > 0 && mapReduceOptions != null && mapReduceOptions.getLimit() == null) {
mapReduce = mapReduce.limit(query.getLimit());
}
if (query.getMeta().getMaxTimeMsec() != null) {
if (query.getMeta().hasMaxTime()) {
mapReduce = mapReduce.maxTime(query.getMeta().getMaxTimeMsec(), TimeUnit.MILLISECONDS);
}
@@ -3326,12 +3326,12 @@ public class MongoTemplate
if (meta.hasValues()) {
if (StringUtils.hasText(meta.getComment())) {
cursorToUse = cursorToUse.comment(meta.getComment());
if (meta.hasComment()) {
cursorToUse = cursorToUse.comment(meta.getRequiredComment());
}
if (meta.getMaxTimeMsec() != null) {
cursorToUse = cursorToUse.maxTime(meta.getMaxTimeMsec(), TimeUnit.MILLISECONDS);
if (meta.hasMaxTime()) {
cursorToUse = cursorToUse.maxTime(meta.getRequiredMaxTimeMsec(), TimeUnit.MILLISECONDS);
}
if (meta.getCursorBatchSize() != null) {

View File

@@ -21,6 +21,7 @@ import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -53,6 +54,7 @@ import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import org.springframework.data.mongodb.core.mapping.ShardKey;
import org.springframework.data.mongodb.core.query.BasicQuery;
import org.springframework.data.mongodb.core.query.Collation;
import org.springframework.data.mongodb.core.query.Meta;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.UpdateDefinition;
import org.springframework.data.mongodb.core.query.UpdateDefinition.ArrayFilter;
@@ -388,7 +390,7 @@ class QueryOperations {
for (Entry<String, Object> entry : fields.entrySet()) {
if (entry.getValue() instanceof MongoExpression mongoExpression) {
if (entry.getValue()instanceof MongoExpression mongoExpression) {
AggregationOperationContext ctx = entity == null ? Aggregation.DEFAULT_CONTEXT
: new RelaxedTypeBasedAggregationOperationContext(entity.getType(), mappingContext, queryMapper);
@@ -564,10 +566,23 @@ class QueryOperations {
if (query.getLimit() > 0) {
options.limit(query.getLimit());
}
if (query.getSkip() > 0) {
options.skip((int) query.getSkip());
}
Meta meta = query.getMeta();
if (meta.hasValues()) {
if (meta.hasMaxTime()) {
options.maxTime(meta.getRequiredMaxTimeMsec(), TimeUnit.MILLISECONDS);
}
if (meta.hasComment()) {
options.comment(meta.getComment());
}
}
HintFunction hintFunction = HintFunction.from(query.getHint());
if (hintFunction.isPresent()) {

View File

@@ -2078,8 +2078,9 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
publisher.sort(mappedSort);
}
if (filterQuery.getMeta().getMaxTimeMsec() != null) {
publisher.maxTime(filterQuery.getMeta().getMaxTimeMsec(), TimeUnit.MILLISECONDS);
Meta meta = filterQuery.getMeta();
if (meta.hasMaxTime()) {
publisher.maxTime(meta.getRequiredMaxTimeMsec(), TimeUnit.MILLISECONDS);
}
if (filterQuery.getLimit() > 0 || (options.getLimit() != null)) {
@@ -3222,12 +3223,12 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
if (meta.hasValues()) {
if (StringUtils.hasText(meta.getComment())) {
findPublisherToUse = findPublisherToUse.comment(meta.getComment());
if (meta.hasComment()) {
findPublisherToUse = findPublisherToUse.comment(meta.getRequiredComment());
}
if (meta.getMaxTimeMsec() != null) {
findPublisherToUse = findPublisherToUse.maxTime(meta.getMaxTimeMsec(), TimeUnit.MILLISECONDS);
if (meta.hasMaxTime()) {
findPublisherToUse = findPublisherToUse.maxTime(meta.getRequiredMaxTimeMsec(), TimeUnit.MILLISECONDS);
}
if (meta.getCursorBatchSize() != null) {

View File

@@ -2313,8 +2313,10 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
if (source instanceof Collection<?> collection) {
Class<?> rawType = typeHint.getType();
if (!Object.class.equals(rawType)) {
if (!Object.class.equals(rawType) && !String.class.equals(rawType)) {
if (!rawType.isArray() && !ClassUtils.isAssignable(Iterable.class, rawType)) {
throw new MappingException(
String.format(INCOMPATIBLE_TYPES, source, source.getClass(), rawType, getPath()));
}
@@ -2343,11 +2345,6 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
return (S) dbRefConverter.convert(context, dbRef, typeHint);
}
if (source instanceof Collection) {
throw new MappingException(
String.format(INCOMPATIBLE_TYPES, source, BasicDBList.class, typeHint.getType(), getPath()));
}
if (BsonUtils.supportsBson(source)) {
return (S) documentConverter.convert(context, BsonUtils.asBson(source), typeHint);
}

View File

@@ -69,6 +69,19 @@ public class Meta {
this.allowDiskUse = source.allowDiskUse;
}
/**
* Return whether the maximum time limit for processing operations is set.
*
* @return {@code true} if set; {@code false} otherwise.
* @since 4.0.6
*/
public boolean hasMaxTime() {
Long maxTimeMsec = getMaxTimeMsec();
return maxTimeMsec != null && maxTimeMsec > 0;
}
/**
* @return {@literal null} if not set.
*/
@@ -77,6 +90,26 @@ public class Meta {
return getValue(MetaKey.MAX_TIME_MS.key);
}
/**
* Returns the required maximum time limit in milliseconds or throws {@link IllegalStateException} if the maximum time
* limit is not set.
*
* @return the maximum time limit in milliseconds for processing operations.
* @throws IllegalStateException if the maximum time limit is not set
* @see #hasMaxTime()
* @since 4.0.6
*/
public Long getRequiredMaxTimeMsec() {
Long maxTimeMsec = getMaxTimeMsec();
if (maxTimeMsec == null) {
throw new IllegalStateException("Maximum time limit in milliseconds not set");
}
return maxTimeMsec;
}
/**
* Set the maximum time limit in milliseconds for processing operations.
*
@@ -99,12 +132,13 @@ public class Meta {
}
/**
* Add a comment to the query that is propagated to the profile log.
* Return whether the comment is set.
*
* @param comment
* @return {@code true} if set; {@code false} otherwise.
* @since 4.0.6
*/
public void setComment(String comment) {
setValue(MetaKey.COMMENT.key, comment);
public boolean hasComment() {
return StringUtils.hasText(getComment());
}
/**
@@ -115,6 +149,34 @@ public class Meta {
return getValue(MetaKey.COMMENT.key);
}
/**
* Returns the required comment or throws {@link IllegalStateException} if the comment is not set.
*
* @return the comment.
* @throws IllegalStateException if the comment is not set
* @see #hasComment()
* @since 4.0.6
*/
public String getRequiredComment() {
String comment = getComment();
if (comment == null) {
throw new IllegalStateException("Comment not set");
}
return comment;
}
/**
* Add a comment to the query that is propagated to the profile log.
*
* @param comment
*/
public void setComment(String comment) {
setValue(MetaKey.COMMENT.key, comment);
}
/**
* @return {@literal null} if not set.
* @since 2.1

View File

@@ -16,7 +16,6 @@
package org.springframework.data.mongodb.repository.query;
import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.function.IntUnaryOperator;
import java.util.function.LongUnaryOperator;
@@ -25,7 +24,6 @@ import org.bson.Document;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort.Order;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationOperation;
import org.springframework.data.mongodb.core.aggregation.AggregationOptions;
import org.springframework.data.mongodb.core.aggregation.AggregationPipeline;
import org.springframework.data.mongodb.core.convert.MongoConverter;
@@ -37,7 +35,6 @@ import org.springframework.expression.ExpressionParser;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
* Internal utility class to help avoid duplicate code required in both the reactive and the sync {@link Aggregation}
@@ -84,7 +81,7 @@ abstract class AggregationUtils {
Meta meta = queryMethod.getQueryMetaAttributes();
if (StringUtils.hasText(meta.getComment())) {
if (meta.hasComment()) {
builder.comment(meta.getComment());
}
@@ -92,8 +89,8 @@ abstract class AggregationUtils {
builder.cursorBatchSize(meta.getCursorBatchSize());
}
if (meta.getMaxTimeMsec() != null && meta.getMaxTimeMsec() > 0) {
builder.maxTime(Duration.ofMillis(meta.getMaxTimeMsec()));
if (meta.hasMaxTime()) {
builder.maxTime(Duration.ofMillis(meta.getRequiredMaxTimeMsec()));
}
if (meta.getAllowDiskUse() != null) {

View File

@@ -2402,6 +2402,26 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
verify(collection).countDocuments(any(Document.class), any());
}
@Test // GH-4374
void countConsidersMaxTimeMs() {
template.count(new BasicQuery("{ 'spring' : 'data-mongodb' }").maxTimeMsec(5000), Human.class);
ArgumentCaptor<CountOptions> options = ArgumentCaptor.forClass(CountOptions.class);
verify(collection).countDocuments(any(Document.class), options.capture());
assertThat(options.getValue().getMaxTime(TimeUnit.MILLISECONDS)).isEqualTo(5000);
}
@Test // GH-4374
void countPassesOnComment() {
template.count(new BasicQuery("{ 'spring' : 'data-mongodb' }").comment("rocks!"), Human.class);
ArgumentCaptor<CountOptions> options = ArgumentCaptor.forClass(CountOptions.class);
verify(collection).countDocuments(any(Document.class), options.capture());
assertThat(options.getValue().getComment()).isEqualTo(BsonUtils.simpleToBsonValue("rocks!"));
}
@Test // GH-3984
void templatePassesOnTimeSeriesOptionsWhenNoTypeGiven() {

View File

@@ -23,6 +23,7 @@ import static org.springframework.data.mongodb.test.util.Assertions.assertThat;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.mongodb.util.BsonUtils;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
@@ -1532,6 +1533,26 @@ public class ReactiveMongoTemplateUnitTests {
verify(collection).countDocuments(any(Document.class), any());
}
@Test // GH-4374
void countConsidersMaxTimeMs() {
template.count(new BasicQuery("{ 'spring' : 'data-mongodb' }").maxTimeMsec(5000), Person.class).subscribe();
ArgumentCaptor<CountOptions> options = ArgumentCaptor.forClass(CountOptions.class);
verify(collection).countDocuments(any(Document.class), options.capture());
assertThat(options.getValue().getMaxTime(TimeUnit.MILLISECONDS)).isEqualTo(5000);
}
@Test // GH-4374
void countPassesOnComment() {
template.count(new BasicQuery("{ 'spring' : 'data-mongodb' }").comment("rocks!"), Person.class).subscribe();
ArgumentCaptor<CountOptions> options = ArgumentCaptor.forClass(CountOptions.class);
verify(collection).countDocuments(any(Document.class), options.capture());
assertThat(options.getValue().getComment()).isEqualTo(BsonUtils.simpleToBsonValue("rocks!"));
}
@Test // GH-2911
void insertErrorsOnPublisher() {

View File

@@ -2831,6 +2831,18 @@ class MappingMongoConverterUnitTests {
assertThat(converter.read(Cyclic.class, source).cycle.value).isEqualTo("v2");
}
@Test // GH-4371
void shouldConvertTypesToStringTargetType() {
org.bson.Document source = org.bson.Document.parse("""
{
city : ["Gotham", "Metropolis"]
}
""");
assertThat(converter.read(Address.class, source).city).isEqualTo("Gotham,Metropolis");
}
static class GenericType<T> {
T content;
}

View File

@@ -274,7 +274,8 @@ public class MongoQueryMethodUnitTests {
void queryCreationForUpdateMethodFailsOnInvalidReturnType() throws Exception {
assertThatExceptionOfType(IllegalStateException.class) //
.isThrownBy(() -> queryMethod(InvalidUpdateMethodRepo.class, "findAndIncrementVisitsByFirstname", String.class).verify()) //
.isThrownBy(() -> queryMethod(InvalidUpdateMethodRepo.class, "findAndIncrementVisitsByFirstname", String.class)
.verify()) //
.withMessageContaining("Update") //
.withMessageContaining("numeric") //
.withMessageContaining("findAndIncrementVisitsByFirstname");
@@ -283,7 +284,8 @@ public class MongoQueryMethodUnitTests {
@Test // GH-3002
void readsCollationFromAtCollationAnnotation() throws Exception {
MongoQueryMethod method = queryMethod(PersonRepository.class, "findWithCollationFromAtCollationByFirstname", String.class);
MongoQueryMethod method = queryMethod(PersonRepository.class, "findWithCollationFromAtCollationByFirstname",
String.class);
assertThat(method.hasAnnotatedCollation()).isTrue();
assertThat(method.getAnnotatedCollation()).isEqualTo("en_US");
@@ -292,7 +294,8 @@ public class MongoQueryMethodUnitTests {
@Test // GH-3002
void readsCollationFromAtQueryAnnotation() throws Exception {
MongoQueryMethod method = queryMethod(PersonRepository.class, "findWithCollationFromAtQueryByFirstname", String.class);
MongoQueryMethod method = queryMethod(PersonRepository.class, "findWithCollationFromAtQueryByFirstname",
String.class);
assertThat(method.hasAnnotatedCollation()).isTrue();
assertThat(method.getAnnotatedCollation()).isEqualTo("en_US");
@@ -301,7 +304,8 @@ public class MongoQueryMethodUnitTests {
@Test // GH-3002
void annotatedCollationClashSelectsAtCollationAnnotationValue() throws Exception {
MongoQueryMethod method = queryMethod(PersonRepository.class, "findWithMultipleCollationsFromAtQueryAndAtCollationByFirstname", String.class);
MongoQueryMethod method = queryMethod(PersonRepository.class,
"findWithMultipleCollationsFromAtQueryAndAtCollationByFirstname", String.class);
assertThat(method.hasAnnotatedCollation()).isTrue();
assertThat(method.getAnnotatedCollation()).isEqualTo("de_AT");

View File

@@ -1,4 +1,4 @@
Spring Data MongoDB 4.1 RC1 (2023.0.0)
Spring Data MongoDB 4.1 GA (2023.0.0)
Copyright (c) [2010-2019] Pivotal Software, Inc.
This product is licensed to you under the Apache License, Version 2.0 (the "License").
@@ -44,5 +44,6 @@ conditions of the subcomponent's license, as noted in the LICENSE file.