Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
adc76123a7 | ||
|
|
e642199abe | ||
|
|
0be2237c4c | ||
|
|
250fe5150d | ||
|
|
50245ca37a | ||
|
|
cc234a84b9 | ||
|
|
b06e8d5f9e | ||
|
|
bb65692c0c | ||
|
|
d3b4972d75 | ||
|
|
4554b62171 | ||
|
|
9920777e55 | ||
|
|
fc26641235 | ||
|
|
c9d72337fd | ||
|
|
c1a587692f | ||
|
|
48353d18ae | ||
|
|
651e5603a6 | ||
|
|
72170c3291 | ||
|
|
a8dbfb96cf | ||
|
|
82917db02f | ||
|
|
e581d9d208 | ||
|
|
d21dddf004 | ||
|
|
0d9cc4ae1c | ||
|
|
1135f007e0 | ||
|
|
486c9cb98e | ||
|
|
1c86166aaf | ||
|
|
40567df6a8 | ||
|
|
9038d7c6df | ||
|
|
c48b2c6b9b | ||
|
|
3a48a2c7d2 | ||
|
|
9cefd61cf5 | ||
|
|
b8995791a8 | ||
|
|
56f2a7ac0f |
4
.mvn/wrapper/maven-wrapper.properties
vendored
4
.mvn/wrapper/maven-wrapper.properties
vendored
@@ -1,2 +1,2 @@
|
||||
#Thu Apr 06 16:17:30 CEST 2023
|
||||
distributionUrl=https\://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.1/apache-maven-3.9.1-bin.zip
|
||||
#Mon Aug 14 07:45:45 EDT 2023
|
||||
distributionUrl=https\://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.4/apache-maven-3.9.4-bin.zip
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Java versions
|
||||
java.main.tag=17.0.6_10-jdk-focal
|
||||
java.main.tag=17.0.8_7-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.18
|
||||
docker.mongodb.5.0.version=5.0.14
|
||||
docker.mongodb.6.0.version=6.0.4
|
||||
docker.mongodb.4.4.version=4.4.23
|
||||
docker.mongodb.5.0.version=5.0.19
|
||||
docker.mongodb.6.0.version=6.0.8
|
||||
|
||||
# Supported versions of Redis
|
||||
docker.redis.6.version=6.2.10
|
||||
docker.redis.6.version=6.2.13
|
||||
|
||||
# Supported versions of Cassandra
|
||||
docker.cassandra.3.version=3.11.14
|
||||
docker.cassandra.3.version=3.11.15
|
||||
|
||||
# Docker environment settings
|
||||
docker.java.inside.basic=-v $HOME:/tmp/jenkins-home
|
||||
|
||||
36
pom.xml
36
pom.xml
@@ -5,7 +5,7 @@
|
||||
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||
<version>4.0.6</version>
|
||||
<version>4.0.9</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.0.6</version>
|
||||
<version>3.0.9</version>
|
||||
</parent>
|
||||
|
||||
<modules>
|
||||
@@ -26,7 +26,7 @@
|
||||
<properties>
|
||||
<project.type>multi</project.type>
|
||||
<dist.id>spring-data-mongodb</dist.id>
|
||||
<springdata.commons>3.0.6</springdata.commons>
|
||||
<springdata.commons>3.0.9</springdata.commons>
|
||||
<mongo>4.8.2</mongo>
|
||||
<mongo.reactivestreams>${mongo}</mongo.reactivestreams>
|
||||
<jmh.version>1.19</jmh.version>
|
||||
@@ -144,34 +144,8 @@
|
||||
</dependencies>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>spring-libs-release</id>
|
||||
<url>https://repo.spring.io/libs-release</url>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>sonatype-libs-snapshot</id>
|
||||
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
|
||||
<releases>
|
||||
<enabled>false</enabled>
|
||||
</releases>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
|
||||
|
||||
</repositories>
|
||||
|
||||
<pluginRepositories>
|
||||
<pluginRepository>
|
||||
<id>spring-plugins-release</id>
|
||||
<url>https://repo.spring.io/plugins-release</url>
|
||||
</pluginRepository>
|
||||
<pluginRepository>
|
||||
<id>spring-libs-milestone</id>
|
||||
<url>https://repo.spring.io/libs-milestone</url>
|
||||
</pluginRepository>
|
||||
</pluginRepositories>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||
<version>4.0.6</version>
|
||||
<version>4.0.9</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||
<version>4.0.6</version>
|
||||
<version>4.0.9</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -45,15 +45,4 @@
|
||||
|
||||
</build>
|
||||
|
||||
<pluginRepositories>
|
||||
<pluginRepository>
|
||||
<id>spring-plugins-release</id>
|
||||
<url>https://repo.spring.io/plugins-release</url>
|
||||
</pluginRepository>
|
||||
<pluginRepository>
|
||||
<id>spring-plugins-snapshot</id>
|
||||
<url>https://repo.spring.io/libs-snapshot</url>
|
||||
</pluginRepository>
|
||||
</pluginRepositories>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||
<version>4.0.6</version>
|
||||
<version>4.0.9</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
||||
@@ -203,8 +203,9 @@ class MappingMongoJsonSchemaCreator implements MongoJsonSchemaCreator {
|
||||
target.properties(nestedProperties.toArray(new JsonSchemaProperty[0])), required));
|
||||
}
|
||||
}
|
||||
return targetProperties.size() == 1 ? targetProperties.iterator().next()
|
||||
JsonSchemaProperty schemaProperty = targetProperties.size() == 1 ? targetProperties.iterator().next()
|
||||
: JsonSchemaProperty.merged(targetProperties);
|
||||
return applyEncryptionDataIfNecessary(property, schemaProperty);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ class AggregationOperationRenderer {
|
||||
contextToUse = new InheritingExposedFieldsAggregationOperationContext(fields, contextToUse);
|
||||
} else {
|
||||
contextToUse = fields.exposesNoFields() ? DEFAULT_CONTEXT
|
||||
: new ExposedFieldsAggregationOperationContext(exposedFieldsOperation.getFields(), contextToUse);
|
||||
: new ExposedFieldsAggregationOperationContext(fields, contextToUse);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ public class ArrayOperators {
|
||||
|
||||
private final @Nullable String fieldReference;
|
||||
private final @Nullable AggregationExpression expression;
|
||||
private final @Nullable Collection values;
|
||||
private final @Nullable Collection<?> values;
|
||||
|
||||
/**
|
||||
* Creates new {@link ArrayOperatorFactory} for given {@literal fieldReference}.
|
||||
@@ -214,6 +214,10 @@ public class ArrayOperators {
|
||||
return Filter.filter(fieldReference);
|
||||
}
|
||||
|
||||
if (usesExpression()) {
|
||||
return Filter.filter(expression);
|
||||
}
|
||||
|
||||
Assert.state(values != null, "Values must not be null");
|
||||
return Filter.filter(new ArrayList<>(values));
|
||||
}
|
||||
@@ -317,7 +321,8 @@ public class ArrayOperators {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that takes the associated array and sorts it by the given {@link Sort order}.
|
||||
* Creates new {@link AggregationExpression} that takes the associated array and sorts it by the given {@link Sort
|
||||
* order}.
|
||||
*
|
||||
* @return new instance of {@link SortArray}.
|
||||
* @since 4.0
|
||||
@@ -397,8 +402,8 @@ public class ArrayOperators {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AggregationExpression} that return the last element in the given array.
|
||||
* <strong>NOTE:</strong> Requires MongoDB 4.4 or later.
|
||||
* Creates new {@link AggregationExpression} that return the last element in the given array. <strong>NOTE:</strong>
|
||||
* Requires MongoDB 4.4 or later.
|
||||
*
|
||||
* @return new instance of {@link Last}.
|
||||
* @since 3.4
|
||||
@@ -649,6 +654,19 @@ public class ArrayOperators {
|
||||
return new FilterExpressionBuilder().filter(field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link AggregationExpression} resolving to an arry to apply the {@code $filter} to.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return never {@literal null}.
|
||||
* @since 4.2
|
||||
*/
|
||||
public static AsBuilder filter(AggregationExpression expression) {
|
||||
|
||||
Assert.notNull(expression, "Field must not be null");
|
||||
return new FilterExpressionBuilder().filter(expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@literal values} to apply the {@code $filter} to.
|
||||
*
|
||||
@@ -681,7 +699,16 @@ public class ArrayOperators {
|
||||
}
|
||||
|
||||
private Object getMappedInput(AggregationOperationContext context) {
|
||||
return input instanceof Field ? context.getReference((Field) input).toString() : input;
|
||||
|
||||
if (input instanceof Field field) {
|
||||
return context.getReference(field).toString();
|
||||
}
|
||||
|
||||
if (input instanceof AggregationExpression expression) {
|
||||
return expression.toDocument(context);
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
private Object getMappedCondition(AggregationOperationContext context) {
|
||||
@@ -715,6 +742,15 @@ public class ArrayOperators {
|
||||
* @return
|
||||
*/
|
||||
AsBuilder filter(Field field);
|
||||
|
||||
/**
|
||||
* Set the {@link AggregationExpression} resolving to an array to apply the {@code $filter} to.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return
|
||||
* @since 4.0.7
|
||||
*/
|
||||
AsBuilder filter(AggregationExpression expression);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -797,6 +833,14 @@ public class ArrayOperators {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AsBuilder filter(AggregationExpression expression) {
|
||||
|
||||
Assert.notNull(expression, "Expression must not be null");
|
||||
filter.input = expression;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConditionBuilder as(String variableName) {
|
||||
|
||||
@@ -1333,7 +1377,7 @@ public class ArrayOperators {
|
||||
Assert.notNull(expressions, "PropertyExpressions must not be null");
|
||||
|
||||
return new Reduce(Fields.field(fieldReference), initialValue,
|
||||
Arrays.<AggregationExpression>asList(expressions));
|
||||
Arrays.<AggregationExpression> asList(expressions));
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1690,7 +1734,7 @@ public class ArrayOperators {
|
||||
* @author Christoph Strobl
|
||||
* @author Shashank Sharma
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.com/manual/reference/operator/aggregation/in/">https://docs.mongodb.com/manual/reference/operator/aggregation/in/</a>
|
||||
* "https://docs.mongodb.com/manual/reference/operator/aggregation/in/">https://docs.mongodb.com/manual/reference/operator/aggregation/in/</a>
|
||||
* @since 2.2
|
||||
*/
|
||||
public static class In extends AbstractAggregationExpression {
|
||||
@@ -1779,7 +1823,7 @@ public class ArrayOperators {
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.com/manual/reference/operator/aggregation/arrayToObject/">https://docs.mongodb.com/manual/reference/operator/aggregation/arrayToObject/</a>
|
||||
* "https://docs.mongodb.com/manual/reference/operator/aggregation/arrayToObject/">https://docs.mongodb.com/manual/reference/operator/aggregation/arrayToObject/</a>
|
||||
* @since 2.1
|
||||
*/
|
||||
public static class ArrayToObject extends AbstractAggregationExpression {
|
||||
@@ -1976,7 +2020,7 @@ public class ArrayOperators {
|
||||
|
||||
/**
|
||||
* Set the order to put elements in.
|
||||
*
|
||||
*
|
||||
* @param sort must not be {@literal null}.
|
||||
* @return new instance of {@link SortArray}.
|
||||
*/
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.aggregation;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference;
|
||||
|
||||
/**
|
||||
@@ -22,6 +23,7 @@ import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldRefe
|
||||
* {@link AggregationOperationContext}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
* @since 1.9
|
||||
*/
|
||||
class InheritingExposedFieldsAggregationOperationContext extends ExposedFieldsAggregationOperationContext {
|
||||
@@ -43,6 +45,11 @@ class InheritingExposedFieldsAggregationOperationContext extends ExposedFieldsAg
|
||||
this.previousContext = previousContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Document getMappedObject(Document document) {
|
||||
return previousContext.getMappedObject(document);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected FieldReference resolveExposedField(Field field, String name) {
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ import org.springframework.data.convert.PropertyValueConversions;
|
||||
import org.springframework.data.convert.PropertyValueConverter;
|
||||
import org.springframework.data.convert.PropertyValueConverterFactory;
|
||||
import org.springframework.data.convert.PropertyValueConverterRegistrar;
|
||||
import org.springframework.data.convert.ReadingConverter;
|
||||
import org.springframework.data.convert.SimplePropertyValueConversions;
|
||||
import org.springframework.data.convert.WritingConverter;
|
||||
import org.springframework.data.mapping.model.SimpleTypeHolder;
|
||||
@@ -361,6 +362,7 @@ public class MongoCustomConversions extends org.springframework.data.convert.Cus
|
||||
}, this.propertyValueConversions);
|
||||
}
|
||||
|
||||
@ReadingConverter
|
||||
private enum DateToUtcLocalDateTimeConverter implements Converter<Date, LocalDateTime> {
|
||||
INSTANCE;
|
||||
|
||||
@@ -370,6 +372,7 @@ public class MongoCustomConversions extends org.springframework.data.convert.Cus
|
||||
}
|
||||
}
|
||||
|
||||
@ReadingConverter
|
||||
private enum DateToUtcLocalTimeConverter implements Converter<Date, LocalTime> {
|
||||
INSTANCE;
|
||||
|
||||
@@ -379,6 +382,7 @@ public class MongoCustomConversions extends org.springframework.data.convert.Cus
|
||||
}
|
||||
}
|
||||
|
||||
@ReadingConverter
|
||||
private enum DateToUtcLocalDateConverter implements Converter<Date, LocalDate> {
|
||||
INSTANCE;
|
||||
|
||||
|
||||
@@ -1074,7 +1074,7 @@ public class QueryMapper {
|
||||
protected static class MetadataBackedField extends Field {
|
||||
|
||||
private static final Pattern POSITIONAL_PARAMETER_PATTERN = Pattern.compile("\\.\\$(\\[.*?\\])?");
|
||||
private static final Pattern DOT_POSITIONAL_PATTERN = Pattern.compile("\\.\\d+(?!$)");
|
||||
private static final Pattern NUMERIC_SEGMENT = Pattern.compile("\\d+");
|
||||
private static final String INVALID_ASSOCIATION_REFERENCE = "Invalid path reference %s; Associations can only be pointed to directly or via their id property";
|
||||
|
||||
private final MongoPersistentEntity<?> entity;
|
||||
@@ -1216,14 +1216,13 @@ public class QueryMapper {
|
||||
private PersistentPropertyPath<MongoPersistentProperty> getPath(String pathExpression,
|
||||
@Nullable MongoPersistentProperty sourceProperty) {
|
||||
|
||||
String rawPath = removePlaceholders(POSITIONAL_OPERATOR,
|
||||
removePlaceholders(DOT_POSITIONAL_PATTERN, pathExpression));
|
||||
|
||||
if (sourceProperty != null && sourceProperty.getOwner().equals(entity)) {
|
||||
return mappingContext.getPersistentPropertyPath(
|
||||
PropertyPath.from(Pattern.quote(sourceProperty.getName()), entity.getTypeInformation()));
|
||||
}
|
||||
|
||||
String rawPath = resolvePath(pathExpression);
|
||||
|
||||
PropertyPath path = forName(rawPath);
|
||||
if (path == null || isPathToJavaLangClassProperty(path)) {
|
||||
return null;
|
||||
@@ -1318,6 +1317,38 @@ public class QueryMapper {
|
||||
return false;
|
||||
}
|
||||
|
||||
private static String resolvePath(String source) {
|
||||
|
||||
String[] segments = source.split("\\.");
|
||||
if (segments.length == 1) {
|
||||
return source;
|
||||
}
|
||||
|
||||
List<String> path = new ArrayList<>(segments.length);
|
||||
|
||||
/* always start from a property, so we can skip the first segment.
|
||||
from there remove any position placeholder */
|
||||
for(int i=1; i < segments.length; i++) {
|
||||
String segment = segments[i];
|
||||
if (segment.startsWith("[") && segment.endsWith("]")) {
|
||||
continue;
|
||||
}
|
||||
if (NUMERIC_SEGMENT.matcher(segment).matches()) {
|
||||
continue;
|
||||
}
|
||||
path.add(segment);
|
||||
}
|
||||
|
||||
// when property is followed only by placeholders eg. 'values.0.3.90'
|
||||
// or when there is no difference in the number of segments
|
||||
if (path.isEmpty() || segments.length == path.size() + 1) {
|
||||
return source;
|
||||
}
|
||||
|
||||
path.add(0, segments[0]);
|
||||
return StringUtils.collectionToDelimitedString(path, ".");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link Converter} to be used to created the mapped key. Default implementation will use
|
||||
* {@link PropertyToFieldNameConverter}.
|
||||
|
||||
@@ -89,25 +89,22 @@ public enum MongoRegexCreator {
|
||||
|
||||
String regex = prepareAndEscapeStringBeforeApplyingLikeRegex(source, matcherType);
|
||||
|
||||
switch (matcherType) {
|
||||
case STARTING_WITH:
|
||||
return String.format("^%s", regex);
|
||||
case ENDING_WITH:
|
||||
return String.format("%s$", regex);
|
||||
case CONTAINING:
|
||||
return String.format(".*%s.*", regex);
|
||||
case EXACT:
|
||||
return String.format("^%s$", regex);
|
||||
default:
|
||||
return regex;
|
||||
}
|
||||
return switch (matcherType) {
|
||||
case STARTING_WITH -> String.format("^%s", regex);
|
||||
case ENDING_WITH -> String.format("%s$", regex);
|
||||
case CONTAINING -> String.format(".*%s.*", regex);
|
||||
case EXACT -> String.format("^%s$", regex);
|
||||
default -> regex;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param source
|
||||
* @return
|
||||
* @since 2.2.14
|
||||
* @deprecated since 4.0.7
|
||||
*/
|
||||
@Deprecated(since = "4.0.7", forRemoval = true)
|
||||
public Object toCaseInsensitiveMatch(Object source) {
|
||||
return source instanceof String ? new BsonRegularExpression(Pattern.quote((String) source), "i") : source;
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ import org.springframework.data.mongodb.core.schema.TypedJsonSchemaObject.Timest
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* {@link JsonSchemaProperty} implementation.
|
||||
@@ -1139,7 +1140,9 @@ public class IdentifiableJsonSchemaProperty<T extends JsonSchemaObject> implemen
|
||||
enc.append("bsonType", type.toBsonType().value()); // TODO: no samples with type -> is it bson type all the way?
|
||||
}
|
||||
|
||||
enc.append("algorithm", algorithm);
|
||||
if (StringUtils.hasText(algorithm)) {
|
||||
enc.append("algorithm", algorithm);
|
||||
}
|
||||
|
||||
propertySpecification.append("encrypt", enc);
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.bson.BsonRegularExpression;
|
||||
import org.springframework.data.domain.Range;
|
||||
import org.springframework.data.domain.Range.Bound;
|
||||
import org.springframework.data.domain.Sort;
|
||||
@@ -52,6 +52,7 @@ import org.springframework.data.repository.query.parser.Part.IgnoreCaseType;
|
||||
import org.springframework.data.repository.query.parser.Part.Type;
|
||||
import org.springframework.data.repository.query.parser.PartTree;
|
||||
import org.springframework.data.util.Streamable;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
@@ -352,6 +353,7 @@ class MongoQueryCreator extends AbstractQueryCreator<Query, Criteria> {
|
||||
* @param part
|
||||
* @return the regex options or {@literal null}.
|
||||
*/
|
||||
@Nullable
|
||||
private String toRegexOptions(Part part) {
|
||||
|
||||
String regexOptions = null;
|
||||
@@ -390,7 +392,18 @@ class MongoQueryCreator extends AbstractQueryCreator<Query, Criteria> {
|
||||
|
||||
Streamable<?> streamable = asStreamable(iterator.next());
|
||||
if (!isSimpleComparisionPossible(part)) {
|
||||
streamable = streamable.map(MongoRegexCreator.INSTANCE::toCaseInsensitiveMatch);
|
||||
|
||||
MatchMode matchMode = toMatchMode(part.getType());
|
||||
String regexOptions = toRegexOptions(part);
|
||||
|
||||
streamable = streamable.map(it -> {
|
||||
if (it instanceof String value) {
|
||||
|
||||
return new BsonRegularExpression(MongoRegexCreator.INSTANCE.toRegularExpression(value, matchMode),
|
||||
regexOptions);
|
||||
}
|
||||
return it;
|
||||
});
|
||||
}
|
||||
|
||||
return streamable.toList();
|
||||
@@ -482,6 +495,7 @@ class MongoQueryCreator extends AbstractQueryCreator<Query, Criteria> {
|
||||
return MatchMode.REGEX;
|
||||
case NEGATING_SIMPLE_PROPERTY:
|
||||
case SIMPLE_PROPERTY:
|
||||
case IN:
|
||||
return MatchMode.EXACT;
|
||||
default:
|
||||
return MatchMode.DEFAULT;
|
||||
|
||||
@@ -271,6 +271,17 @@ class MappingMongoJsonSchemaCreatorUnitTests {
|
||||
.containsEntry("properties.value", new Document("type", "string"));
|
||||
}
|
||||
|
||||
@Test // GH-4454
|
||||
void wrapEncryptedEntityTypeLikeProperty() {
|
||||
|
||||
MongoJsonSchema schema = MongoJsonSchemaCreator.create() //
|
||||
.filter(MongoJsonSchemaCreator.encryptedOnly()) // filter non encrypted fields
|
||||
.createSchemaFor(WithEncryptedEntityLikeProperty.class);
|
||||
|
||||
assertThat(schema.schemaDocument()) //
|
||||
.containsEntry("properties.domainTypeValue", Document.parse("{'encrypt': {'bsonType': 'object' } }"));
|
||||
}
|
||||
|
||||
// --> TYPES AND JSON
|
||||
|
||||
// --> ENUM
|
||||
@@ -676,4 +687,9 @@ class MappingMongoJsonSchemaCreatorUnitTests {
|
||||
static class PropertyClashWithA {
|
||||
Integer aNonEncrypted;
|
||||
}
|
||||
|
||||
@Encrypted(algorithm = "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic")
|
||||
static class WithEncryptedEntityLikeProperty {
|
||||
@Encrypted SomeDomainType domainTypeValue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,6 @@ import org.bson.types.ObjectId;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.support.GenericApplicationContext;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
@@ -69,6 +68,8 @@ import org.springframework.data.mongodb.InvalidMongoDbApiUsageException;
|
||||
import org.springframework.data.mongodb.MongoDatabaseFactory;
|
||||
import org.springframework.data.mongodb.core.BulkOperations.BulkMode;
|
||||
import org.springframework.data.mongodb.core.convert.LazyLoadingProxy;
|
||||
import org.springframework.data.mongodb.core.convert.MongoCustomConversions;
|
||||
import org.springframework.data.mongodb.core.convert.MongoCustomConversions.MongoConverterConfigurationAdapter;
|
||||
import org.springframework.data.mongodb.core.geo.GeoJsonPoint;
|
||||
import org.springframework.data.mongodb.core.index.Index;
|
||||
import org.springframework.data.mongodb.core.index.IndexField;
|
||||
@@ -1789,6 +1790,30 @@ public class MongoTemplateTests {
|
||||
assertThat(result.get(0).date).isNotNull();
|
||||
}
|
||||
|
||||
@Test // GH-4390
|
||||
void nativeDriverDateTimeCodecShouldBeApplied/*when configured*/() {
|
||||
|
||||
MongoTestTemplate ops = new MongoTestTemplate(cfg -> {
|
||||
cfg.configureConversion(conversion -> {
|
||||
conversion.customConversions(
|
||||
MongoCustomConversions.create(MongoConverterConfigurationAdapter::useNativeDriverJavaTimeCodecs));
|
||||
});
|
||||
});
|
||||
|
||||
TypeWithDate source = new TypeWithDate();
|
||||
source.id = "id-1";
|
||||
source.date = Date.from(Instant.now());
|
||||
|
||||
ops.save(source);
|
||||
|
||||
var dbDate = ops.execute(TypeWithDate.class,
|
||||
collection -> collection.find(new org.bson.Document("_id", source.id)).first().get("date"));
|
||||
|
||||
TypeWithDate target = ops.findOne(query(where("date").is(source.date)), TypeWithDate.class);
|
||||
|
||||
assertThat(target.date).isEqualTo(source.date).isEqualTo(dbDate);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-540
|
||||
public void findOneAfterUpsertForNonExistingObjectReturnsTheInsertedObject() {
|
||||
|
||||
|
||||
@@ -35,8 +35,10 @@ import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.data.mongodb.config.AbstractMongoClientConfiguration;
|
||||
import org.springframework.data.mongodb.core.CollectionOptions.ValidationOptions;
|
||||
import org.springframework.data.mongodb.core.mapping.Encrypted;
|
||||
import org.springframework.data.mongodb.core.mapping.Field;
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
import org.springframework.data.mongodb.core.schema.MongoJsonSchema;
|
||||
import org.springframework.data.mongodb.test.util.Client;
|
||||
import org.springframework.data.mongodb.test.util.MongoClientExtension;
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -48,11 +50,13 @@ import com.mongodb.client.model.ValidationLevel;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link CollectionOptions#validation(ValidationOptions)} using
|
||||
* {@link org.springframework.data.mongodb.core.validation.CriteriaValidator} and
|
||||
* {@link org.springframework.data.mongodb.core.validation.DocumentValidator}.
|
||||
* {@link org.springframework.data.mongodb.core.validation.CriteriaValidator},
|
||||
* {@link org.springframework.data.mongodb.core.validation.DocumentValidator} and
|
||||
* {@link org.springframework.data.mongodb.core.validation.JsonSchemaValidator}.
|
||||
*
|
||||
* @author Andreas Zink
|
||||
* @author Christoph Strobl
|
||||
* @author Julia Lee
|
||||
*/
|
||||
@ExtendWith({ MongoClientExtension.class, SpringExtension.class })
|
||||
public class MongoTemplateValidationTests {
|
||||
@@ -188,6 +192,20 @@ public class MongoTemplateValidationTests {
|
||||
assertThat(getValidatorInfo(COLLECTION_NAME)).isEqualTo(new Document("customName", new Document("$type", "bool")));
|
||||
}
|
||||
|
||||
@Test // GH-4454
|
||||
public void failsJsonSchemaValidationForEncryptedDomainEntityProperty() {
|
||||
|
||||
MongoJsonSchema schema = MongoJsonSchemaCreator.create().createSchemaFor(BeanWithEncryptedDomainEntity.class);
|
||||
template.createCollection(COLLECTION_NAME, CollectionOptions.empty().schema(schema));
|
||||
|
||||
BeanWithEncryptedDomainEntity person = new BeanWithEncryptedDomainEntity();
|
||||
person.encryptedDomainEntity = new SimpleBean("some string", 100, null);
|
||||
|
||||
assertThatExceptionOfType(DataIntegrityViolationException.class)
|
||||
.isThrownBy(() -> template.save(person))
|
||||
.withMessageContaining("Document failed validation");
|
||||
}
|
||||
|
||||
private Document getCollectionOptions(String collectionName) {
|
||||
return getCollectionInfo(collectionName).get("options", Document.class);
|
||||
}
|
||||
@@ -222,4 +240,10 @@ public class MongoTemplateValidationTests {
|
||||
private @Nullable Integer rangedInteger;
|
||||
private @Field("customName") Object customFieldName;
|
||||
}
|
||||
|
||||
@org.springframework.data.mongodb.core.mapping.Document(collection = COLLECTION_NAME)
|
||||
@Encrypted(algorithm = "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic")
|
||||
static class BeanWithEncryptedDomainEntity {
|
||||
@Encrypted SimpleBean encryptedDomainEntity;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* 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.mongodb.core.aggregation;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.assertj.core.api.InstanceOfAssertFactories;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.springframework.data.mongodb.core.aggregation.FieldsExposingAggregationOperation.InheritsFieldsAggregationOperation;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public class AggregationOperationRendererUnitTests {
|
||||
|
||||
@Test // GH-4443
|
||||
void nonFieldsExposingAggregationOperationContinuesWithSameContextForNextStage() {
|
||||
|
||||
AggregationOperationContext rootContext = mock(AggregationOperationContext.class);
|
||||
AggregationOperation stage1 = mock(AggregationOperation.class);
|
||||
AggregationOperation stage2 = mock(AggregationOperation.class);
|
||||
|
||||
AggregationOperationRenderer.toDocument(List.of(stage1, stage2), rootContext);
|
||||
|
||||
verify(stage1).toPipelineStages(eq(rootContext));
|
||||
verify(stage2).toPipelineStages(eq(rootContext));
|
||||
}
|
||||
|
||||
@Test // GH-4443
|
||||
void fieldsExposingAggregationOperationNotExposingFieldsForcesUseOfDefaultContextForNextStage() {
|
||||
|
||||
AggregationOperationContext rootContext = mock(AggregationOperationContext.class);
|
||||
FieldsExposingAggregationOperation stage1 = mock(FieldsExposingAggregationOperation.class);
|
||||
ExposedFields stage1fields = mock(ExposedFields.class);
|
||||
AggregationOperation stage2 = mock(AggregationOperation.class);
|
||||
|
||||
when(stage1.getFields()).thenReturn(stage1fields);
|
||||
when(stage1fields.exposesNoFields()).thenReturn(true);
|
||||
|
||||
AggregationOperationRenderer.toDocument(List.of(stage1, stage2), rootContext);
|
||||
|
||||
verify(stage1).toPipelineStages(eq(rootContext));
|
||||
verify(stage2).toPipelineStages(eq(AggregationOperationRenderer.DEFAULT_CONTEXT));
|
||||
}
|
||||
|
||||
@Test // GH-4443
|
||||
void fieldsExposingAggregationOperationForcesNewContextForNextStage() {
|
||||
|
||||
AggregationOperationContext rootContext = mock(AggregationOperationContext.class);
|
||||
FieldsExposingAggregationOperation stage1 = mock(FieldsExposingAggregationOperation.class);
|
||||
ExposedFields stage1fields = mock(ExposedFields.class);
|
||||
AggregationOperation stage2 = mock(AggregationOperation.class);
|
||||
|
||||
when(stage1.getFields()).thenReturn(stage1fields);
|
||||
when(stage1fields.exposesNoFields()).thenReturn(false);
|
||||
|
||||
ArgumentCaptor<AggregationOperationContext> captor = ArgumentCaptor.forClass(AggregationOperationContext.class);
|
||||
|
||||
AggregationOperationRenderer.toDocument(List.of(stage1, stage2), rootContext);
|
||||
|
||||
verify(stage1).toPipelineStages(eq(rootContext));
|
||||
verify(stage2).toPipelineStages(captor.capture());
|
||||
|
||||
assertThat(captor.getValue()).isInstanceOf(ExposedFieldsAggregationOperationContext.class)
|
||||
.isNotInstanceOf(InheritingExposedFieldsAggregationOperationContext.class);
|
||||
}
|
||||
|
||||
@Test // GH-4443
|
||||
void inheritingFieldsExposingAggregationOperationForcesNewContextForNextStageKeepingReferenceToPreviousContext() {
|
||||
|
||||
AggregationOperationContext rootContext = mock(AggregationOperationContext.class);
|
||||
InheritsFieldsAggregationOperation stage1 = mock(InheritsFieldsAggregationOperation.class);
|
||||
InheritsFieldsAggregationOperation stage2 = mock(InheritsFieldsAggregationOperation.class);
|
||||
InheritsFieldsAggregationOperation stage3 = mock(InheritsFieldsAggregationOperation.class);
|
||||
|
||||
ExposedFields exposedFields = mock(ExposedFields.class);
|
||||
when(exposedFields.exposesNoFields()).thenReturn(false);
|
||||
when(stage1.getFields()).thenReturn(exposedFields);
|
||||
when(stage2.getFields()).thenReturn(exposedFields);
|
||||
when(stage3.getFields()).thenReturn(exposedFields);
|
||||
|
||||
ArgumentCaptor<AggregationOperationContext> captor = ArgumentCaptor.forClass(AggregationOperationContext.class);
|
||||
|
||||
AggregationOperationRenderer.toDocument(List.of(stage1, stage2, stage3), rootContext);
|
||||
|
||||
verify(stage1).toPipelineStages(captor.capture());
|
||||
verify(stage2).toPipelineStages(captor.capture());
|
||||
verify(stage3).toPipelineStages(captor.capture());
|
||||
|
||||
assertThat(captor.getAllValues().get(0)).isEqualTo(rootContext);
|
||||
|
||||
assertThat(captor.getAllValues().get(1))
|
||||
.asInstanceOf(InstanceOfAssertFactories.type(InheritingExposedFieldsAggregationOperationContext.class))
|
||||
.extracting("previousContext").isSameAs(captor.getAllValues().get(0));
|
||||
|
||||
assertThat(captor.getAllValues().get(2))
|
||||
.asInstanceOf(InstanceOfAssertFactories.type(InheritingExposedFieldsAggregationOperationContext.class))
|
||||
.extracting("previousContext").isSameAs(captor.getAllValues().get(1));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -90,6 +90,7 @@ import com.mongodb.client.MongoCollection;
|
||||
* @author Maninder Singh
|
||||
* @author Sergey Shcherbakov
|
||||
* @author Minsu Kim
|
||||
* @author Julia Lee
|
||||
*/
|
||||
@ExtendWith(MongoTemplateExtension.class)
|
||||
public class AggregationTests {
|
||||
@@ -118,7 +119,7 @@ public class AggregationTests {
|
||||
|
||||
mongoTemplate.flush(Product.class, UserWithLikes.class, DATAMONGO753.class, Data.class, DATAMONGO788.class,
|
||||
User.class, Person.class, Reservation.class, Venue.class, MeterData.class, LineItem.class, InventoryItem.class,
|
||||
Sales.class, Sales2.class, Employee.class, Art.class, Venue.class);
|
||||
Sales.class, Sales2.class, Employee.class, Art.class, Venue.class, Item.class);
|
||||
|
||||
mongoTemplate.dropCollection(INPUT_COLLECTION);
|
||||
mongoTemplate.dropCollection("personQueryTemp");
|
||||
@@ -1952,6 +1953,42 @@ public class AggregationTests {
|
||||
assertThat(aggregate.getMappedResults()).contains(widget);
|
||||
}
|
||||
|
||||
@Test // GH-4443
|
||||
void shouldHonorFieldAliasesForFieldReferencesUsingFieldExposingOperation() {
|
||||
|
||||
Item item1 = Item.builder().itemId("1").tags(Arrays.asList("a", "b")).build();
|
||||
Item item2 = Item.builder().itemId("1").tags(Arrays.asList("a", "c")).build();
|
||||
mongoTemplate.insert(Arrays.asList(item1, item2), Item.class);
|
||||
|
||||
TypedAggregation<Item> aggregation = newAggregation(Item.class,
|
||||
match(where("itemId").is("1")),
|
||||
unwind("tags"),
|
||||
match(where("itemId").is("1").and("tags").is("c")));
|
||||
AggregationResults<Document> results = mongoTemplate.aggregate(aggregation, Document.class);
|
||||
List<Document> mappedResults = results.getMappedResults();
|
||||
assertThat(mappedResults).hasSize(1);
|
||||
assertThat(mappedResults.get(0)).containsEntry("item_id", "1");
|
||||
}
|
||||
|
||||
@Test // GH-4443
|
||||
void projectShouldResetContextToAvoidMappingFieldsAgainstANoLongerExistingTarget() {
|
||||
|
||||
Item item1 = Item.builder().itemId("1").tags(Arrays.asList("a", "b")).build();
|
||||
Item item2 = Item.builder().itemId("1").tags(Arrays.asList("a", "c")).build();
|
||||
mongoTemplate.insert(Arrays.asList(item1, item2), Item.class);
|
||||
|
||||
TypedAggregation<Item> aggregation = newAggregation(Item.class,
|
||||
match(where("itemId").is("1")),
|
||||
unwind("tags"),
|
||||
project().and("itemId").as("itemId").and("tags").as("tags"),
|
||||
match(where("itemId").is("1").and("tags").is("c")));
|
||||
|
||||
AggregationResults<Document> results = mongoTemplate.aggregate(aggregation, Document.class);
|
||||
List<Document> mappedResults = results.getMappedResults();
|
||||
assertThat(mappedResults).hasSize(1);
|
||||
assertThat(mappedResults.get(0)).containsEntry("itemId", "1");
|
||||
}
|
||||
|
||||
private void createUsersWithReferencedPersons() {
|
||||
|
||||
mongoTemplate.dropCollection(User.class);
|
||||
@@ -2204,7 +2241,7 @@ public class AggregationTests {
|
||||
List<Item> items;
|
||||
}
|
||||
|
||||
// DATAMONGO-1491
|
||||
// DATAMONGO-1491, GH-4443
|
||||
@lombok.Data
|
||||
@Builder
|
||||
static class Item {
|
||||
@@ -2213,6 +2250,7 @@ public class AggregationTests {
|
||||
String itemId;
|
||||
Integer quantity;
|
||||
Long price;
|
||||
List<String> tags = new ArrayList<>();
|
||||
}
|
||||
|
||||
// DATAMONGO-1538
|
||||
|
||||
@@ -49,6 +49,7 @@ import com.mongodb.client.model.Projections;
|
||||
* @author Thomas Darimont
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @author Julia Lee
|
||||
*/
|
||||
public class AggregationUnitTests {
|
||||
|
||||
@@ -612,7 +613,7 @@ public class AggregationUnitTests {
|
||||
WithRetypedIdField.class, mappingContext,
|
||||
new QueryMapper(new MappingMongoConverter(NoOpDbRefResolver.INSTANCE, mappingContext)));
|
||||
Document document = project(WithRetypedIdField.class).toDocument(context);
|
||||
assertThat(document).isEqualTo(new Document("$project", new Document("_id", 1).append("renamed-field", 1)));
|
||||
assertThat(document).isEqualTo(new Document("$project", new Document("_id", 1).append("renamed-field", 1).append("entries", 1)));
|
||||
}
|
||||
|
||||
@Test // GH-4038
|
||||
@@ -653,6 +654,22 @@ public class AggregationUnitTests {
|
||||
assertThat(documents.get(2)).isEqualTo("{ $sort : { 'serial_number' : -1, 'label_name' : -1 } }");
|
||||
}
|
||||
|
||||
@Test // GH-4443
|
||||
void fieldsExposingContextShouldUseCustomFieldNameFromRelaxedRootContext() {
|
||||
|
||||
MongoMappingContext mappingContext = new MongoMappingContext();
|
||||
RelaxedTypeBasedAggregationOperationContext context = new RelaxedTypeBasedAggregationOperationContext(
|
||||
WithRetypedIdField.class, mappingContext,
|
||||
new QueryMapper(new MappingMongoConverter(NoOpDbRefResolver.INSTANCE, mappingContext)));
|
||||
|
||||
TypedAggregation<WithRetypedIdField> agg = newAggregation(WithRetypedIdField.class,
|
||||
unwind("entries"), match(where("foo").is("value 2")));
|
||||
List<Document> pipeline = agg.toPipeline(context);
|
||||
|
||||
Document fields = getAsDocument(pipeline.get(1), "$match");
|
||||
assertThat(fields.get("renamed-field")).isEqualTo("value 2");
|
||||
}
|
||||
|
||||
private Document extractPipelineElement(Document agg, int index, String operation) {
|
||||
|
||||
List<Document> pipeline = (List<Document>) agg.get("pipeline");
|
||||
@@ -672,5 +689,7 @@ public class AggregationUnitTests {
|
||||
|
||||
@org.springframework.data.mongodb.core.mapping.Field("renamed-field") private String foo;
|
||||
|
||||
private List<String> entries = new ArrayList<>();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,6 +117,23 @@ class FilterExpressionUnitTests {
|
||||
assertThat($filter).isEqualTo(new Document(expected));
|
||||
}
|
||||
|
||||
@Test // GH-4394
|
||||
void filterShouldAcceptExpression() {
|
||||
|
||||
Document $filter = ArrayOperators.arrayOf(ObjectOperators.valueOf("data.metadata").toArray()).filter().as("item")
|
||||
.by(ComparisonOperators.valueOf("item.price").greaterThan("field-1")).toDocument(Aggregation.DEFAULT_CONTEXT);
|
||||
|
||||
Document expected = Document.parse("""
|
||||
{ $filter : {
|
||||
input: { $objectToArray: "$data.metadata" },
|
||||
as: "item",
|
||||
cond: { $gt: [ "$$item.price", "$field-1" ] }
|
||||
}}
|
||||
""");
|
||||
|
||||
assertThat($filter).isEqualTo(expected);
|
||||
}
|
||||
|
||||
private Document extractFilterOperatorFromDocument(Document source) {
|
||||
|
||||
List<Object> pipeline = DocumentTestUtils.getAsDBList(source, "pipeline");
|
||||
|
||||
@@ -64,6 +64,16 @@ class MongoCustomConversionsUnitTests {
|
||||
assertThat(conversions.getPropertyValueConversions().hasValueConverter(persistentProperty)).isTrue();
|
||||
}
|
||||
|
||||
@Test // GH-4390
|
||||
void doesNotReturnConverterForNativeTimeTimeIfUsingDriverCodec() {
|
||||
|
||||
MongoCustomConversions conversions = MongoCustomConversions.create(config -> {
|
||||
config.useNativeDriverJavaTimeCodecs();
|
||||
});
|
||||
|
||||
assertThat(conversions.getCustomWriteTarget(Date.class)).isEmpty();
|
||||
}
|
||||
|
||||
static class DateToZonedDateTimeConverter implements Converter<Date, ZonedDateTime> {
|
||||
|
||||
@Override
|
||||
|
||||
@@ -35,6 +35,8 @@ import org.bson.types.ObjectId;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
@@ -1213,24 +1215,26 @@ class UpdateMapperUnitTests {
|
||||
assertThat(mappedUpdate).isEqualTo(new org.bson.Document("$set", new org.bson.Document("levelOne.a.b.d", "e")));
|
||||
}
|
||||
|
||||
@Test // GH-3775
|
||||
void mapNestedIntegerFieldCorrectly() {
|
||||
@ParameterizedTest // GH-3775, GH-4426
|
||||
@ValueSource(strings = {"levelOne.0.1.3", "levelOne.0.1.32", "levelOne2.0.1.32", "levelOne2.0.1.320"})
|
||||
void mapNestedIntegerFieldCorrectly(String path) {
|
||||
|
||||
Update update = new Update().set("levelOne.0.1.3", "4");
|
||||
Update update = new Update().set(path, "4");
|
||||
Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
|
||||
context.getPersistentEntity(EntityWithNestedMap.class));
|
||||
|
||||
assertThat(mappedUpdate).isEqualTo(new org.bson.Document("$set", new org.bson.Document("levelOne.0.1.3", "4")));
|
||||
assertThat(mappedUpdate).isEqualTo(new org.bson.Document("$set", new org.bson.Document(path, "4")));
|
||||
}
|
||||
|
||||
@Test // GH-3775
|
||||
void mapNestedMixedStringIntegerFieldCorrectly() {
|
||||
@ParameterizedTest // GH-3775, GH-4426
|
||||
@ValueSource(strings = {"levelOne.0.1.c", "levelOne.0.1.c.32", "levelOne2.0.1.32.c", "levelOne2.0.1.c.320"})
|
||||
void mapNestedMixedStringIntegerFieldCorrectly(String path) {
|
||||
|
||||
Update update = new Update().set("levelOne.0.1.c", "4");
|
||||
Update update = new Update().set(path, "4");
|
||||
Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
|
||||
context.getPersistentEntity(EntityWithNestedMap.class));
|
||||
|
||||
assertThat(mappedUpdate).isEqualTo(new org.bson.Document("$set", new org.bson.Document("levelOne.0.1.c", "4")));
|
||||
assertThat(mappedUpdate).isEqualTo(new org.bson.Document("$set", new org.bson.Document(path, "4")));
|
||||
}
|
||||
|
||||
@Test // GH-3775
|
||||
@@ -1720,6 +1724,7 @@ class UpdateMapperUnitTests {
|
||||
|
||||
static class EntityWithNestedMap {
|
||||
Map<String, Map<String, Map<String, Object>>> levelOne;
|
||||
Map<String, Map<String, Map<String, Object>>> levelOne2;
|
||||
}
|
||||
|
||||
static class Customer {
|
||||
|
||||
@@ -1451,9 +1451,16 @@ public abstract class AbstractPersonRepositoryIntegrationTests implements Dirtie
|
||||
assertThat(result.get(0).getId().equals(bart.getId()));
|
||||
}
|
||||
|
||||
@Test // GH-3395
|
||||
@Test // GH-3395, GH-4404
|
||||
void caseInSensitiveInClause() {
|
||||
|
||||
assertThat(repository.findByLastnameIgnoreCaseIn("bEAuFoRd", "maTTheWs")).hasSize(3);
|
||||
|
||||
repository.save(new Person("the-first", "The First"));
|
||||
repository.save(new Person("the-first-one", "The First One"));
|
||||
repository.save(new Person("the-second", "The Second"));
|
||||
|
||||
assertThat(repository.findByLastnameIgnoreCaseIn("tHE fIRsT")).hasSize(1);
|
||||
}
|
||||
|
||||
@Test // GH-3395
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.repository.query;
|
||||
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.springframework.data.mongodb.core.query.Criteria.*;
|
||||
import static org.springframework.data.mongodb.core.query.Query.*;
|
||||
import static org.springframework.data.mongodb.repository.query.StubParameterAccessor.*;
|
||||
@@ -25,6 +24,7 @@ import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.bson.BsonRegularExpression;
|
||||
import org.bson.Document;
|
||||
import org.bson.types.ObjectId;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
@@ -273,6 +273,17 @@ class MongoQueryCreatorUnitTests {
|
||||
assertThat(query).isEqualTo(query(where("firstName").regex("^dave$", "i")));
|
||||
}
|
||||
|
||||
@Test // GH-4404
|
||||
void createsQueryWithFindByInClauseHavingIgnoreCaseCorrectly() {
|
||||
|
||||
PartTree tree = new PartTree("findAllByFirstNameInIgnoreCase", Person.class);
|
||||
MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, List.of("da've", "carter")), context);
|
||||
|
||||
Query query = creator.createQuery();
|
||||
assertThat(query).isEqualTo(query(where("firstName")
|
||||
.in(List.of(new BsonRegularExpression("^\\Qda've\\E$", "i"), new BsonRegularExpression("^carter$", "i")))));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-770
|
||||
void createsQueryWithFindByNotIgnoreCaseCorrectly() {
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ class Person {
|
||||
----
|
||||
Account account = …
|
||||
|
||||
tempate.insert(account); <2>
|
||||
template.insert(account); <2>
|
||||
|
||||
template.update(Person.class)
|
||||
.matching(where("id").is(…))
|
||||
@@ -441,7 +441,7 @@ class Entity {
|
||||
"lastname" : "Long", <2>
|
||||
}
|
||||
----
|
||||
<1> Read/wirte the keys `fn` & `ln` from/to the linkage document based on the lookup query.
|
||||
<1> Read/write the keys `fn` & `ln` from/to the linkage document based on the lookup query.
|
||||
<2> Use non _id_ fields for the lookup of the target documents.
|
||||
====
|
||||
|
||||
@@ -477,7 +477,7 @@ class ToDocumentPointerConverter implements Converter<ReferencedObject, Document
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> Read/wirte the keys `_id` from/to the reference document to use them in the lookup query.
|
||||
<1> Read/write the keys `_id` from/to the reference document to use them in the lookup query.
|
||||
<2> The collection name can be read from the reference document using its key.
|
||||
====
|
||||
|
||||
|
||||
@@ -350,6 +350,14 @@ You can add additional converters to the converter by overriding the `customConv
|
||||
MongoDB's native JSR-310 support can be enabled through `MongoConverterConfigurationAdapter.useNativeDriverJavaTimeCodecs()`.
|
||||
Also shown in the preceding example is a `LoggingEventListener`, which logs `MongoMappingEvent` instances that are posted onto Spring's `ApplicationContextEvent` infrastructure.
|
||||
|
||||
[TIP]
|
||||
====
|
||||
.Java Time Types
|
||||
|
||||
We recommend using MongoDB's native JSR-310 support via `MongoConverterConfigurationAdapter.useNativeDriverJavaTimeCodecs()` as described above as it is using an `UTC` based approach.
|
||||
The default JSR-310 support for `java.time` types inherited from Spring Data Commons uses the local machine timezone as reference and should only be used for backwards compatibility.
|
||||
====
|
||||
|
||||
NOTE: `AbstractMongoClientConfiguration` creates a `MongoTemplate` instance and registers it with the container under the name `mongoTemplate`.
|
||||
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ embedded schema objects that describe properties and subdocuments.
|
||||
<2> `required` is a property that describes which properties are required in a document. It can be specified optionally, along with other
|
||||
schema constraints. See MongoDB's documentation on https://docs.mongodb.com/manual/reference/operator/query/jsonSchema/#available-keywords[available keywords].
|
||||
<3> `properties` is related to a schema object that describes an `object` type. It contains property-specific schema constraints.
|
||||
<4> `firstname` specifies constraints for the `firsname` field inside the document. Here, it is a string-based `properties` element declaring
|
||||
<4> `firstname` specifies constraints for the `firstname` field inside the document. Here, it is a string-based `properties` element declaring
|
||||
possible field values.
|
||||
<5> `address` is a subdocument defining a schema for values in its `postCode` field.
|
||||
====
|
||||
|
||||
@@ -77,7 +77,7 @@ Therefore, the `Sort` properties are mapped against the methods return type `Per
|
||||
<4> `$skip`, `$limit` and `$sort` can be passed on via a `Pageable` argument. Same as in <2>, the operators are appended to the pipeline definition. Methods accepting `Pageable` can return `Slice` for easier pagination.
|
||||
<5> Aggregation methods can return `Stream` to consume results directly from an underlying cursor. Make sure to close the stream after consuming it to release the server-side cursor by either calling `close()` or through `try-with-resources`.
|
||||
<6> Map the result of an aggregation returning a single `Document` to an instance of a desired `SumValue` target type.
|
||||
<7> Aggregations resulting in single document holding just an accumulation result like eg. `$sum` can be extracted directly from the result `Document`.
|
||||
<7> Aggregations resulting in single document holding just an accumulation result like e.g. `$sum` can be extracted directly from the result `Document`.
|
||||
To gain more control, you might consider `AggregationResult` as method return type as shown in <7>.
|
||||
<8> Obtain the raw `AggregationResults` mapped to the generic target wrapper type `SumValue` or `org.bson.Document`.
|
||||
<9> Like in <6>, a single value can be directly obtained from multiple result ``Document``s.
|
||||
|
||||
@@ -57,7 +57,7 @@ To create a Spring project in STS:
|
||||
<repository>
|
||||
<id>spring-milestone</id>
|
||||
<name>Spring Maven MILESTONE Repository</name>
|
||||
<url>https://repo.spring.io/libs-milestone</url>
|
||||
<url>https://repo.spring.io/milestone</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
----
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Spring Data MongoDB 4.0.6 (2022.0.6)
|
||||
Spring Data MongoDB 4.0.9 (2022.0.9)
|
||||
Copyright (c) [2010-2019] Pivotal Software, Inc.
|
||||
|
||||
This product is licensed to you under the Apache License, Version 2.0 (the "License").
|
||||
@@ -44,6 +44,9 @@ conditions of the subcomponent's license, as noted in the LICENSE file.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user