Compare commits

..

18 Commits

Author SHA1 Message Date
Mark Paluch
61bbc9ab7f DATAMONGO-2500 - Release version 2.2.7 (Moore SR7). 2020-04-28 14:35:55 +02:00
Mark Paluch
9e802a59c7 DATAMONGO-2500 - Prepare 2.2.7 (Moore SR7). 2020-04-28 14:35:24 +02:00
Mark Paluch
38e1e632a7 DATAMONGO-2500 - Updated changelog. 2020-04-28 14:35:16 +02:00
Mark Paluch
89cf78cc4a DATAMONGO-2484 - Updated changelog. 2020-04-28 11:59:03 +02:00
Mark Paluch
cecd47d679 DATAMONGO-2529 - Ensure that MappingMongoConverter.read(…) is never called with null.
Previously, various methods attempted to pass a null argument as source for the converter. The API is non-null and implementations relying on these constraints were easily breakable.

We now make sure that the source is never null.
2020-04-23 15:25:45 +02:00
Mark Paluch
ed35e577af DATAMONGO-2504 - Polishing.
Update equals/hashCode implementation to use the Spring Data form. Make fields final where possible. Use diamond syntax. Reorder methods. Reformat code. Extend tests.

Original pull request: #848.
2020-04-23 15:25:45 +02:00
ddebray
f54cf40eda DATAMONGO-2504 - Add hashCode and equals to TextCriteria and Term.
Original pull request: #848.
2020-04-23 12:29:07 +02:00
Christoph Strobl
5314e6f8bb DATAMONGO-2513 - Fix Eq aggregation operator comparing collection values.
Original pull request: #855.
2020-04-22 11:47:02 +02:00
Mark Paluch
b7b2709177 DATAMONGO-2523 - Polishing.
Reformat code.

Original pull request: #859.
2020-04-22 10:08:33 +02:00
Christoph Strobl
34c47e84c0 DATAMONGO-2523 - Fix Json binding of SpEL expressions in arrays.
The closing bracket must not have a leading whitespace.

Original pull request: #859.
2020-04-22 10:08:30 +02:00
Mark Paluch
f7d91184a0 DATAMONGO-2517 - Polishing.
Reformat code.

Original pull request: #857.
2020-04-21 16:02:36 +02:00
Christoph Strobl
eeddc860f7 DATAMONGO-2517 - Fix invalid entity creation for text queries.
Fix a glitch in the MappingMongoConverter that uses the single String argument constructor (since it matches in type and parameter count to the given input string) to falsely instantiate an Entity when it should not.

Original pull request: #857.
2020-04-21 16:02:24 +02:00
Christoph Strobl
bcefdd209b DATAMONGO-2506 - Provide meaningful error message when using unsupported return type in repository aggregation method.
We improved the error message for unsupported return types instead of running into an IllegalArgumentException for unique results.

Original pull request: #851.
2020-04-07 14:59:30 +02:00
Mark Paluch
3f1fea2d19 DATAMONGO-2502 - Polishing.
Extend tests. Fix generics. Consistently use compiled patterns for positional placeholder removal.

Original pull request: #847.
2020-04-07 14:47:42 +02:00
Christoph Strobl
665322a69a DATAMONGO-2502 - Fix nested array path mapping for updates.
Original pull request: #847.
2020-04-07 14:17:05 +02:00
Mark Paluch
3e59bc3b38 DATAMONGO-2492 - Updated changelog. 2020-03-31 15:08:52 +02:00
Mark Paluch
1752931dde DATAMONGO-2485 - After release cleanups. 2020-03-25 10:58:27 +01:00
Mark Paluch
4b9bae1656 DATAMONGO-2485 - Prepare next development iteration. 2020-03-25 10:58:26 +01:00
22 changed files with 360 additions and 59 deletions

View File

@@ -5,7 +5,7 @@
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>2.2.6.RELEASE</version>
<version>2.2.7.RELEASE</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>2.2.6.RELEASE</version>
<version>2.2.7.RELEASE</version>
</parent>
<modules>
@@ -26,7 +26,7 @@
<properties>
<project.type>multi</project.type>
<dist.id>spring-data-mongodb</dist.id>
<springdata.commons>2.2.6.RELEASE</springdata.commons>
<springdata.commons>2.2.7.RELEASE</springdata.commons>
<mongo>3.11.2</mongo>
<mongo.reactivestreams>1.12.0</mongo.reactivestreams>
<jmh.version>1.19</jmh.version>

