Compare commits
23 Commits
1.10.6.REL
...
1.10.8.REL
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
946617f634 | ||
|
|
50bb03004f | ||
|
|
2f68d8c85d | ||
|
|
ebfbc4e9d0 | ||
|
|
b96707a0e2 | ||
|
|
7523eedd8d | ||
|
|
ae8df6b705 | ||
|
|
72a0a5623a | ||
|
|
5f8f858d89 | ||
|
|
8db4feeef0 | ||
|
|
b9a392168d | ||
|
|
479dc3a0d6 | ||
|
|
59ebbd3d35 | ||
|
|
38556f522f | ||
|
|
166304849a | ||
|
|
f71b38b731 | ||
|
|
c4af78d81d | ||
|
|
a281ec83b5 | ||
|
|
407087b3a7 | ||
|
|
90411decce | ||
|
|
71135395c1 | ||
|
|
9c43ece3a7 | ||
|
|
283bfce2fe |
6
pom.xml
6
pom.xml
@@ -5,7 +5,7 @@
|
||||
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||
<version>1.10.6.RELEASE</version>
|
||||
<version>1.10.8.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>1.9.6.RELEASE</version>
|
||||
<version>1.9.8.RELEASE</version>
|
||||
</parent>
|
||||
|
||||
<modules>
|
||||
@@ -28,7 +28,7 @@
|
||||
<properties>
|
||||
<project.type>multi</project.type>
|
||||
<dist.id>spring-data-mongodb</dist.id>
|
||||
<springdata.commons>1.13.6.RELEASE</springdata.commons>
|
||||
<springdata.commons>1.13.8.RELEASE</springdata.commons>
|
||||
<mongo>2.14.3</mongo>
|
||||
<mongo.osgi>2.13.0</mongo.osgi>
|
||||
<jmh.version>1.19</jmh.version>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||
<version>1.10.6.RELEASE</version>
|
||||
<version>1.10.8.RELEASE</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||
<version>1.10.6.RELEASE</version>
|
||||
<version>1.10.8.RELEASE</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb</artifactId>
|
||||
<version>1.10.6.RELEASE</version>
|
||||
<version>1.10.8.RELEASE</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||
<version>1.10.6.RELEASE</version>
|
||||
<version>1.10.8.RELEASE</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||
<version>1.10.6.RELEASE</version>
|
||||
<version>1.10.8.RELEASE</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb-parent</artifactId>
|
||||
<version>1.10.6.RELEASE</version>
|
||||
<version>1.10.8.RELEASE</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
||||
@@ -19,12 +19,10 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField;
|
||||
import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.DBObject;
|
||||
@@ -40,6 +38,7 @@ import com.mongodb.DBObject;
|
||||
* @author Oliver Gierke
|
||||
* @author Gustavo de Geus
|
||||
* @author Christoph Strobl
|
||||
* @author Sergey Shcherbakov
|
||||
* @since 1.3
|
||||
* @see <a href="https://docs.mongodb.org/manual/reference/aggregation/group/">MongoDB Aggregation Framework: $group</a>
|
||||
*/
|
||||
@@ -157,6 +156,21 @@ public class GroupOperation implements FieldsExposingAggregationOperation {
|
||||
return sum(reference, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an {@link GroupOperationBuilder} for an {@code $sum}-expression for the given
|
||||
* {@link AggregationExpression}.
|
||||
*
|
||||
* @param expr must not be {@literal null}.
|
||||
* @return new instance of {@link GroupOperationBuilder}. Never {@literal null}.
|
||||
* @throws IllegalArgumentException when {@code expr} is {@literal null}.
|
||||
* @since 1.10.8
|
||||
*/
|
||||
public GroupOperationBuilder sum(AggregationExpression expr) {
|
||||
|
||||
Assert.notNull(expr, "Expr must not be null!");
|
||||
return newBuilder(GroupOps.SUM, null, expr);
|
||||
}
|
||||
|
||||
private GroupOperationBuilder sum(String reference, Object value) {
|
||||
return newBuilder(GroupOps.SUM, reference, value);
|
||||
}
|
||||
@@ -196,7 +210,8 @@ public class GroupOperation implements FieldsExposingAggregationOperation {
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an {@link GroupOperationBuilder} for an {@code $last}-expression for the given {@link AggregationExpression}.
|
||||
* Generates an {@link GroupOperationBuilder} for an {@code $last}-expression for the given
|
||||
* {@link AggregationExpression}.
|
||||
*
|
||||
* @param expr
|
||||
* @return
|
||||
@@ -216,7 +231,8 @@ public class GroupOperation implements FieldsExposingAggregationOperation {
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an {@link GroupOperationBuilder} for a {@code $first}-expression for the given {@link AggregationExpression}.
|
||||
* Generates an {@link GroupOperationBuilder} for a {@code $first}-expression for the given
|
||||
* {@link AggregationExpression}.
|
||||
*
|
||||
* @param expr
|
||||
* @return
|
||||
@@ -236,7 +252,8 @@ public class GroupOperation implements FieldsExposingAggregationOperation {
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an {@link GroupOperationBuilder} for an {@code $avg}-expression for the given {@link AggregationExpression}.
|
||||
* Generates an {@link GroupOperationBuilder} for an {@code $avg}-expression for the given
|
||||
* {@link AggregationExpression}.
|
||||
*
|
||||
* @param expr
|
||||
* @return
|
||||
@@ -280,7 +297,8 @@ public class GroupOperation implements FieldsExposingAggregationOperation {
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an {@link GroupOperationBuilder} for an {@code $min}-expression that for the given {@link AggregationExpression}.
|
||||
* Generates an {@link GroupOperationBuilder} for an {@code $min}-expression that for the given
|
||||
* {@link AggregationExpression}.
|
||||
*
|
||||
* @param expr
|
||||
* @return
|
||||
@@ -300,7 +318,8 @@ public class GroupOperation implements FieldsExposingAggregationOperation {
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an {@link GroupOperationBuilder} for an {@code $max}-expression that for the given {@link AggregationExpression}.
|
||||
* Generates an {@link GroupOperationBuilder} for an {@code $max}-expression that for the given
|
||||
* {@link AggregationExpression}.
|
||||
*
|
||||
* @param expr
|
||||
* @return
|
||||
@@ -322,7 +341,8 @@ public class GroupOperation implements FieldsExposingAggregationOperation {
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an {@link GroupOperationBuilder} for an {@code $stdDevSamp}-expression that for the given {@link AggregationExpression}.
|
||||
* Generates an {@link GroupOperationBuilder} for an {@code $stdDevSamp}-expression that for the given
|
||||
* {@link AggregationExpression}.
|
||||
*
|
||||
* @param expr must not be {@literal null}.
|
||||
* @return never {@literal null}.
|
||||
@@ -344,7 +364,8 @@ public class GroupOperation implements FieldsExposingAggregationOperation {
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an {@link GroupOperationBuilder} for an {@code $stdDevPop}-expression that for the given {@link AggregationExpression}.
|
||||
* Generates an {@link GroupOperationBuilder} for an {@code $stdDevPop}-expression that for the given
|
||||
* {@link AggregationExpression}.
|
||||
*
|
||||
* @param expr must not be {@literal null}.
|
||||
* @return never {@literal null}.
|
||||
@@ -418,7 +439,8 @@ public class GroupOperation implements FieldsExposingAggregationOperation {
|
||||
|
||||
private static enum GroupOps implements Keyword {
|
||||
|
||||
SUM("$sum"), LAST("$last"), FIRST("$first"), PUSH("$push"), AVG("$avg"), MIN("$min"), MAX("$max"), ADD_TO_SET("$addToSet"), STD_DEV_POP("$stdDevPop"), STD_DEV_SAMP("$stdDevSamp");
|
||||
SUM("$sum"), LAST("$last"), FIRST("$first"), PUSH("$push"), AVG("$avg"), MIN("$min"), MAX("$max"), ADD_TO_SET(
|
||||
"$addToSet"), STD_DEV_POP("$stdDevPop"), STD_DEV_SAMP("$stdDevSamp");
|
||||
|
||||
private String mongoOperator;
|
||||
|
||||
@@ -426,7 +448,6 @@ public class GroupOperation implements FieldsExposingAggregationOperation {
|
||||
this.mongoOperator = mongoOperator;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return mongoOperator;
|
||||
|
||||
@@ -1408,7 +1408,7 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation {
|
||||
protected List<Object> getOperationArguments(AggregationOperationContext context) {
|
||||
|
||||
List<Object> result = new ArrayList<Object>(values.size());
|
||||
result.add(context.getReference(getField().getName()).toString());
|
||||
result.add(context.getReference(getField()).toString());
|
||||
|
||||
for (Object element : values) {
|
||||
|
||||
|
||||
@@ -25,7 +25,9 @@ import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
@@ -46,7 +48,6 @@ import org.springframework.util.Assert;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.BasicDBObjectBuilder;
|
||||
import com.mongodb.DB;
|
||||
import com.mongodb.DBObject;
|
||||
import com.mongodb.DBRef;
|
||||
@@ -62,6 +63,8 @@ import com.mongodb.DBRef;
|
||||
*/
|
||||
public class DefaultDbRefResolver implements DbRefResolver {
|
||||
|
||||
private static final String ID = "_id";
|
||||
|
||||
private final MongoDbFactory mongoDbFactory;
|
||||
private final PersistenceExceptionTranslator exceptionTranslator;
|
||||
private final ObjenesisStd objenesis;
|
||||
@@ -144,10 +147,37 @@ public class DefaultDbRefResolver implements DbRefResolver {
|
||||
ids.add(ref.getId());
|
||||
}
|
||||
|
||||
Map<Object, DBObject> documentsById = getDocumentsById(ids, collection);
|
||||
List<DBObject> result = new ArrayList<DBObject>(ids.size());
|
||||
|
||||
for (Object id : ids) {
|
||||
result.add(documentsById.get(id));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all documents with the given ids contained in the given collection mapped by their ids.
|
||||
*
|
||||
* @param ids must not be {@literal null}.
|
||||
* @param collection must not be {@literal null} or empty.
|
||||
* @return
|
||||
*/
|
||||
private Map<Object, DBObject> getDocumentsById(List<Object> ids, String collection) {
|
||||
|
||||
Assert.notNull(ids, "Ids must not be null!");
|
||||
Assert.hasText(collection, "Collection must not be null or empty!");
|
||||
|
||||
DB db = mongoDbFactory.getDb();
|
||||
List<DBObject> result = db.getCollection(collection)
|
||||
.find(new BasicDBObjectBuilder().add("_id", new BasicDBObject("$in", ids)).get()).toArray();
|
||||
Collections.sort(result, new DbRefByReferencePositionComparator(ids));
|
||||
BasicDBObject query = new BasicDBObject(ID, new BasicDBObject("$in", ids));
|
||||
List<DBObject> documents = db.getCollection(collection).find(query).toArray();
|
||||
Map<Object, DBObject> result = new HashMap<Object, DBObject>(documents.size());
|
||||
|
||||
for (DBObject document : documents) {
|
||||
result.put(document.get(ID), document);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -469,7 +499,7 @@ public class DefaultDbRefResolver implements DbRefResolver {
|
||||
*/
|
||||
@Override
|
||||
public int compare(DBObject o1, DBObject o2) {
|
||||
return Integer.compare(reference.indexOf(o1.get("_id")), reference.indexOf(o2.get("_id")));
|
||||
return Integer.compare(reference.indexOf(o1.get(ID)), reference.indexOf(o2.get(ID)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import java.util.Stack;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.data.domain.Example;
|
||||
import org.springframework.data.domain.ExampleMatcher;
|
||||
import org.springframework.data.domain.ExampleMatcher.NullHandler;
|
||||
import org.springframework.data.domain.ExampleMatcher.PropertyValueTransformer;
|
||||
import org.springframework.data.domain.ExampleMatcher.StringMatcher;
|
||||
@@ -41,6 +42,7 @@ import org.springframework.data.repository.core.support.ExampleMatcherAccessor;
|
||||
import org.springframework.data.repository.query.parser.Part.Type;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@@ -48,9 +50,13 @@ import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.DBObject;
|
||||
|
||||
/**
|
||||
* Mapper from {@link Example} to a query {@link DBObject}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @since 1.8
|
||||
* @see Example
|
||||
* @see org.springframework.data.domain.ExampleMatcher
|
||||
*/
|
||||
public class MongoExampleMapper {
|
||||
|
||||
@@ -58,6 +64,11 @@ public class MongoExampleMapper {
|
||||
private final MongoConverter converter;
|
||||
private final Map<StringMatcher, Type> stringMatcherPartMapping = new HashMap<StringMatcher, Type>();
|
||||
|
||||
/**
|
||||
* Create a new {@link MongoTypeMapper} given {@link MongoConverter}.
|
||||
*
|
||||
* @param converter must not be {@literal null}.
|
||||
*/
|
||||
public MongoExampleMapper(MongoConverter converter) {
|
||||
|
||||
this.converter = converter;
|
||||
@@ -99,8 +110,10 @@ public class MongoExampleMapper {
|
||||
|
||||
DBObject reference = (DBObject) converter.convertToMongoType(example.getProbe());
|
||||
|
||||
if (entity.hasIdProperty() && entity.getIdentifierAccessor(example.getProbe()).getIdentifier() == null) {
|
||||
reference.removeField(entity.getIdProperty().getFieldName());
|
||||
if (entity.hasIdProperty() && ClassUtils.isAssignable(entity.getType(), example.getProbeType())) {
|
||||
if (entity.getIdentifierAccessor(example.getProbe()).getIdentifier() == null) {
|
||||
reference.removeField(entity.getIdProperty().getFieldName());
|
||||
}
|
||||
}
|
||||
|
||||
ExampleMatcherAccessor matcherAccessor = new ExampleMatcherAccessor(example.getMatcher());
|
||||
@@ -111,80 +124,7 @@ public class MongoExampleMapper {
|
||||
: new BasicDBObject(SerializationUtils.flattenMap(reference));
|
||||
DBObject result = example.getMatcher().isAllMatching() ? flattened : orConcatenate(flattened);
|
||||
|
||||
this.converter.getTypeMapper().writeTypeRestrictions(result, getTypesToMatch(example));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static DBObject orConcatenate(DBObject source) {
|
||||
|
||||
List<DBObject> foo = new ArrayList<DBObject>(source.keySet().size());
|
||||
|
||||
for (String key : source.keySet()) {
|
||||
foo.add(new BasicDBObject(key, source.get(key)));
|
||||
}
|
||||
|
||||
return new BasicDBObject("$or", foo);
|
||||
}
|
||||
|
||||
private Set<Class<?>> getTypesToMatch(Example<?> example) {
|
||||
|
||||
Set<Class<?>> types = new HashSet<Class<?>>();
|
||||
|
||||
for (TypeInformation<?> reference : mappingContext.getManagedTypes()) {
|
||||
if (example.getProbeType().isAssignableFrom(reference.getType())) {
|
||||
types.add(reference.getType());
|
||||
}
|
||||
}
|
||||
|
||||
return types;
|
||||
}
|
||||
|
||||
private String getMappedPropertyPath(String path, Class<?> probeType) {
|
||||
|
||||
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(probeType);
|
||||
|
||||
Iterator<String> parts = Arrays.asList(path.split("\\.")).iterator();
|
||||
|
||||
final Stack<MongoPersistentProperty> stack = new Stack<MongoPersistentProperty>();
|
||||
|
||||
List<String> resultParts = new ArrayList<String>();
|
||||
|
||||
while (parts.hasNext()) {
|
||||
|
||||
final String part = parts.next();
|
||||
MongoPersistentProperty prop = entity.getPersistentProperty(part);
|
||||
|
||||
if (prop == null) {
|
||||
|
||||
entity.doWithProperties(new PropertyHandler<MongoPersistentProperty>() {
|
||||
|
||||
@Override
|
||||
public void doWithPersistentProperty(MongoPersistentProperty property) {
|
||||
|
||||
if (property.getFieldName().equals(part)) {
|
||||
stack.push(property);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (stack.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
prop = stack.pop();
|
||||
}
|
||||
|
||||
resultParts.add(prop.getName());
|
||||
|
||||
if (prop.isEntity() && mappingContext.hasPersistentEntityFor(prop.getActualType())) {
|
||||
entity = mappingContext.getPersistentEntity(prop.getActualType());
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return StringUtils.collectionToDelimitedString(resultParts, ".");
|
||||
|
||||
return updateTypeRestrictions(result, example);
|
||||
}
|
||||
|
||||
private void applyPropertySpecs(String path, DBObject source, Class<?> probeType,
|
||||
@@ -246,7 +186,102 @@ public class MongoExampleMapper {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isEmptyIdProperty(Entry<String, Object> entry) {
|
||||
private String getMappedPropertyPath(String path, Class<?> probeType) {
|
||||
|
||||
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(probeType);
|
||||
|
||||
Iterator<String> parts = Arrays.asList(path.split("\\.")).iterator();
|
||||
|
||||
final Stack<MongoPersistentProperty> stack = new Stack<MongoPersistentProperty>();
|
||||
|
||||
List<String> resultParts = new ArrayList<String>();
|
||||
|
||||
while (parts.hasNext()) {
|
||||
|
||||
final String part = parts.next();
|
||||
MongoPersistentProperty prop = entity.getPersistentProperty(part);
|
||||
|
||||
if (prop == null) {
|
||||
|
||||
entity.doWithProperties(new PropertyHandler<MongoPersistentProperty>() {
|
||||
|
||||
@Override
|
||||
public void doWithPersistentProperty(MongoPersistentProperty property) {
|
||||
|
||||
if (property.getFieldName().equals(part)) {
|
||||
stack.push(property);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (stack.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
prop = stack.pop();
|
||||
}
|
||||
|
||||
resultParts.add(prop.getName());
|
||||
|
||||
if (prop.isEntity() && mappingContext.hasPersistentEntityFor(prop.getActualType())) {
|
||||
entity = mappingContext.getPersistentEntity(prop.getActualType());
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return StringUtils.collectionToDelimitedString(resultParts, ".");
|
||||
}
|
||||
|
||||
private DBObject updateTypeRestrictions(DBObject query, Example example) {
|
||||
|
||||
DBObject result = new BasicDBObject();
|
||||
|
||||
if (isTypeRestricting(example.getMatcher())) {
|
||||
|
||||
result.putAll(query);
|
||||
this.converter.getTypeMapper().writeTypeRestrictions(result, getTypesToMatch(example));
|
||||
return result;
|
||||
}
|
||||
|
||||
for (String key : query.keySet()) {
|
||||
if (!this.converter.getTypeMapper().isTypeKey(key)) {
|
||||
result.put(key, query.get(key));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean isTypeRestricting(ExampleMatcher matcher) {
|
||||
|
||||
if (matcher.getIgnoredPaths().isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (String path : matcher.getIgnoredPaths()) {
|
||||
if (this.converter.getTypeMapper().isTypeKey(path)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private Set<Class<?>> getTypesToMatch(Example<?> example) {
|
||||
|
||||
Set<Class<?>> types = new HashSet<Class<?>>();
|
||||
|
||||
for (TypeInformation<?> reference : mappingContext.getManagedTypes()) {
|
||||
if (example.getProbeType().isAssignableFrom(reference.getType())) {
|
||||
types.add(reference.getType());
|
||||
}
|
||||
}
|
||||
|
||||
return types;
|
||||
}
|
||||
|
||||
private static boolean isEmptyIdProperty(Entry<String, Object> entry) {
|
||||
return entry.getKey().equals("_id") && entry.getValue() == null;
|
||||
}
|
||||
|
||||
@@ -272,4 +307,15 @@ public class MongoExampleMapper {
|
||||
dbo.put("$options", "i");
|
||||
}
|
||||
}
|
||||
|
||||
private static DBObject orConcatenate(DBObject source) {
|
||||
|
||||
List<DBObject> or = new ArrayList<DBObject>(source.keySet().size());
|
||||
|
||||
for (String key : source.keySet()) {
|
||||
or.add(new BasicDBObject(key, source.get(key)));
|
||||
}
|
||||
|
||||
return new BasicDBObject("$or", or);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2016 the original author or authors.
|
||||
* Copyright 2013-2017 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.
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.convert;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
@@ -161,6 +162,10 @@ public class UpdateMapper extends QueryMapper {
|
||||
return info;
|
||||
}
|
||||
|
||||
if (source instanceof Collection) {
|
||||
return NESTED_DOCUMENT;
|
||||
}
|
||||
|
||||
if (!type.equals(source.getClass())) {
|
||||
return info;
|
||||
}
|
||||
|
||||
@@ -15,12 +15,17 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.index;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
@@ -29,9 +34,11 @@ import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.mapping.Association;
|
||||
import org.springframework.data.mapping.AssociationHandler;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.mapping.PropertyHandler;
|
||||
import org.springframework.data.mapping.model.MappingException;
|
||||
import org.springframework.data.mongodb.core.index.Index.Duplicates;
|
||||
import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexResolver.CycleGuard.Path;
|
||||
import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexResolver.TextIndexIncludeOptions.IncludeStrategy;
|
||||
import org.springframework.data.mongodb.core.index.TextIndexDefinition.TextIndexDefinitionBuilder;
|
||||
import org.springframework.data.mongodb.core.index.TextIndexDefinition.TextIndexedFieldSpec;
|
||||
@@ -41,6 +48,7 @@ import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.mongodb.BasicDBObject;
|
||||
@@ -57,6 +65,7 @@ import com.mongodb.util.JSON;
|
||||
* @author Christoph Strobl
|
||||
* @author Thomas Darimont
|
||||
* @author Martin Macko
|
||||
* @author Mark Paluch
|
||||
* @since 1.5
|
||||
*/
|
||||
public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
||||
@@ -99,7 +108,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
||||
Document document = root.findAnnotation(Document.class);
|
||||
Assert.notNull(document, "Given entity is not collection root.");
|
||||
|
||||
final List<IndexDefinitionHolder> indexInformation = new ArrayList<MongoPersistentEntityIndexResolver.IndexDefinitionHolder>();
|
||||
final List<IndexDefinitionHolder> indexInformation = new ArrayList<IndexDefinitionHolder>();
|
||||
indexInformation.addAll(potentiallyCreateCompoundIndexDefinitions("", root.getCollection(), root));
|
||||
indexInformation.addAll(potentiallyCreateTextIndexDefinition(root));
|
||||
|
||||
@@ -113,7 +122,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
||||
try {
|
||||
if (persistentProperty.isEntity()) {
|
||||
indexInformation.addAll(resolveIndexForClass(persistentProperty.getTypeInformation().getActualType(),
|
||||
persistentProperty.getFieldName(), root.getCollection(), guard));
|
||||
persistentProperty.getFieldName(), Path.of(persistentProperty), root.getCollection(), guard));
|
||||
}
|
||||
|
||||
IndexDefinitionHolder indexDefinitionHolder = createIndexDefinitionHolderForProperty(
|
||||
@@ -136,31 +145,35 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
||||
* Recursively resolve and inspect properties of given {@literal type} for indexes to be created.
|
||||
*
|
||||
* @param type
|
||||
* @param path The {@literal "dot} path.
|
||||
* @param dotPath The {@literal "dot} path.
|
||||
* @param path {@link PersistentProperty} path for cycle detection.
|
||||
* @param collection
|
||||
* @param guard
|
||||
* @return List of {@link IndexDefinitionHolder} representing indexes for given type and its referenced property
|
||||
* types. Will never be {@code null}.
|
||||
*/
|
||||
private List<IndexDefinitionHolder> resolveIndexForClass(final TypeInformation<?> type, final String path,
|
||||
final String collection, final CycleGuard guard) {
|
||||
private List<IndexDefinitionHolder> resolveIndexForClass(final TypeInformation<?> type, final String dotPath,
|
||||
final Path path, final String collection, final CycleGuard guard) {
|
||||
|
||||
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(type);
|
||||
|
||||
final List<IndexDefinitionHolder> indexInformation = new ArrayList<MongoPersistentEntityIndexResolver.IndexDefinitionHolder>();
|
||||
indexInformation.addAll(potentiallyCreateCompoundIndexDefinitions(path, collection, entity));
|
||||
final List<IndexDefinitionHolder> indexInformation = new ArrayList<IndexDefinitionHolder>();
|
||||
indexInformation.addAll(potentiallyCreateCompoundIndexDefinitions(dotPath, collection, entity));
|
||||
|
||||
entity.doWithProperties(new PropertyHandler<MongoPersistentProperty>() {
|
||||
|
||||
@Override
|
||||
public void doWithPersistentProperty(MongoPersistentProperty persistentProperty) {
|
||||
|
||||
String propertyDotPath = (StringUtils.hasText(path) ? path + "." : "") + persistentProperty.getFieldName();
|
||||
guard.protect(persistentProperty, path);
|
||||
String propertyDotPath = (StringUtils.hasText(dotPath) ? dotPath + "." : "")
|
||||
+ persistentProperty.getFieldName();
|
||||
Path propertyPath = path.append(persistentProperty);
|
||||
guard.protect(persistentProperty, propertyPath);
|
||||
|
||||
if (persistentProperty.isEntity()) {
|
||||
try {
|
||||
indexInformation.addAll(resolveIndexForClass(persistentProperty.getTypeInformation().getActualType(),
|
||||
propertyDotPath, collection, guard));
|
||||
propertyDotPath, propertyPath, collection, guard));
|
||||
} catch (CyclicPropertyReferenceException e) {
|
||||
LOGGER.info(e.getMessage());
|
||||
}
|
||||
@@ -174,7 +187,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
||||
}
|
||||
});
|
||||
|
||||
indexInformation.addAll(resolveIndexesForDbrefs(path, collection, entity));
|
||||
indexInformation.addAll(resolveIndexesForDbrefs(dotPath, collection, entity));
|
||||
|
||||
return indexInformation;
|
||||
}
|
||||
@@ -212,8 +225,8 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
||||
}
|
||||
|
||||
try {
|
||||
appendTextIndexInformation("", indexDefinitionBuilder, root, new TextIndexIncludeOptions(IncludeStrategy.DEFAULT),
|
||||
new CycleGuard());
|
||||
appendTextIndexInformation("", Path.empty(), indexDefinitionBuilder, root,
|
||||
new TextIndexIncludeOptions(IncludeStrategy.DEFAULT), new CycleGuard());
|
||||
} catch (CyclicPropertyReferenceException e) {
|
||||
LOGGER.info(e.getMessage());
|
||||
}
|
||||
@@ -229,15 +242,16 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
||||
|
||||
}
|
||||
|
||||
private void appendTextIndexInformation(final String dotPath, final TextIndexDefinitionBuilder indexDefinitionBuilder,
|
||||
final MongoPersistentEntity<?> entity, final TextIndexIncludeOptions includeOptions, final CycleGuard guard) {
|
||||
private void appendTextIndexInformation(final String dotPath, final Path path,
|
||||
final TextIndexDefinitionBuilder indexDefinitionBuilder, final MongoPersistentEntity<?> entity,
|
||||
final TextIndexIncludeOptions includeOptions, final CycleGuard guard) {
|
||||
|
||||
entity.doWithProperties(new PropertyHandler<MongoPersistentProperty>() {
|
||||
|
||||
@Override
|
||||
public void doWithPersistentProperty(MongoPersistentProperty persistentProperty) {
|
||||
|
||||
guard.protect(persistentProperty, dotPath);
|
||||
guard.protect(persistentProperty, path);
|
||||
|
||||
if (persistentProperty.isExplicitLanguageProperty() && !StringUtils.hasText(dotPath)) {
|
||||
indexDefinitionBuilder.withLanguageOverride(persistentProperty.getFieldName());
|
||||
@@ -250,6 +264,8 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
||||
String propertyDotPath = (StringUtils.hasText(dotPath) ? dotPath + "." : "")
|
||||
+ persistentProperty.getFieldName();
|
||||
|
||||
Path propertyPath = path.append(persistentProperty);
|
||||
|
||||
Float weight = indexed != null ? indexed.weight()
|
||||
: (includeOptions.getParentFieldSpec() != null ? includeOptions.getParentFieldSpec().getWeight() : 1.0F);
|
||||
|
||||
@@ -262,7 +278,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
||||
}
|
||||
|
||||
try {
|
||||
appendTextIndexInformation(propertyDotPath, indexDefinitionBuilder,
|
||||
appendTextIndexInformation(propertyDotPath, propertyPath, indexDefinitionBuilder,
|
||||
mappingContext.getPersistentEntity(persistentProperty.getActualType()), optionsForNestedType, guard);
|
||||
} catch (CyclicPropertyReferenceException e) {
|
||||
LOGGER.info(e.getMessage());
|
||||
@@ -291,7 +307,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
||||
protected List<IndexDefinitionHolder> createCompoundIndexDefinitions(String dotPath, String fallbackCollection,
|
||||
MongoPersistentEntity<?> entity) {
|
||||
|
||||
List<IndexDefinitionHolder> indexDefinitions = new ArrayList<MongoPersistentEntityIndexResolver.IndexDefinitionHolder>();
|
||||
List<IndexDefinitionHolder> indexDefinitions = new ArrayList<IndexDefinitionHolder>();
|
||||
CompoundIndexes indexes = entity.findAnnotation(CompoundIndexes.class);
|
||||
|
||||
if (indexes != null) {
|
||||
@@ -482,53 +498,38 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
||||
* to detect potential cycles within the references.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
static class CycleGuard {
|
||||
|
||||
private final Map<String, List<Path>> propertyTypeMap;
|
||||
|
||||
CycleGuard() {
|
||||
this.propertyTypeMap = new LinkedHashMap<String, List<Path>>();
|
||||
}
|
||||
private final Set<String> seenProperties = new HashSet<String>();
|
||||
|
||||
/**
|
||||
* Detect a cycle in a property path if the property was seen at least once.
|
||||
*
|
||||
* @param property The property to inspect
|
||||
* @param path The path under which the property can be reached.
|
||||
* @param path The type path under which the property can be reached.
|
||||
* @throws CyclicPropertyReferenceException in case a potential cycle is detected.
|
||||
* @see Path#cycles(MongoPersistentProperty, String)
|
||||
* @see Path#isCycle()
|
||||
*/
|
||||
void protect(MongoPersistentProperty property, String path) throws CyclicPropertyReferenceException {
|
||||
void protect(MongoPersistentProperty property, Path path) throws CyclicPropertyReferenceException {
|
||||
|
||||
String propertyTypeKey = createMapKey(property);
|
||||
if (propertyTypeMap.containsKey(propertyTypeKey)) {
|
||||
if (!seenProperties.add(propertyTypeKey)) {
|
||||
|
||||
List<Path> paths = propertyTypeMap.get(propertyTypeKey);
|
||||
|
||||
for (Path existingPath : paths) {
|
||||
|
||||
if (existingPath.cycles(property, path) && property.isEntity()) {
|
||||
paths.add(new Path(property, path));
|
||||
|
||||
throw new CyclicPropertyReferenceException(property.getFieldName(), property.getOwner().getType(),
|
||||
existingPath.getPath());
|
||||
}
|
||||
if (path.isCycle()) {
|
||||
throw new CyclicPropertyReferenceException(property.getFieldName(), property.getOwner().getType(),
|
||||
path.toCyclePath());
|
||||
}
|
||||
|
||||
paths.add(new Path(property, path));
|
||||
} else {
|
||||
|
||||
ArrayList<Path> paths = new ArrayList<Path>();
|
||||
paths.add(new Path(property, path));
|
||||
propertyTypeMap.put(propertyTypeKey, paths);
|
||||
}
|
||||
}
|
||||
|
||||
private String createMapKey(MongoPersistentProperty property) {
|
||||
return property.getOwner().getType().getSimpleName() + ":" + property.getFieldName();
|
||||
return ClassUtils.getShortName(property.getOwner().getType()) + ":" + property.getFieldName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Path defines the property and its full path from the document root. <br />
|
||||
* Path defines the full property path from the document root. <br />
|
||||
* A {@link Path} with {@literal spring.data.mongodb} would be created for the property {@code Three.mongodb}.
|
||||
*
|
||||
* <pre>
|
||||
@@ -549,39 +550,117 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
||||
* </pre>
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
@EqualsAndHashCode
|
||||
static class Path {
|
||||
|
||||
private final MongoPersistentProperty property;
|
||||
private final String path;
|
||||
private static final Path EMPTY = new Path(Collections.<PersistentProperty<?>> emptyList(), false);
|
||||
|
||||
Path(MongoPersistentProperty property, String path) {
|
||||
private final List<PersistentProperty<?>> elements;
|
||||
private final boolean cycle;
|
||||
|
||||
this.property = property;
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
return path;
|
||||
/**
|
||||
* @return an empty {@link Path}.
|
||||
* @since 1.10.8
|
||||
*/
|
||||
static Path empty() {
|
||||
return EMPTY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given property is owned by the same entity and if it has been already visited by a subset of
|
||||
* the current path. Given {@literal foo.bar.bar} cycles if {@literal foo.bar} has already been visited and
|
||||
* {@code class Bar} contains a property of type {@code Bar}. The previously mentioned path would not cycle if
|
||||
* {@code class Bar} contained a property of type {@code SomeEntity} named {@literal bar}.
|
||||
* Creates a new {@link Path} from the initial {@link PersistentProperty}.
|
||||
*
|
||||
* @param property
|
||||
* @param path
|
||||
* @return
|
||||
* @param initial must not be {@literal null}.
|
||||
* @return the new {@link Path}.
|
||||
* @since 1.10.8
|
||||
*/
|
||||
boolean cycles(MongoPersistentProperty property, String path) {
|
||||
static Path of(PersistentProperty<?> initial) {
|
||||
return new Path(Collections.<PersistentProperty<?>> singletonList(initial), false);
|
||||
}
|
||||
|
||||
if (!property.getOwner().equals(this.property.getOwner())) {
|
||||
return false;
|
||||
/**
|
||||
* Creates a new {@link Path} by appending a {@link PersistentProperty breadcrumb} to the path.
|
||||
*
|
||||
* @param breadcrumb must not be {@literal null}.
|
||||
* @return the new {@link Path}.
|
||||
* @since 1.10.8
|
||||
*/
|
||||
Path append(PersistentProperty<?> breadcrumb) {
|
||||
|
||||
List<PersistentProperty<?>> elements = new ArrayList<PersistentProperty<?>>(this.elements.size() + 1);
|
||||
elements.addAll(this.elements);
|
||||
elements.add(breadcrumb);
|
||||
|
||||
return new Path(elements, this.elements.contains(breadcrumb));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@literal true} if a cycle was detected.
|
||||
* @since 1.10.8
|
||||
*/
|
||||
public boolean isCycle() {
|
||||
return cycle;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.elements.isEmpty() ? "(empty)" : toPath(this.elements.iterator());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the cycle path truncated to the first discovered cycle. The result for the path
|
||||
* {@literal foo.bar.baz.bar} is {@literal bar -> baz -> bar}.
|
||||
*
|
||||
* @return the cycle path truncated to the first discovered cycle.
|
||||
* @since 1.10.8
|
||||
*/
|
||||
String toCyclePath() {
|
||||
|
||||
if(!cycle) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return path.equals(this.path) || path.contains(this.path + ".") || path.contains("." + this.path);
|
||||
for (int i = 0; i < this.elements.size(); i++) {
|
||||
|
||||
int index = indexOf(this.elements, this.elements.get(i), i + 1);
|
||||
|
||||
if (index != -1) {
|
||||
return toPath(this.elements.subList(i, index + 1).iterator());
|
||||
}
|
||||
}
|
||||
|
||||
return toString();
|
||||
}
|
||||
|
||||
private static <T> int indexOf(List<T> haystack, T needle, int offset) {
|
||||
|
||||
for (int i = offset; i < haystack.size(); i++) {
|
||||
if (haystack.get(i).equals(needle)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static String toPath(Iterator<PersistentProperty<?>> iterator) {
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
while (iterator.hasNext()) {
|
||||
|
||||
builder.append(iterator.next().getName());
|
||||
if (iterator.hasNext()) {
|
||||
builder.append(" -> ");
|
||||
}
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,6 +166,32 @@ public class QueryByExampleTests {
|
||||
assertThat(result, hasItems(p1, p2));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1768
|
||||
public void typedExampleMatchesNothingIfTypesDoNotMatch() {
|
||||
|
||||
NotAPersonButStillMatchingFields probe = new NotAPersonButStillMatchingFields();
|
||||
probe.lastname = "stark";
|
||||
|
||||
Query query = new Query(new Criteria().alike(Example.of(probe)));
|
||||
List<Person> result = operations.find(query, Person.class);
|
||||
|
||||
assertThat(result, hasSize(0));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1768
|
||||
public void untypedExampleMatchesCorrectly() {
|
||||
|
||||
NotAPersonButStillMatchingFields probe = new NotAPersonButStillMatchingFields();
|
||||
probe.lastname = "stark";
|
||||
|
||||
Query query = new Query(
|
||||
new Criteria().alike(Example.of(probe, ExampleMatcher.matching().withIgnorePaths("_class"))));
|
||||
List<Person> result = operations.find(query, Person.class);
|
||||
|
||||
assertThat(result, hasSize(2));
|
||||
assertThat(result, hasItems(p1, p3));
|
||||
}
|
||||
|
||||
@Document(collection = "dramatis-personae")
|
||||
@EqualsAndHashCode
|
||||
@ToString
|
||||
@@ -175,4 +201,12 @@ public class QueryByExampleTests {
|
||||
String firstname, middlename;
|
||||
@Field("last_name") String lastname;
|
||||
}
|
||||
|
||||
@EqualsAndHashCode
|
||||
@ToString
|
||||
static class NotAPersonButStillMatchingFields {
|
||||
|
||||
String firstname, middlename;
|
||||
@Field("last_name") String lastname;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,6 +88,7 @@ import com.mongodb.util.JSON;
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @author Nikolay Bogdanov
|
||||
* @author Sergey Shcherbakov
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration("classpath:infrastructure.xml")
|
||||
@@ -151,7 +152,7 @@ public class AggregationTests {
|
||||
/**
|
||||
* Imports the sample dataset (zips.json) if necessary (e.g. if it doesn't exist yet). The dataset can originally be
|
||||
* found on the mongodb aggregation framework example website:
|
||||
*
|
||||
*
|
||||
* @see <a href="https://docs.mongodb.org/manual/tutorial/aggregation-examples/">MongoDB Aggregation Examples</a>
|
||||
*/
|
||||
private void initSampleDataIfNecessary() {
|
||||
@@ -343,7 +344,7 @@ public class AggregationTests {
|
||||
public void complexAggregationFrameworkUsageLargestAndSmallestCitiesByState() {
|
||||
/*
|
||||
//complex mongodb aggregation framework example from https://docs.mongodb.org/manual/tutorial/aggregation-examples/#largest-and-smallest-cities-by-state
|
||||
db.zipInfo.aggregate(
|
||||
db.zipInfo.aggregate(
|
||||
{
|
||||
$group: {
|
||||
_id: {
|
||||
@@ -450,18 +451,18 @@ public class AggregationTests {
|
||||
@Test // DATAMONGO-586
|
||||
public void findStatesWithPopulationOver10MillionAggregationExample() {
|
||||
/*
|
||||
//complex mongodb aggregation framework example from
|
||||
//complex mongodb aggregation framework example from
|
||||
https://docs.mongodb.org/manual/tutorial/aggregation-examples/#largest-and-smallest-cities-by-state
|
||||
|
||||
db.zipcodes.aggregate(
|
||||
|
||||
db.zipcodes.aggregate(
|
||||
{
|
||||
$group: {
|
||||
_id:"$state",
|
||||
totalPop:{ $sum:"$pop"}
|
||||
}
|
||||
},
|
||||
{
|
||||
$sort: { _id: 1, "totalPop": 1 }
|
||||
{
|
||||
$sort: { _id: 1, "totalPop": 1 }
|
||||
},
|
||||
{
|
||||
$match: {
|
||||
@@ -726,6 +727,49 @@ public class AggregationTests {
|
||||
assertThat(((Number) good.get("score")).longValue(), is(equalTo(9000L)));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1784
|
||||
public void shouldAllowSumUsingConditionalExpressions() {
|
||||
|
||||
mongoTemplate.dropCollection(CarPerson.class);
|
||||
|
||||
CarPerson person1 = new CarPerson("first1", "last1", new CarDescriptor.Entry("MAKE1", "MODEL1", 2000),
|
||||
new CarDescriptor.Entry("MAKE1", "MODEL2", 2001));
|
||||
|
||||
CarPerson person2 = new CarPerson("first2", "last2", new CarDescriptor.Entry("MAKE3", "MODEL4", 2014));
|
||||
CarPerson person3 = new CarPerson("first3", "last3", new CarDescriptor.Entry("MAKE2", "MODEL5", 2015));
|
||||
|
||||
mongoTemplate.save(person1);
|
||||
mongoTemplate.save(person2);
|
||||
mongoTemplate.save(person3);
|
||||
|
||||
TypedAggregation<CarPerson> agg = Aggregation.newAggregation(CarPerson.class,
|
||||
unwind("descriptors.carDescriptor.entries"), //
|
||||
project() //
|
||||
.and(ConditionalOperators //
|
||||
.when(Criteria.where("descriptors.carDescriptor.entries.make").is("MAKE1")).then("good")
|
||||
.otherwise("meh"))
|
||||
.as("make") //
|
||||
.and("descriptors.carDescriptor.entries.model").as("model") //
|
||||
.and("descriptors.carDescriptor.entries.year").as("year"), //
|
||||
group("make").sum(ConditionalOperators //
|
||||
.when(Criteria.where("year").gte(2012)) //
|
||||
.then(1) //
|
||||
.otherwise(9000)).as("score"),
|
||||
sort(ASC, "make"));
|
||||
|
||||
AggregationResults<DBObject> result = mongoTemplate.aggregate(agg, DBObject.class);
|
||||
|
||||
assertThat(result.getMappedResults(), hasSize(2));
|
||||
|
||||
DBObject meh = result.getMappedResults().get(0);
|
||||
assertThat((String) meh.get("_id"), is(equalTo("meh")));
|
||||
assertThat(((Number) meh.get("score")).longValue(), is(equalTo(2L)));
|
||||
|
||||
DBObject good = result.getMappedResults().get(1);
|
||||
assertThat((String) good.get("_id"), is(equalTo("good")));
|
||||
assertThat(((Number) good.get("score")).longValue(), is(equalTo(18000L)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.com/manual/tutorial/aggregation-with-user-preference-data/#return-the-five-most-common-likes">Return
|
||||
|
||||
@@ -40,7 +40,7 @@ import com.mongodb.util.JSON;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link Aggregation}.
|
||||
*
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Thomas Darimont
|
||||
* @author Christoph Strobl
|
||||
@@ -564,6 +564,16 @@ public class AggregationUnitTests {
|
||||
assertThat(getAsDBObject(fields, "foosum"), isBsonObject().containing("$first.$cond.else", "no-answer"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1756
|
||||
public void projectOperationShouldRenderNestedFieldNamesCorrectly() {
|
||||
|
||||
DBObject agg = newAggregation(project().and("value1.value").plus("value2.value").as("val")).toDbObject("collection",
|
||||
Aggregation.DEFAULT_CONTEXT);
|
||||
|
||||
assertThat((BasicDBObject) extractPipelineElement(agg, 0, "$project"), is(equalTo(new BasicDBObject("val",
|
||||
new BasicDBObject("$add", new BasicDbListBuilder().add("$value1.value").add("$value2.value").get())))));
|
||||
}
|
||||
|
||||
private DBObject extractPipelineElement(DBObject agg, int index, String operation) {
|
||||
|
||||
List<DBObject> pipeline = (List<DBObject>) agg.get("pipeline");
|
||||
|
||||
@@ -24,6 +24,7 @@ import java.util.Arrays;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.mongodb.core.DBObjectTestUtils;
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.DBObject;
|
||||
@@ -218,6 +219,31 @@ public class GroupOperationUnitTests {
|
||||
assertThat(push, is((DBObject) new BasicDBObject("$stdDevPop", "$field")));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1784
|
||||
public void shouldRenderSumWithExpressionInGroup() {
|
||||
|
||||
GroupOperation groupOperation = Aggregation //
|
||||
.group("username") //
|
||||
.sum(ConditionalOperators //
|
||||
.when(Criteria.where("foo").is("bar")) //
|
||||
.then(1) //
|
||||
.otherwise(-1)) //
|
||||
.as("foobar");
|
||||
|
||||
DBObject groupClause = extractDbObjectFromGroupOperation(groupOperation);
|
||||
DBObject foobar = DBObjectTestUtils.getAsDBObject(groupClause, "foobar");
|
||||
|
||||
assertThat((DBObject) foobar.get("$sum"),
|
||||
is((DBObject) new BasicDBObject("$cond",
|
||||
new BasicDBObject("if", new BasicDBObject("$eq", Arrays.asList("$foo", "bar"))).append("then", 1)
|
||||
.append("else", -1))));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class) // DATAMONGO-1784
|
||||
public void sumWithNullExpressionShouldThrowException() {
|
||||
Aggregation.group("username").sum((AggregationExpression) null);
|
||||
}
|
||||
|
||||
private DBObject extractDbObjectFromGroupOperation(GroupOperation groupOperation) {
|
||||
DBObject dbObject = groupOperation.toDBObject(Aggregation.DEFAULT_CONTEXT);
|
||||
DBObject groupClause = DBObjectTestUtils.getAsDBObject(dbObject, "$group");
|
||||
|
||||
@@ -47,6 +47,7 @@ import org.springframework.data.mongodb.core.convert.QueryMapper;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
import org.springframework.data.mongodb.test.util.BasicDbListBuilder;
|
||||
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.DBObject;
|
||||
@@ -54,10 +55,11 @@ import com.mongodb.util.JSON;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link TypeBasedAggregationOperationContext}.
|
||||
*
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Thomas Darimont
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class TypeBasedAggregationOperationContextUnitTests {
|
||||
@@ -336,6 +338,19 @@ public class TypeBasedAggregationOperationContextUnitTests {
|
||||
assertThat(age, isBsonObject().containing("$ifNull.[1]._class", Age.class.getName()));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1756
|
||||
public void projectOperationShouldRenderNestedFieldNamesCorrectlyForTypedAggregation() {
|
||||
|
||||
AggregationOperationContext context = getContext(Wrapper.class);
|
||||
|
||||
DBObject agg = newAggregation(Wrapper.class, project().and("nested1.value1").plus("nested2.value2").as("val"))
|
||||
.toDbObject("collection", context);
|
||||
|
||||
BasicDBObject project = (BasicDBObject) getPipelineElementFromAggregationAt(agg, 0).get("$project");
|
||||
assertThat(project, is(equalTo(new BasicDBObject("val", new BasicDBObject("$add",
|
||||
new BasicDbListBuilder().add("$nested1.value1").add("$field2.nestedValue2").get())))));
|
||||
}
|
||||
|
||||
@Document(collection = "person")
|
||||
public static class FooPerson {
|
||||
|
||||
@@ -406,4 +421,15 @@ public class TypeBasedAggregationOperationContextUnitTests {
|
||||
|
||||
String name;
|
||||
}
|
||||
|
||||
static class Wrapper {
|
||||
|
||||
Nested nested1;
|
||||
@org.springframework.data.mongodb.core.mapping.Field("field2") Nested nested2;
|
||||
}
|
||||
|
||||
static class Nested {
|
||||
String value1;
|
||||
@org.springframework.data.mongodb.core.mapping.Field("nestedValue2") String value2;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ public class DefaultDbRefResolverUnitTests {
|
||||
when(factoryMock.getDb()).thenReturn(dbMock);
|
||||
when(dbMock.getCollection(anyString())).thenReturn(collectionMock);
|
||||
when(collectionMock.find(Mockito.any(DBObject.class))).thenReturn(cursorMock);
|
||||
when(cursorMock.toArray()).thenReturn(Collections.<DBObject>emptyList());
|
||||
when(cursorMock.toArray()).thenReturn(Collections.<DBObject> emptyList());
|
||||
|
||||
resolver = new DefaultDbRefResolver(factoryMock);
|
||||
}
|
||||
@@ -100,7 +100,7 @@ public class DefaultDbRefResolverUnitTests {
|
||||
@Test // DATAMONGO-1194
|
||||
public void bulkFetchShouldReturnEarlyForEmptyLists() {
|
||||
|
||||
resolver.bulkFetch(Collections.<DBRef>emptyList());
|
||||
resolver.bulkFetch(Collections.<DBRef> emptyList());
|
||||
|
||||
verify(collectionMock, never()).find(Mockito.any(DBObject.class));
|
||||
}
|
||||
@@ -118,4 +118,17 @@ public class DefaultDbRefResolverUnitTests {
|
||||
|
||||
assertThat(resolver.bulkFetch(Arrays.asList(ref1, ref2)), contains(o1, o2));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1765
|
||||
public void bulkFetchContainsDuplicates() {
|
||||
|
||||
DBObject document = new BasicDBObject("_id", new ObjectId());
|
||||
|
||||
DBRef ref1 = new DBRef("collection-1", document.get("_id"));
|
||||
DBRef ref2 = new DBRef("collection-1", document.get("_id"));
|
||||
|
||||
when(cursorMock.toArray()).thenReturn(Arrays.asList(document));
|
||||
|
||||
assertThat(resolver.bulkFetch(Arrays.asList(ref1, ref2)), contains(document, document));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import static org.springframework.data.mongodb.test.util.IsBsonObject.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.bson.BSONObject;
|
||||
@@ -36,8 +37,7 @@ import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.domain.Example;
|
||||
import org.springframework.data.domain.ExampleMatcher;
|
||||
import org.springframework.data.domain.ExampleMatcher.GenericPropertyMatchers;
|
||||
import org.springframework.data.domain.ExampleMatcher.StringMatcher;
|
||||
import org.springframework.data.domain.ExampleMatcher.*;
|
||||
import org.springframework.data.geo.Point;
|
||||
import org.springframework.data.mongodb.MongoDbFactory;
|
||||
import org.springframework.data.mongodb.core.convert.QueryMapperUnitTests.ClassWithGeoTypes;
|
||||
@@ -47,6 +47,7 @@ import org.springframework.data.mongodb.core.mapping.Document;
|
||||
import org.springframework.data.mongodb.core.mapping.Field;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
|
||||
import org.springframework.data.mongodb.test.util.IsBsonObject;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
|
||||
import com.mongodb.BasicDBList;
|
||||
import com.mongodb.BasicDBObject;
|
||||
@@ -288,7 +289,7 @@ public class MongoExampleMapperUnitTests {
|
||||
DBObject dbo = mapper.getMappedExample(of(probe), context.getPersistentEntity(WithDBRef.class));
|
||||
com.mongodb.DBRef reference = getTypedValue(dbo, "referenceDocument", com.mongodb.DBRef.class);
|
||||
|
||||
assertThat(reference.getId(), Is.<Object>is("200"));
|
||||
assertThat(reference.getId(), Is.<Object> is("200"));
|
||||
assertThat(reference.getCollectionName(), is("refDoc"));
|
||||
}
|
||||
|
||||
@@ -311,8 +312,8 @@ public class MongoExampleMapperUnitTests {
|
||||
|
||||
DBObject dbo = mapper.getMappedExample(of(probe), context.getPersistentEntity(WithDBRef.class));
|
||||
|
||||
assertThat(dbo.get("legacyPoint.x"), Is.<Object>is(10D));
|
||||
assertThat(dbo.get("legacyPoint.y"), Is.<Object>is(20D));
|
||||
assertThat(dbo.get("legacyPoint.x"), Is.<Object> is(10D));
|
||||
assertThat(dbo.get("legacyPoint.y"), Is.<Object> is(20D));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1245
|
||||
@@ -426,6 +427,52 @@ public class MongoExampleMapperUnitTests {
|
||||
assertThat(mapper.getMappedExample(example), isBsonObject().containing("$or").containing("_class"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1768
|
||||
public void allowIgnoringTypeRestrictionBySettingUpTypeKeyAsAnIgnoredPath() {
|
||||
|
||||
WrapperDocument probe = new WrapperDocument();
|
||||
probe.flatDoc = new FlatDocument();
|
||||
probe.flatDoc.stringValue = "conflux";
|
||||
|
||||
DBObject dbo = mapper.getMappedExample(Example.of(probe, ExampleMatcher.matching().withIgnorePaths("_class")));
|
||||
|
||||
assertThat(dbo, isBsonObject().notContaining("_class"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1768
|
||||
public void allowIgnoringTypeRestrictionBySettingUpTypeKeyAsAnIgnoredPathWhenUsingCustomTypeMapper() {
|
||||
|
||||
WrapperDocument probe = new WrapperDocument();
|
||||
probe.flatDoc = new FlatDocument();
|
||||
probe.flatDoc.stringValue = "conflux";
|
||||
|
||||
MappingMongoConverter mappingMongoConverter = new MappingMongoConverter(new DefaultDbRefResolver(factory), context);
|
||||
mappingMongoConverter.setTypeMapper(new DefaultMongoTypeMapper() {
|
||||
|
||||
@Override
|
||||
public boolean isTypeKey(String key) {
|
||||
return "_foo".equals(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTypeRestrictions(DBObject dbo, Set<Class<?>> restrictedTypes) {
|
||||
dbo.put("_foo", "bar");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeType(TypeInformation<?> info, DBObject sink) {
|
||||
sink.put("_foo", "bar");
|
||||
|
||||
}
|
||||
});
|
||||
mappingMongoConverter.afterPropertiesSet();
|
||||
|
||||
DBObject dbo = new MongoExampleMapper(mappingMongoConverter)
|
||||
.getMappedExample(Example.of(probe, ExampleMatcher.matching().withIgnorePaths("_foo")));
|
||||
|
||||
assertThat(dbo, isBsonObject().notContaining("_class").notContaining("_foo"));
|
||||
}
|
||||
|
||||
static class FlatDocument {
|
||||
|
||||
@Id String id;
|
||||
|
||||
@@ -901,6 +901,34 @@ public class UpdateMapperUnitTests {
|
||||
}
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1772
|
||||
public void mappingShouldAddTypeKeyInListOfInterfaceTypeContainedInConcreteObjectCorrectly() {
|
||||
|
||||
ConcreteInner inner = new ConcreteInner();
|
||||
inner.interfaceTypeList = Collections.<SomeInterfaceType> singletonList(new SomeInterfaceImpl());
|
||||
List<ConcreteInner> list = Collections.singletonList(inner);
|
||||
|
||||
DBObject mappedUpdate = mapper.getMappedObject(new Update().set("concreteInnerList", list).getUpdateObject(),
|
||||
context.getPersistentEntity(Outer.class));
|
||||
|
||||
assertThat(mappedUpdate, isBsonObject().containing("$set.concreteInnerList.[0].interfaceTypeList.[0]._class")
|
||||
.notContaining("$set.concreteInnerList.[0]._class"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1772
|
||||
public void mappingShouldAddTypeKeyInListOfAbstractTypeContainedInConcreteObjectCorrectly() {
|
||||
|
||||
ConcreteInner inner = new ConcreteInner();
|
||||
inner.abstractTypeList = Collections.<SomeAbstractType> singletonList(new SomeInterfaceImpl());
|
||||
List<ConcreteInner> list = Collections.singletonList(inner);
|
||||
|
||||
DBObject mappedUpdate = mapper.getMappedObject(new Update().set("concreteInnerList", list).getUpdateObject(),
|
||||
context.getPersistentEntity(Outer.class));
|
||||
|
||||
assertThat(mappedUpdate, isBsonObject().containing("$set.concreteInnerList.[0].abstractTypeList.[0]._class")
|
||||
.notContaining("$set.concreteInnerList.[0]._class"));
|
||||
}
|
||||
|
||||
static class DomainTypeWrappingConcreteyTypeHavingListOfInterfaceTypeAttributes {
|
||||
ListModelWrapper concreteTypeWithListAttributeOfInterfaceType;
|
||||
}
|
||||
@@ -1187,4 +1215,26 @@ public class UpdateMapperUnitTests {
|
||||
Integer intValue;
|
||||
int primIntValue;
|
||||
}
|
||||
|
||||
static class Outer {
|
||||
List<ConcreteInner> concreteInnerList;
|
||||
}
|
||||
|
||||
static class ConcreteInner {
|
||||
List<SomeInterfaceType> interfaceTypeList;
|
||||
List<SomeAbstractType> abstractTypeList;
|
||||
}
|
||||
|
||||
interface SomeInterfaceType {
|
||||
|
||||
}
|
||||
|
||||
static abstract class SomeAbstractType {
|
||||
|
||||
}
|
||||
|
||||
static class SomeInterfaceImpl extends SomeAbstractType implements SomeInterfaceType {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ public class MongoPersistentEntityIndexResolverUnitTests {
|
||||
|
||||
/**
|
||||
* Test resolution of {@link Indexed}.
|
||||
*
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public static class IndexResolutionTests {
|
||||
@@ -310,7 +310,7 @@ public class MongoPersistentEntityIndexResolverUnitTests {
|
||||
|
||||
/**
|
||||
* Test resolution of {@link GeoSpatialIndexed}.
|
||||
*
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public static class GeoSpatialIndexResolutionTests {
|
||||
@@ -423,7 +423,7 @@ public class MongoPersistentEntityIndexResolverUnitTests {
|
||||
|
||||
/**
|
||||
* Test resolution of {@link CompoundIndexes}.
|
||||
*
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public static class CompoundIndexResolutionTests {
|
||||
@@ -914,6 +914,17 @@ public class MongoPersistentEntityIndexResolverUnitTests {
|
||||
.resolveIndexForEntity(selfCyclingEntity);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1782
|
||||
public void shouldAllowMultiplePathsToDeeplyType() {
|
||||
|
||||
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(
|
||||
NoCycleManyPathsToDeepValueObject.class);
|
||||
|
||||
assertIndexPathAndCollection("l3.valueObject.value", "rules", indexDefinitions.get(0));
|
||||
assertIndexPathAndCollection("l2.l3.valueObject.value", "rules", indexDefinitions.get(1));
|
||||
assertThat(indexDefinitions, hasSize(2));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1025
|
||||
public void shouldUsePathIndexAsIndexNameForDocumentsHavingNamedNestedCompoundIndexFixedOnCollection() {
|
||||
|
||||
@@ -1071,6 +1082,25 @@ public class MongoPersistentEntityIndexResolverUnitTests {
|
||||
@Indexed String foo;
|
||||
}
|
||||
|
||||
@Document(collection = "rules")
|
||||
static class NoCycleManyPathsToDeepValueObject {
|
||||
|
||||
private NoCycleLevel3 l3;
|
||||
private NoCycleLevel2 l2;
|
||||
}
|
||||
|
||||
static class NoCycleLevel2 {
|
||||
private NoCycleLevel3 l3;
|
||||
}
|
||||
|
||||
static class NoCycleLevel3 {
|
||||
private ValueObject valueObject;
|
||||
}
|
||||
|
||||
static class ValueObject {
|
||||
@Indexed private String value;
|
||||
}
|
||||
|
||||
@Document
|
||||
static class SimilarityHolingBean {
|
||||
|
||||
@@ -1187,7 +1217,6 @@ public class MongoPersistentEntityIndexResolverUnitTests {
|
||||
static class EntityWithGenericTypeWrapperAsElement {
|
||||
List<GenericEntityWrapper<DocumentWithNamedIndex>> listWithGeneircTypeElement;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static List<IndexDefinitionHolder> prepareMappingContextAndResolveIndexForType(Class<?> type) {
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.index;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@@ -31,8 +31,9 @@ import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link Path}.
|
||||
*
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class PathUnitTests {
|
||||
@@ -45,39 +46,50 @@ public class PathUnitTests {
|
||||
when(entityMock.getType()).thenReturn((Class) Object.class);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-962
|
||||
public void shouldIdentifyCycleForOwnerOfSameTypeAndMatchingPath() {
|
||||
@Test // DATAMONGO-962, DATAMONGO-1782
|
||||
public void shouldIdentifyCycle() {
|
||||
|
||||
MongoPersistentProperty property = createPersistentPropertyMock(entityMock, "foo");
|
||||
assertThat(new Path(property, "foo.bar").cycles(property, "foo.bar.bar"), is(true));
|
||||
MongoPersistentProperty foo = createPersistentPropertyMock(entityMock, "foo");
|
||||
MongoPersistentProperty bar = createPersistentPropertyMock(entityMock, "bar");
|
||||
|
||||
Path path = Path.of(foo).append(bar).append(bar);
|
||||
|
||||
assertThat(path.isCycle(), is(true));
|
||||
assertThat(path.toCyclePath(), is(equalTo("bar -> bar")));
|
||||
assertThat(path.toString(), is(equalTo("foo -> bar -> bar")));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-962
|
||||
@SuppressWarnings("rawtypes")
|
||||
public void shouldAllowMatchingPathForDifferentOwners() {
|
||||
@Test // DATAMONGO-1782
|
||||
public void isCycleShouldReturnFalseWhenNoCyclePresent() {
|
||||
|
||||
MongoPersistentProperty existing = createPersistentPropertyMock(entityMock, "foo");
|
||||
MongoPersistentProperty foo = createPersistentPropertyMock(entityMock, "foo");
|
||||
MongoPersistentProperty bar = createPersistentPropertyMock(entityMock, "bar");
|
||||
|
||||
MongoPersistentEntity entityOfDifferentType = Mockito.mock(MongoPersistentEntity.class);
|
||||
when(entityOfDifferentType.getType()).thenReturn(String.class);
|
||||
MongoPersistentProperty toBeVerified = createPersistentPropertyMock(entityOfDifferentType, "foo");
|
||||
Path path = Path.of(foo).append(bar);
|
||||
|
||||
assertThat(new Path(existing, "foo.bar").cycles(toBeVerified, "foo.bar.bar"), is(false));
|
||||
assertThat(path.isCycle(), is(false));
|
||||
assertThat(path.toCyclePath(), is(equalTo("")));
|
||||
assertThat(path.toString(), is(equalTo("foo -> bar")));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-962
|
||||
public void shouldAllowEqaulPropertiesOnDifferentPaths() {
|
||||
@Test // DATAMONGO-1782
|
||||
public void isCycleShouldReturnFalseCycleForNonEqualProperties() {
|
||||
|
||||
MongoPersistentProperty property = createPersistentPropertyMock(entityMock, "foo");
|
||||
assertThat(new Path(property, "foo.bar").cycles(property, "foo2.bar.bar"), is(false));
|
||||
MongoPersistentProperty foo = createPersistentPropertyMock(entityMock, "foo");
|
||||
MongoPersistentProperty bar = createPersistentPropertyMock(entityMock, "bar");
|
||||
MongoPersistentProperty bar2 = createPersistentPropertyMock(mock(MongoPersistentEntity.class), "bar");
|
||||
|
||||
assertThat(Path.of(foo).append(bar).append(bar2).isCycle(), is(false));
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
private MongoPersistentProperty createPersistentPropertyMock(MongoPersistentEntity owner, String fieldname) {
|
||||
private static MongoPersistentProperty createPersistentPropertyMock(MongoPersistentEntity owner, String fieldname) {
|
||||
|
||||
MongoPersistentProperty property = Mockito.mock(MongoPersistentProperty.class);
|
||||
|
||||
when(property.getOwner()).thenReturn(owner);
|
||||
when(property.getFieldName()).thenReturn(fieldname);
|
||||
when(property.getName()).thenReturn(fieldname);
|
||||
|
||||
return property;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,54 @@
|
||||
Spring Data MongoDB Changelog
|
||||
=============================
|
||||
|
||||
Changes in version 1.10.8.RELEASE (2017-10-11)
|
||||
----------------------------------------------
|
||||
* DATAMONGO-1784 - Add support for AggregationExpression in GroupOperation.sum.
|
||||
* DATAMONGO-1782 - CyclicPropertyReferenceException on index resolution.
|
||||
* DATAMONGO-1775 - Release 1.10.8 (Ingalls SR8).
|
||||
|
||||
|
||||
Changes in version 2.0.0.RELEASE (2017-10-02)
|
||||
---------------------------------------------
|
||||
* DATAMONGO-1791 - Adapt to changed Spring Framework 5 documentation structure.
|
||||
* DATAMONGO-1787 - Add explicit automatic module name for Java 9.
|
||||
* DATAMONGO-1786 - Adapt tests to nullability enforcement in repository query methods.
|
||||
* DATAMONGO-1785 - Upgrade to OpenWebBeans 2.0.1.
|
||||
* DATAMONGO-1784 - Add support for AggregationExpression in GroupOperation.sum.
|
||||
* DATAMONGO-1782 - CyclicPropertyReferenceException on index resolution.
|
||||
* DATAMONGO-1781 - Update what's new in reference documentation.
|
||||
* DATAMONGO-1779 - Query.limit(N) on empty query is not applied.
|
||||
* DATAMONGO-1778 - Update.equals(…) fails to recognize an equal Update object.
|
||||
* DATAMONGO-1777 - Update.toString(…) does not pretty-print modifiers.
|
||||
* DATAMONGO-1776 - Release 2.0 GA (Kay).
|
||||
|
||||
|
||||
Changes in version 2.0.0.RC3 (2017-09-11)
|
||||
-----------------------------------------
|
||||
* DATAMONGO-1774 - ReactiveMongoOperations#remove(Mono, java.lang.String) ends up in an infinite loop.
|
||||
* DATAMONGO-1772 - Type hint not added when updating nested list elements with inheritance.
|
||||
* DATAMONGO-1771 - Fix MongoDB setup for travis-ci.
|
||||
* DATAMONGO-1770 - Upgrade to mongo-java-driver 3.5.0 and mongodb-driver-reactivestreams 1.6.0.
|
||||
* DATAMONGO-1768 - QueryByExample FindOne : probe type.
|
||||
* DATAMONGO-1765 - Duplicate elements in DBRefs list not correctly mapped.
|
||||
* DATAMONGO-1762 - Introduce nullable annotations for API validation.
|
||||
* DATAMONGO-1758 - Remove non existing spring-data-mongodb-log4j from benchmarks profile.
|
||||
* DATAMONGO-1757 - Improve exception message if type to read doesn't match declared type on Map value.
|
||||
* DATAMONGO-1756 - Aggregation project and arithmetic operation not working with nested fields.
|
||||
* DATAMONGO-1754 - Release 2.0 RC3 (Kay).
|
||||
* DATAMONGO-1706 - Adapt to deprecated RxJava 1 CRUD repositories.
|
||||
* DATAMONGO-1631 - AbstractReactiveMongoConfiguration.mongoDbFactory conflicts with blocking MongoDbFactory.
|
||||
|
||||
|
||||
Changes in version 1.10.7.RELEASE (2017-09-11)
|
||||
----------------------------------------------
|
||||
* DATAMONGO-1772 - Type hint not added when updating nested list elements with inheritance.
|
||||
* DATAMONGO-1768 - QueryByExample FindOne : probe type.
|
||||
* DATAMONGO-1765 - Duplicate elements in DBRefs list not correctly mapped.
|
||||
* DATAMONGO-1756 - Aggregation project and arithmetic operation not working with nested fields.
|
||||
* DATAMONGO-1755 - Release 1.10.7 (Ingalls SR7).
|
||||
|
||||
|
||||
Changes in version 1.10.6.RELEASE (2017-07-26)
|
||||
----------------------------------------------
|
||||
* DATAMONGO-1750 - Release 1.10.6 (Ingalls SR6).
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Spring Data MongoDB 1.10.6
|
||||
Spring Data MongoDB 1.10.8
|
||||
Copyright (c) [2010-2015] Pivotal Software, Inc.
|
||||
|
||||
This product is licensed to you under the Apache License, Version 2.0 (the "License").
|
||||
|
||||
Reference in New Issue
Block a user