View File

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

View File

@@ -14,7 +14,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>2.2.6.RELEASE</version>
<version>2.2.7.RELEASE</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@@ -11,7 +11,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>2.2.6.RELEASE</version>
<version>2.2.7.RELEASE</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@@ -3155,12 +3155,13 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
@Nullable
public T doWith(@Nullable Document object) {
T source = null;
if (null != object) {
maybeEmitEvent(new AfterLoadEvent<>(object, type, collectionName));
source = reader.read(type, object);
}
T source = reader.read(type, object);
if (null != source) {
maybeEmitEvent(new AfterConvertEvent<>(object, source, collectionName));
}
@@ -3200,9 +3201,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
Class<?> typeToRead = targetType.isInterface() || targetType.isAssignableFrom(entityType) ? entityType
: targetType;
if (null != object) {
maybeEmitEvent(new AfterLoadEvent<>(object, targetType, collectionName));
}
maybeEmitEvent(new AfterLoadEvent<>(object, targetType, collectionName));
Object source = reader.read(typeToRead, object);
Object result = targetType.isInterface() ? projectionFactory.createProjection(targetType, source) : source;

View File

@@ -17,6 +17,7 @@ package org.springframework.data.mongodb.core.aggregation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
@@ -100,14 +101,14 @@ abstract class AbstractAggregationExpression implements AggregationExpression {
return value;
}
protected List<Object> append(Object value) {
protected List<Object> append(Object value, Expand expandList) {
if (this.value instanceof List) {
List<Object> clone = new ArrayList<Object>((List) this.value);
if (value instanceof List) {
clone.addAll((List) value);
if (value instanceof Collection && Expand.EXPAND_VALUES.equals(expandList)) {
clone.addAll((Collection<?>) value);
} else {
clone.add(value);
}
@@ -117,6 +118,17 @@ abstract class AbstractAggregationExpression implements AggregationExpression {
return Arrays.asList(this.value, value);
}
/**
* Expand a nested list of values to single entries or keep the list.
*/
protected enum Expand {
EXPAND_VALUES, KEEP_SOURCE
}
protected List<Object> append(Object value) {
return append(value, Expand.EXPAND_VALUES);
}
@SuppressWarnings("unchecked")
protected java.util.Map<String, Object> append(String key, Object value) {

View File

@@ -411,7 +411,7 @@ public class ComparisonOperators {
public Cmp compareToValue(Object value) {
Assert.notNull(value, "Value must not be null!");
return new Cmp(append(value));
return new Cmp(append(value, Expand.KEEP_SOURCE));
}
}
@@ -488,7 +488,7 @@ public class ComparisonOperators {
public Eq equalToValue(Object value) {
Assert.notNull(value, "Value must not be null!");
return new Eq(append(value));
return new Eq(append(value, Expand.KEEP_SOURCE));
}
}
@@ -873,7 +873,7 @@ public class ComparisonOperators {
public Ne notEqualToValue(Object value) {
Assert.notNull(value, "Value must not be null!");
return new Ne(append(value));
return new Ne(append(value, Expand.KEEP_SOURCE));
}
}
}

View File

@@ -228,11 +228,9 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
@Nullable
@SuppressWarnings("unchecked")
private <S extends Object> S read(TypeInformation<S> type, @Nullable Bson bson, ObjectPath path) {
private <S extends Object> S read(TypeInformation<S> type, Bson bson, ObjectPath path) {
if (null == bson) {
return null;
}
Assert.notNull(bson, "Bson must not be null!");
TypeInformation<? extends S> typeToUse = typeMapper.readType(bson, type);
Class<? extends S> rawType = typeToUse.getType();
@@ -1263,9 +1261,16 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
}
if (conversions.isSimpleType(obj.getClass())) {
// Doesn't need conversion
return getPotentiallyConvertedSimpleWrite(obj,
typeInformation != null ? typeInformation.getType() : Object.class);
Class<?> conversionTargetType;
if (typeInformation != null && conversions.isSimpleType(typeInformation.getType())) {
conversionTargetType = typeInformation.getType();
} else {
conversionTargetType = Object.class;
}
return getPotentiallyConvertedSimpleWrite(obj, conversionTargetType);
}
if (obj instanceof List) {
@@ -1613,17 +1618,19 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
for (Document document : referencedRawDocuments) {
T target = null;
if (document != null) {
maybeEmitEvent(
new AfterLoadEvent<>(document, (Class<T>) (rawType != null ? rawType : Object.class), collectionName));
target = (T) read(type, document, path);
}
final T target = (T) read(type, document, path);
targeList.add(target);
if (target != null) {
maybeEmitEvent(new AfterConvertEvent<>(document, target, collectionName));
}
targeList.add(target);
}
return targeList;

View File

@@ -419,7 +419,7 @@ public class QueryMapper {
return false;
}
Class<? extends Object> type = value.getClass();
Class<?> type = value.getClass();
MongoPersistentProperty property = documentField.getProperty();
if (property.getActualType().isAssignableFrom(type)) {
@@ -443,7 +443,7 @@ public class QueryMapper {
protected Object convertSimpleOrDocument(Object source, @Nullable MongoPersistentEntity<?> entity) {
if (source instanceof Example) {
return exampleMapper.getMappedExample((Example) source, entity);
return exampleMapper.getMappedExample((Example<?>) source, entity);
}
if (source instanceof List) {
@@ -922,6 +922,8 @@ public class QueryMapper {
*/
protected static class MetadataBackedField extends Field {
private static final Pattern POSITIONAL_PARAMETER_PATTERN = Pattern.compile("\\.\\$(\\[.*?\\])?|\\.\\d+");
private static final Pattern DOT_POSITIONAL_PATTERN = 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;
@@ -963,7 +965,7 @@ public class QueryMapper {
this.entity = entity;
this.mappingContext = context;
this.path = getPath(name);
this.path = getPath(removePlaceholders(POSITIONAL_PARAMETER_PATTERN, name));
this.property = path == null ? property : path.getLeafProperty();
this.association = findAssociation();
}
@@ -1071,7 +1073,7 @@ public class QueryMapper {
}
/**
* Returns the {@link PersistentPropertyPath} for the given <code>pathExpression</code>.
* Returns the {@link PersistentPropertyPath} for the given {@code pathExpression}.
*
* @param pathExpression
* @return
@@ -1079,8 +1081,8 @@ public class QueryMapper {
@Nullable
private PersistentPropertyPath<MongoPersistentProperty> getPath(String pathExpression) {
String rawPath = pathExpression.replaceAll("\\.\\d+", "") //
.replaceAll(POSITIONAL_OPERATOR.pattern(), "");
String rawPath = removePlaceholders(POSITIONAL_OPERATOR,
removePlaceholders(DOT_POSITIONAL_PATTERN, pathExpression));
PropertyPath path = forName(rawPath);
if (path == null || isPathToJavaLangClassProperty(path)) {
@@ -1168,13 +1170,17 @@ public class QueryMapper {
* @since 1.7
*/
protected Converter<MongoPersistentProperty, String> getAssociationConverter() {
return new AssociationConverter(getAssociation());
return new AssociationConverter(name, getAssociation());
}
protected MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> getMappingContext() {
return mappingContext;
}
private static String removePlaceholders(Pattern pattern, String raw) {
return pattern.matcher(raw).replaceAll("");
}
/**
* @author Christoph Strobl
* @since 1.8
@@ -1226,14 +1232,12 @@ public class QueryMapper {
static class KeyMapper {
private final Iterator<String> iterator;
private final MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext;
public KeyMapper(String key,
MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext) {
this.iterator = Arrays.asList(key.split("\\.")).iterator();
this.iterator.next();
this.mappingContext = mappingContext;
}
/**
@@ -1247,21 +1251,10 @@ public class QueryMapper {
StringBuilder mappedName = new StringBuilder(PropertyToFieldNameConverter.INSTANCE.convert(property));
boolean inspect = iterator.hasNext();
int depth = 0;
while (inspect) {
String partial = iterator.next();
if (depth > 0 && property.isCollectionLike() && property.isEntity() && property.getComponentType() != null) {
MongoPersistentEntity<?> persistentEntity = mappingContext
.getRequiredPersistentEntity(property.getComponentType());
MongoPersistentProperty persistentProperty = persistentEntity.getPersistentProperty(partial);
if (persistentProperty != null) {
partial = mapPropertyName(persistentProperty);
}
}
boolean isPositional = (isPositionalParameter(partial) && (property.isMap() || property.isCollectionLike()));
if (isPositional) {
@@ -1269,13 +1262,12 @@ public class QueryMapper {
}
inspect = isPositional && iterator.hasNext();
depth++;
}
return mappedName.toString();
}
private static boolean isPositionalParameter(String partial) {
static boolean isPositionalParameter(String partial) {
if ("$".equals(partial)) {
return true;
@@ -1303,6 +1295,7 @@ public class QueryMapper {
*/
protected static class AssociationConverter implements Converter<MongoPersistentProperty, String> {
private final String name;
private final MongoPersistentProperty property;
private boolean associationFound;
@@ -1311,10 +1304,11 @@ public class QueryMapper {
*
* @param association must not be {@literal null}.
*/
public AssociationConverter(Association<MongoPersistentProperty> association) {
public AssociationConverter(String name, Association<MongoPersistentProperty> association) {
Assert.notNull(association, "Association must not be null!");
this.property = association.getInverse();
this.name = name;
}
/*
@@ -1332,6 +1326,12 @@ public class QueryMapper {
associationFound = true;
}
if (associationFound) {
if (name.endsWith("$") && property.isCollectionLike()) {
return source.getFieldName() + ".$";
}
}
return source.getFieldName();
}
}

View File

@@ -272,6 +272,7 @@ public class UpdateMapper extends QueryMapper {
*
* @author Thomas Darimont
* @author Oliver Gierke
* @author Christoph Strobl
*/
private static class MetadataBackedUpdateField extends MetadataBackedField {
@@ -289,7 +290,7 @@ public class UpdateMapper extends QueryMapper {
public MetadataBackedUpdateField(MongoPersistentEntity<?> entity, String key,
MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext) {
super(key.replaceAll("\\.\\$(\\[.*\\])?", ""), entity, mappingContext);
super(key, entity, mappingContext);
this.key = key;
}
@@ -338,7 +339,7 @@ public class UpdateMapper extends QueryMapper {
MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext,
Association<MongoPersistentProperty> association, String key) {
super(association);
super(key, association);
this.mapper = new KeyMapper(key, mappingContext);
}

View File

@@ -15,7 +15,10 @@
*/
package org.springframework.data.mongodb.core.query;
import static org.springframework.util.ObjectUtils.*;
import org.springframework.lang.Nullable;
import org.springframework.util.ObjectUtils;
/**
* A {@link Term} defines one or multiple words {@link Type#WORD} or phrases {@link Type#PHRASE} to be used in the
@@ -90,6 +93,47 @@ public class Term {
return negated ? negateRaw(formatted) : formatted;
}
/*
* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Term)) {
return false;
}
Term term = (Term) o;
return ObjectUtils.nullSafeEquals(negated, term.negated) && ObjectUtils.nullSafeEquals(type, term.type)
&& ObjectUtils.nullSafeEquals(raw, term.raw);
}
/*
* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
int result = 17;
result += ObjectUtils.nullSafeHashCode(type);
result += ObjectUtils.nullSafeHashCode(raw);
result += ObjectUtils.nullSafeHashCode(negated);
return result;
}
/*
* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return getFormatted();

View File

@@ -21,6 +21,7 @@ import java.util.List;
import org.bson.Document;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
@@ -34,7 +35,7 @@ import org.springframework.util.StringUtils;
public class TextCriteria implements CriteriaDefinition {
private final List<Term> terms;
private @Nullable String language;
private final @Nullable String language;
private @Nullable Boolean caseSensitive;
private @Nullable Boolean diacriticSensitive;
@@ -51,7 +52,7 @@ public class TextCriteria implements CriteriaDefinition {
private TextCriteria(@Nullable String language) {
this.language = language;
this.terms = new ArrayList<Term>();
this.terms = new ArrayList<>();
}
/**
@@ -231,9 +232,47 @@ public class TextCriteria implements CriteriaDefinition {
return new Document("$text", document);
}
/*
* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof TextCriteria)) {
return false;
}
TextCriteria that = (TextCriteria) o;
return ObjectUtils.nullSafeEquals(terms, that.terms) && ObjectUtils.nullSafeEquals(language, that.language)
&& ObjectUtils.nullSafeEquals(caseSensitive, that.caseSensitive)
&& ObjectUtils.nullSafeEquals(diacriticSensitive, that.diacriticSensitive);
}
/*
* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
int result = 17;
result += ObjectUtils.nullSafeHashCode(terms);
result += ObjectUtils.nullSafeHashCode(language);
result += ObjectUtils.nullSafeHashCode(caseSensitive);
result += ObjectUtils.nullSafeHashCode(diacriticSensitive);
return result;
}
private String join(Iterable<Term> terms) {
List<String> result = new ArrayList<String>();
List<String> result = new ArrayList<>();
for (Term term : terms) {
if (term != null) {

View File

@@ -20,6 +20,7 @@ import java.util.stream.Collectors;
import org.bson.Document;
import org.springframework.data.mongodb.InvalidMongoDbApiUsageException;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationOperation;
@@ -71,6 +72,10 @@ public class StringBasedAggregation extends AbstractMongoQuery {
protected Object doExecute(MongoQueryMethod method, ResultProcessor resultProcessor,
ConvertingParameterAccessor accessor, Class<?> typeToRead) {
if (method.isPageQuery() || method.isSliceQuery()) {
throw new InvalidMongoDbApiUsageException(String.format("Repository aggregation method '%s' does not support '%s' return type. Please use eg. 'List' instead.", method.getName(), method.getReturnType().getType().getSimpleName()));
}
Class<?> sourceType = method.getDomainClass();
Class<?> targetType = typeToRead;

View File

@@ -231,7 +231,7 @@ class JsonScanner {
parenthesisCount--;
if (parenthesisCount == 0) {
buffer.read();
c = buffer.read();
break;
}
}

View File

@@ -333,6 +333,52 @@ public class DefaultBulkOperationsUnitTests {
.isEqualTo(new org.bson.Document("element", new Document("$gte", 100)));
}
@Test // DATAMONGO-2502
public void shouldRetainNestedArrayPathWithPlaceholdersForNoMatchingPaths() {
ops.updateOne(new BasicQuery("{}"), new Update().set("items.$.documents.0.fileId", "new-id")).execute();
verify(collection).bulkWrite(captor.capture(), any());
UpdateOneModel<Document> updateModel = (UpdateOneModel<Document>) captor.getValue().get(0);
assertThat(updateModel.getUpdate())
.isEqualTo(new Document("$set", new Document("items.$.documents.0.fileId", "new-id")));
}
@Test // DATAMONGO-2502
public void shouldRetainNestedArrayPathWithPlaceholdersForMappedEntity() {
DefaultBulkOperations ops = new DefaultBulkOperations(template, "collection-1",
new BulkOperationContext(BulkMode.ORDERED, Optional.of(mappingContext.getPersistentEntity(OrderTest.class)),
new QueryMapper(converter), new UpdateMapper(converter), null, null));
ops.updateOne(new BasicQuery("{}"), Update.update("items.$.documents.0.fileId", "file-id")).execute();
verify(collection).bulkWrite(captor.capture(), any());
UpdateOneModel<Document> updateModel = (UpdateOneModel<Document>) captor.getValue().get(0);
assertThat(updateModel.getUpdate())
.isEqualTo(new Document("$set", new Document("items.$.documents.0.the_file_id", "file-id")));
}
static class OrderTest {
String id;
List<OrderTestItem> items;
}
static class OrderTestItem {
private String cartId;
private List<OrderTestDocument> documents;
}
static class OrderTestDocument {
@Field("the_file_id")
private String fileId;
}
class SomeDomainType {
@Id String id;

View File

@@ -1499,6 +1499,15 @@ public class ProjectionOperationUnitTests {
assertThat(agg).isEqualTo(Document.parse("{ $project: { eq250: { $eq: [\"$qty\", 250]} } }"));
}
@Test // DATAMONGO-2513
public void shouldRenderEqAggregationExpressionWithListComparison() {
Document agg = project().and(ComparisonOperators.valueOf("qty").equalToValue(Arrays.asList(250))).as("eq250")
.toDocument(Aggregation.DEFAULT_CONTEXT);
assertThat(agg).isEqualTo(Document.parse("{ $project: { eq250: { $eq: [\"$qty\", [250]]} } }"));
}
@Test // DATAMONGO-1536
public void shouldRenderGtAggregationExpression() {

View File

@@ -55,6 +55,7 @@ import org.springframework.data.mongodb.core.mapping.TextScore;
import org.springframework.data.mongodb.core.query.BasicQuery;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.TextQuery;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
@@ -967,6 +968,42 @@ public class QueryMapperUnitTests {
assertThat(target).isEqualTo(new org.bson.Document("arrayCustomName.$[some_item].nes-ted.$[other_item]", "value"));
}
@Test // DATAMONGO-2502
public void shouldAllowDeeplyNestedPlaceholders() {
org.bson.Document target = mapper.getMappedObject(
query(where("level0.$[some_item].arrayObj.$[other_item].nested").is("value")).getQueryObject(),
context.getPersistentEntity(WithDeepArrayNesting.class));
assertThat(target).isEqualTo(new org.bson.Document("level0.$[some_item].arrayObj.$[other_item].nested", "value"));
}
@Test // DATAMONGO-2502
public void shouldAllowDeeplyNestedPlaceholdersWithCustomName() {
org.bson.Document target = mapper.getMappedObject(
query(where("level0.$[some_item].arrayCustomName.$[other_item].nested").is("value")).getQueryObject(),
context.getPersistentEntity(WithDeepArrayNesting.class));
assertThat(target)
.isEqualTo(new org.bson.Document("level0.$[some_item].arrayCustomName.$[other_item].nes-ted", "value"));
}
@Test // DATAMONGO-2517
public void shouldParseNestedKeywordWithArgumentMatchingTheSourceEntitiesConstructorCorrectly() {
TextQuery source = new TextQuery("test");
org.bson.Document target = mapper.getMappedObject(source.getQueryObject(),
context.getPersistentEntity(WithSingleStringArgConstructor.class));
assertThat(target).isEqualTo(org.bson.Document.parse("{\"$text\" : { \"$search\" : \"test\" }}"));
}
class WithDeepArrayNesting {
List<WithNestedArray> level0;
}
class WithNestedArray {
List<NestedArrayOfObj> arrayObj;
@@ -1133,4 +1170,16 @@ public class QueryMapperUnitTests {
String id;
@Field(targetType = FieldType.OBJECT_ID) String stringAsOid;
}
@Document
static class WithSingleStringArgConstructor {
String value;
public WithSingleStringArgConstructor() {}
public WithSingleStringArgConstructor(String value) {
this.value = value;
}
}
}

View File

@@ -26,6 +26,7 @@ import org.springframework.data.mongodb.core.DocumentTestUtils;
* Unit tests for {@link TextCriteria}.
*
* @author Christoph Strobl
* @author Daniel Debray
*/
public class TextCriteriaUnitTests {
@@ -33,6 +34,7 @@ public class TextCriteriaUnitTests {
public void shouldNotHaveLanguageField() {
TextCriteria criteria = TextCriteria.forDefaultLanguage();
assertThat(criteria.getCriteriaObject()).isEqualTo(searchObject("{ }"));
}
@@ -40,6 +42,7 @@ public class TextCriteriaUnitTests {
public void shouldNotHaveLanguageForNonDefaultLanguageField() {
TextCriteria criteria = TextCriteria.forLanguage("spanish");
assertThat(criteria.getCriteriaObject()).isEqualTo(searchObject("{ \"$language\" : \"spanish\" }"));
}
@@ -47,6 +50,7 @@ public class TextCriteriaUnitTests {
public void shouldCreateSearchFieldForSingleTermCorrectly() {
TextCriteria criteria = TextCriteria.forDefaultLanguage().matching("cake");
assertThat(criteria.getCriteriaObject()).isEqualTo(searchObject("{ \"$search\" : \"cake\" }"));
}
@@ -54,6 +58,7 @@ public class TextCriteriaUnitTests {
public void shouldCreateSearchFieldCorrectlyForMultipleTermsCorrectly() {
TextCriteria criteria = TextCriteria.forDefaultLanguage().matchingAny("bake", "coffee", "cake");
assertThat(criteria.getCriteriaObject()).isEqualTo(searchObject("{ \"$search\" : \"bake coffee cake\" }"));
}
@@ -61,6 +66,7 @@ public class TextCriteriaUnitTests {
public void shouldCreateSearchFieldForPhraseCorrectly() {
TextCriteria criteria = TextCriteria.forDefaultLanguage().matchingPhrase("coffee cake");
assertThat(DocumentTestUtils.getAsDocument(criteria.getCriteriaObject(), "$text"))
.isEqualTo(new Document("$search", "\"coffee cake\""));
}
@@ -69,6 +75,7 @@ public class TextCriteriaUnitTests {
public void shouldCreateNotFieldCorrectly() {
TextCriteria criteria = TextCriteria.forDefaultLanguage().notMatching("cake");
assertThat(criteria.getCriteriaObject()).isEqualTo(searchObject("{ \"$search\" : \"-cake\" }"));
}
@@ -76,6 +83,7 @@ public class TextCriteriaUnitTests {
public void shouldCreateSearchFieldCorrectlyForNotMultipleTermsCorrectly() {
TextCriteria criteria = TextCriteria.forDefaultLanguage().notMatchingAny("bake", "coffee", "cake");
assertThat(criteria.getCriteriaObject()).isEqualTo(searchObject("{ \"$search\" : \"-bake -coffee -cake\" }"));
}
@@ -83,6 +91,7 @@ public class TextCriteriaUnitTests {
public void shouldCreateSearchFieldForNotPhraseCorrectly() {
TextCriteria criteria = TextCriteria.forDefaultLanguage().notMatchingPhrase("coffee cake");
assertThat(DocumentTestUtils.getAsDocument(criteria.getCriteriaObject(), "$text"))
.isEqualTo(new Document("$search", "-\"coffee cake\""));
}
@@ -91,6 +100,7 @@ public class TextCriteriaUnitTests {
public void caseSensitiveOperatorShouldBeSetCorrectly() {
TextCriteria criteria = TextCriteria.forDefaultLanguage().matching("coffee").caseSensitive(true);
assertThat(DocumentTestUtils.getAsDocument(criteria.getCriteriaObject(), "$text"))
.isEqualTo(new Document("$search", "coffee").append("$caseSensitive", true));
}
@@ -99,10 +109,23 @@ public class TextCriteriaUnitTests {
public void diacriticSensitiveOperatorShouldBeSetCorrectly() {
TextCriteria criteria = TextCriteria.forDefaultLanguage().matching("coffee").diacriticSensitive(true);
assertThat(DocumentTestUtils.getAsDocument(criteria.getCriteriaObject(), "$text"))
.isEqualTo(new Document("$search", "coffee").append("$diacriticSensitive", true));
}
@Test // DATAMONGO-2504
public void twoIdenticalCriteriaShouldBeEqual() {
TextCriteria criteriaOne = TextCriteria.forDefaultLanguage().matching("coffee");
TextCriteria criteriaTwo = TextCriteria.forDefaultLanguage().matching("coffee");
assertThat(criteriaOne).isEqualTo(criteriaTwo);
assertThat(criteriaOne).hasSameHashCodeAs(criteriaTwo);
assertThat(criteriaOne).isNotEqualTo(criteriaTwo.diacriticSensitive(false));
assertThat(criteriaOne.hashCode()).isNotEqualTo(criteriaTwo.diacriticSensitive(false).hashCode());
}
private Document searchObject(String json) {
return new Document("$text", Document.parse(json));
}

View File

@@ -34,8 +34,12 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.mongodb.InvalidMongoDbApiUsageException;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.aggregation.AggregationOperationContext;
import org.springframework.data.mongodb.core.aggregation.AggregationOptions;
@@ -198,6 +202,16 @@ public class StringBasedAggregationUnitTests {
assertThat(collationOf(invocation)).isEqualTo(Collation.of("en_US"));
}
@Test // DATAMONGO-2506
public void aggregateRaisesErrorOnInvalidReturnType() {
StringBasedAggregation sba = createAggregationForMethod("invalidPageReturnType", Pageable.class);
assertThatExceptionOfType(InvalidMongoDbApiUsageException.class) //
.isThrownBy(() -> sba.execute(new Object[] { PageRequest.of(0, 1) })) //
.withMessageContaining("invalidPageReturnType") //
.withMessageContaining("Page");
}
private AggregationInvocation executeAggregation(String name, Object... args) {
Class<?>[] argTypes = Arrays.stream(args).map(Object::getClass).toArray(Class[]::new);
@@ -276,6 +290,9 @@ public class StringBasedAggregationUnitTests {
@Aggregation(pipeline = RAW_GROUP_BY_LASTNAME_STRING, collation = "de_AT")
PersonAggregate aggregateWithCollation(Collation collation);
@Aggregation(RAW_GROUP_BY_LASTNAME_STRING)
Page<Person> invalidPageReturnType(Pageable page);
}
static class PersonAggregate {

View File

@@ -231,7 +231,7 @@ public class ParameterBindingJsonReaderUnitTests {
public void bindMultipleUnquotedParameterInArray() {
Document target = parse("{ 'name' : { $in : [?0,?1] } }", "dalinar", "kohlin");
assertThat(target).isEqualTo(new Document("name", new Document("$in",Arrays.asList("dalinar", "kohlin"))));
assertThat(target).isEqualTo(new Document("name", new Document("$in", Arrays.asList("dalinar", "kohlin"))));
}
@Test // DATAMONGO-2476
@@ -252,7 +252,16 @@ public class ParameterBindingJsonReaderUnitTests {
public void bindQuotedMulitParameterInArray() {
Document target = parse("{ 'name' : { $in : ['?0,?1'] } }", "dalinar", "kohlin");
assertThat(target).isEqualTo(new Document("name", new Document("$in", Collections.singletonList("dalinar,kohlin"))));
assertThat(target)
.isEqualTo(new Document("name", new Document("$in", Collections.singletonList("dalinar,kohlin"))));
}
@Test // DATAMONGO-2523
public void bindSpelExpressionInArrayCorrectly/* closing bracket must not have leading whitespace! */() {
Document target = parse("{ $and : [?#{ [0] == null ? { '$where' : 'true' } : { 'v1' : { '$in' : {[0]} } } }]}", 1);
assertThat(target).isEqualTo(Document.parse("{\"$and\": [{\"v1\": {\"$in\": [1]}}]}"));
}
private static Document parse(String json, Object... args) {

View File

@@ -1,6 +1,43 @@
Spring Data MongoDB Changelog
=============================
Changes in version 2.2.7.RELEASE (2020-04-28)
---------------------------------------------
* DATAMONGO-2529 - EntityReader called with null argument.
* DATAMONGO-2523 - ParameterBindingJsonReader skips too many chars.
* DATAMONGO-2517 - Text search fails on entity with second constructor.
* DATAMONGO-2513 - ComparisonOperators.Eq#equalToValue doesn't work well with Lists.
* DATAMONGO-2506 - StringBasedAggregation should raise meaningful error when unsupported return type requested.
* DATAMONGO-2504 - TextCriteria Hashcode and equals.
* DATAMONGO-2502 - Regression in Update property mapping.
* DATAMONGO-2500 - Release 2.2.7 (Moore SR7).
Changes in version 2.1.17.RELEASE (2020-04-28)
----------------------------------------------
* DATAMONGO-2529 - EntityReader called with null argument.
* DATAMONGO-2497 - Update documentation regarding @Transient properties usage in the persistence constructor.
* DATAMONGO-2484 - Release 2.1.17 (Lovelace SR17).
* DATAMONGO-2300 - Can't read and convert DBRef when the type is Map.
Changes in version 3.0.0.RC1 (2020-03-31)
-----------------------------------------
* DATAMONGO-2501 - Upgrade to Querydsl 4.3.
* DATAMONGO-2498 - Upgrade to MongoDB 4.0.1 Drivers.
* DATAMONGO-2497 - Update documentation regarding @Transient properties usage in the persistence constructor.
* DATAMONGO-2492 - Release 3.0 RC1 (Neumann).
* DATAMONGO-2488 - KeyMapper.mapPropertyName does not work for nested arrays.
* DATAMONGO-2479 - More EntityCallback specializations.
* DATAMONGO-2477 - Disable auto-index creation by default.
* DATAMONGO-2475 - QueryDSL: $and and $or with more than two arguments in final query document.
* DATAMONGO-2416 - Add default methods accepting CriteriaDefinition on Fluent API.
* DATAMONGO-2300 - Can't read and convert DBRef when the type is Map.
* DATAMONGO-1026 - Joda, JSR-310 and ThreeTenBp converters are timezone-sensitive.
* DATAMONGO-931 - Add support for $redact operation.
* DATAMONGO-625 - GridFsOperations.createFile method should allow specification of _id.
Changes in version 2.2.6.RELEASE (2020-03-25)
---------------------------------------------
* DATAMONGO-2497 - Update documentation regarding @Transient properties usage in the persistence constructor.
@@ -2960,3 +2997,6 @@ Repository

View File

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