diff --git a/.travis.yml b/.travis.yml
index 604395caa..a6382b721 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -16,9 +16,12 @@ before_install:
env:
matrix:
- - PROFILE=ci
+ - MONGO_VERSION=4.1.10
+ - MONGO_VERSION=4.0.4
+ - MONGO_VERSION=3.6.12
+ - MONGO_VERSION=3.4.20
global:
- - MONGO_VERSION=4.0.0
+ - PROFILE=ci
addons:
apt:
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultScriptOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultScriptOperations.java
index 5d992b4a4..71d59993b 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultScriptOperations.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultScriptOperations.java
@@ -42,13 +42,15 @@ import com.mongodb.MongoException;
import com.mongodb.client.MongoDatabase;
/**
- * Default implementation of {@link ScriptOperations} capable of saving and executing {@link ServerSideJavaScript}.
+ * Default implementation of {@link ScriptOperations} capable of saving and executing {@link ExecutableMongoScript}.
*
* @author Christoph Strobl
* @author Oliver Gierke
* @author Mark Paluch
* @since 1.7
+ * @deprecated since 2.2. The {@code eval} command has been removed in MongoDB Server 4.2.0.
*/
+@Deprecated
class DefaultScriptOperations implements ScriptOperations {
private static final String SCRIPT_COLLECTION_NAME = "system.js";
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/EntityOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/EntityOperations.java
index cc2cfe88f..24078c973 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/EntityOperations.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/EntityOperations.java
@@ -24,7 +24,6 @@ import java.util.Map;
import java.util.Optional;
import org.bson.Document;
-
import org.springframework.core.convert.ConversionService;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.mapping.IdentifierAccessor;
@@ -45,6 +44,7 @@ import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
+import org.springframework.util.ObjectUtils;
/**
* Common operations performed on an entity in the context of it's mapping metadata.
@@ -164,6 +164,28 @@ class EntityOperations {
return ID_FIELD;
}
+ /**
+ * Return the name used for {@code $geoNear.distanceField} avoiding clashes with potentially existing properties.
+ *
+ * @param domainType must not be {@literal null}.
+ * @return the name of the distanceField to use. {@literal dis} by default.
+ * @since 2.2
+ */
+ public String nearQueryDistanceFieldName(Class> domainType) {
+
+ if (!context.hasPersistentEntityFor(domainType)
+ || context.getPersistentEntity(domainType).getPersistentProperty("dis") == null) {
+ return "dis";
+ }
+
+ String distanceFieldName = "calculated-distance";
+ while (context.getPersistentEntity(domainType).getPersistentProperty(distanceFieldName) != null) {
+ distanceFieldName += "-" + ObjectUtils.getIdentityHexString(new Object());
+ }
+
+ return distanceFieldName;
+ }
+
private static Document parse(String source) {
try {
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoOperations.java
index 11b44ee67..82a7badee 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoOperations.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoOperations.java
@@ -355,7 +355,9 @@ public interface MongoOperations extends FluentMongoOperations {
*
* @return
* @since 1.7
+ * @deprecated since 2.2. The {@code eval} command has been removed without replacement in MongoDB Server 4.2.0.
*/
+ @Deprecated
ScriptOperations scriptOps();
/**
@@ -427,7 +429,11 @@ public interface MongoOperations extends FluentMongoOperations {
* reduce function.
* @param entityClass The parametrized type of the returned list
* @return The results of the group operation
+ * @deprecated since 2.2. The {@code group} command has been removed in MongoDB Server 4.2.0.
+ * Please use {@link #aggregate(TypedAggregation, String, Class) } with a
+ * {@link org.springframework.data.mongodb.core.aggregation.GroupOperation} instead.
*/
+ @Deprecated
GroupByResults group(String inputCollectionName, GroupBy groupBy, Class entityClass);
/**
@@ -442,7 +448,12 @@ public interface MongoOperations extends FluentMongoOperations {
* reduce function.
* @param entityClass The parametrized type of the returned list
* @return The results of the group operation
+ * @deprecated since 2.2. The {@code group} command has been removed in MongoDB Server 4.2.0.
+ * Please use {@link #aggregate(TypedAggregation, String, Class) } with a
+ * {@link org.springframework.data.mongodb.core.aggregation.GroupOperation} and
+ * {@link org.springframework.data.mongodb.core.aggregation.MatchOperation} instead.
*/
+ @Deprecated
GroupByResults group(@Nullable Criteria criteria, String inputCollectionName, GroupBy groupBy,
Class entityClass);
@@ -1147,8 +1158,8 @@ public interface MongoOperations extends FluentMongoOperations {
* If you object has an "Id' property, it will be set with the generated Id from MongoDB. If your Id property is a
* String then MongoDB ObjectId will be used to populate that string. Otherwise, the conversion from ObjectId to your
* property type will be handled by Spring's BeanWrapper class that leverages Type Conversion API. See
- * Spring's Type
- * Conversion" for more details.
+ * Spring's
+ * Type Conversion" for more details.
*
*
* Insert is used to initially store the object into the database. To update an existing object use the save method.
@@ -1209,8 +1220,8 @@ public interface MongoOperations extends FluentMongoOperations {
* If you object has an "Id' property, it will be set with the generated Id from MongoDB. If your Id property is a
* String then MongoDB ObjectId will be used to populate that string. Otherwise, the conversion from ObjectId to your
* property type will be handled by Spring's BeanWrapper class that leverages Type Conversion API. See
- * Spring's Type
- * Conversion" for more details.
+ * Spring's
+ * Type Conversion" for more details.
*
* @param objectToSave the object to store in the collection. Must not be {@literal null}.
* @return the saved object.
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java
index 3beda20b4..389e9f48e 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java
@@ -23,18 +23,9 @@ import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.Scanner;
-import java.util.Set;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@@ -44,7 +35,6 @@ import org.bson.codecs.Codec;
import org.bson.conversions.Bson;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
@@ -126,6 +116,7 @@ import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
+import org.springframework.util.NumberUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ResourceUtils;
import org.springframework.util.StringUtils;
@@ -148,17 +139,7 @@ import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoCursor;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.MongoIterable;
-import com.mongodb.client.model.CountOptions;
-import com.mongodb.client.model.CreateCollectionOptions;
-import com.mongodb.client.model.DeleteOptions;
-import com.mongodb.client.model.FindOneAndDeleteOptions;
-import com.mongodb.client.model.FindOneAndReplaceOptions;
-import com.mongodb.client.model.FindOneAndUpdateOptions;
-import com.mongodb.client.model.ReplaceOptions;
-import com.mongodb.client.model.ReturnDocument;
-import com.mongodb.client.model.UpdateOptions;
-import com.mongodb.client.model.ValidationAction;
-import com.mongodb.client.model.ValidationLevel;
+import com.mongodb.client.model.*;
import com.mongodb.client.result.DeleteResult;
import com.mongodb.client.result.UpdateResult;
@@ -989,7 +970,6 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
return geoNear(near, domainType, collectionName, domainType);
}
- @SuppressWarnings("unchecked")
public GeoResults geoNear(NearQuery near, Class> domainType, String collectionName, Class returnType) {
if (near == null) {
@@ -1005,53 +985,31 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
String collection = StringUtils.hasText(collectionName) ? collectionName
: operations.determineCollectionName(domainType);
- Document nearDocument = near.toDocument();
+ String distanceField = operations.nearQueryDistanceFieldName(domainType);
- Document command = new Document("geoNear", collection);
- command.putAll(queryMapper.getMappedObject(nearDocument, Optional.empty()));
+ Aggregation $geoNear = TypedAggregation.newAggregation(domainType, Aggregation.geoNear(near, distanceField))
+ .withOptions(AggregationOptions.builder().collation(near.getCollation()).build());
- if (nearDocument.containsKey("query")) {
- Document query = (Document) nearDocument.get("query");
- command.put("query", queryMapper.getMappedObject(query, getPersistentEntity(domainType)));
+ AggregationResults results = aggregate($geoNear, collection, Document.class);
+
+ DocumentCallback> callback = new GeoNearResultDocumentCallback<>(distanceField,
+ new ProjectingReadCallback<>(mongoConverter, domainType, returnType, collection), near.getMetric());
+
+ List> result = new ArrayList<>();
+
+ BigDecimal aggregate = BigDecimal.ZERO;
+ for (Document element : results) {
+
+ GeoResult geoResult = callback.doWith(element);
+ aggregate = aggregate.add(new BigDecimal(geoResult.getDistance().getValue()));
+ result.add(geoResult);
}
- if (LOGGER.isDebugEnabled()) {
- LOGGER.debug("Executing geoNear using: {} for class: {} in collection: {}", serializeToJsonSafely(command),
- domainType, collectionName);
- }
+ Distance avgDistance = new Distance(
+ result.size() == 0 ? 0 : aggregate.divide(new BigDecimal(result.size()), RoundingMode.HALF_UP).doubleValue(),
+ near.getMetric());
- Document commandResult = executeCommand(command, this.readPreference);
- List results = (List) commandResult.get("results");
- results = results == null ? Collections.emptyList() : results;
-
- DocumentCallback> callback = new GeoNearResultDocumentCallback<>(
- new ProjectingReadCallback<>(mongoConverter, domainType, returnType, collectionName), near.getMetric());
- List> result = new ArrayList<>(results.size());
-
- int index = 0;
- long elementsToSkip = near.getSkip() != null ? near.getSkip() : 0;
-
- for (Object element : results) {
-
- /*
- * As MongoDB currently (2.4.4) doesn't support the skipping of elements in near queries
- * we skip the elements ourselves to avoid at least the document 2 object mapping overhead.
- *
- * @see MongoDB Jira: SERVER-3925
- */
- if (index >= elementsToSkip) {
- result.add(callback.doWith((Document) element));
- }
- index++;
- }
-
- if (elementsToSkip > 0) {
- // as we skipped some elements we have to calculate the averageDistance ourselves:
- return new GeoResults<>(result, near.getMetric());
- }
-
- GeoCommandStatistics stats = GeoCommandStatistics.from(commandResult);
- return new GeoResults<>(result, new Distance(stats.getAverageDistance(), near.getMetric()));
+ return new GeoResults<>(result, avgDistance);
}
@Nullable
@@ -3314,9 +3272,11 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
* a delegate and creates a {@link GeoResult} from the result.
*
* @author Oliver Gierke
+ * @author Christoph Strobl
*/
static class GeoNearResultDocumentCallback implements DocumentCallback> {
+ private final String distanceField;
private final DocumentCallback delegate;
private final Metric metric;
@@ -3324,12 +3284,15 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
* Creates a new {@link GeoNearResultDocumentCallback} using the given {@link DocumentCallback} delegate for
* {@link GeoResult} content unmarshalling.
*
+ * @param distanceField the field to read the distance from.
* @param delegate must not be {@literal null}.
+ * @param metric the {@link Metric} to apply to the result distance.
*/
- public GeoNearResultDocumentCallback(DocumentCallback delegate, Metric metric) {
+ GeoNearResultDocumentCallback(String distanceField, DocumentCallback delegate, Metric metric) {
Assert.notNull(delegate, "DocumentCallback must not be null!");
+ this.distanceField = distanceField;
this.delegate = delegate;
this.metric = metric;
}
@@ -3337,10 +3300,14 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
@Nullable
public GeoResult doWith(@Nullable Document object) {
- double distance = ((Double) object.get("dis")).doubleValue();
- Document content = (Document) object.get("obj");
+ double distance = Double.NaN;
+ if (object.containsKey(distanceField)) {
- T doWith = delegate.doWith(content);
+ distance = NumberUtils.convertNumberToTargetClass(object.get(distanceField, Number.class), Double.class)
+ .doubleValue();
+ }
+
+ T doWith = delegate.doWith(object);
return new GeoResult<>(doWith, new Distance(distance, metric));
}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java
index 328e92e30..103ca5f49 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java
@@ -20,22 +20,13 @@ import static org.springframework.data.mongodb.core.query.SerializationUtils.*;
import lombok.AccessLevel;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
+import org.springframework.util.NumberUtils;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.function.Tuple2;
import reactor.util.function.Tuples;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.Set;
+import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -50,7 +41,6 @@ import org.reactivestreams.Publisher;
import org.reactivestreams.Subscriber;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
@@ -1060,34 +1050,16 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
}
String collection = StringUtils.hasText(collectionName) ? collectionName : determineCollectionName(entityClass);
- Document nearDocument = near.toDocument();
+ String distanceField = operations.nearQueryDistanceFieldName(entityClass);
- Document command = new Document("geoNear", collection);
- command.putAll(nearDocument);
+ GeoNearResultDocumentCallback callback = new GeoNearResultDocumentCallback<>(distanceField,
+ new ProjectingReadCallback<>(mongoConverter, entityClass, returnType, collection), near.getMetric());
- return Flux.defer(() -> {
+ Aggregation $geoNear = TypedAggregation.newAggregation(entityClass, Aggregation.geoNear(near, "dis"))
+ .withOptions(AggregationOptions.builder().collation(near.getCollation()).build());
- if (nearDocument.containsKey("query")) {
- Document query = (Document) nearDocument.get("query");
- command.put("query", queryMapper.getMappedObject(query, getPersistentEntity(entityClass)));
- }
-
- if (LOGGER.isDebugEnabled()) {
- LOGGER.debug("Executing geoNear using: {} for class: {} in collection: {}", serializeToJsonSafely(command),
- entityClass, collectionName);
- }
-
- GeoNearResultDocumentCallback callback = new GeoNearResultDocumentCallback<>(
- new ProjectingReadCallback<>(mongoConverter, entityClass, returnType, collectionName), near.getMetric());
-
- return executeCommand(command, this.readPreference).flatMapMany(document -> {
-
- List results = document.get("results", List.class);
-
- return results == null ? Flux.empty() : Flux.fromIterable(results);
-
- }).skip(near.getSkip() != null ? near.getSkip() : 0).map(callback::doWith);
- });
+ return aggregate($geoNear, collection, Document.class) //
+ .map(callback::doWith);
}
/*
@@ -3039,9 +3011,11 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
* a delegate and creates a {@link GeoResult} from the result.
*
* @author Mark Paluch
+ * @author Chrstoph Strobl
*/
static class GeoNearResultDocumentCallback implements DocumentCallback> {
+ private final String distanceField;
private final DocumentCallback delegate;
private final Metric metric;
@@ -3049,22 +3023,29 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
* Creates a new {@link GeoNearResultDocumentCallback} using the given {@link DocumentCallback} delegate for
* {@link GeoResult} content unmarshalling.
*
+ * @param distanceField the field to read the distance from.
* @param delegate must not be {@literal null}.
+ * @param metric the {@link Metric} to apply to the result distance.
*/
- GeoNearResultDocumentCallback(DocumentCallback delegate, Metric metric) {
+ GeoNearResultDocumentCallback(String distanceField, DocumentCallback delegate, Metric metric) {
Assert.notNull(delegate, "DocumentCallback must not be null!");
+ this.distanceField = distanceField;
this.delegate = delegate;
this.metric = metric;
}
public GeoResult doWith(Document object) {
- double distance = (Double) object.get("dis");
- Document content = (Document) object.get("obj");
+ double distance = Double.NaN;
+ if (object.containsKey(distanceField)) {
- T doWith = delegate.doWith(content);
+ distance = NumberUtils.convertNumberToTargetClass(object.get(distanceField, Number.class), Double.class)
+ .doubleValue();
+ }
+
+ T doWith = delegate.doWith(object);
return new GeoResult<>(doWith, new Distance(distance, metric));
}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ScriptOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ScriptOperations.java
index 9e2aaab84..0dafcee5b 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ScriptOperations.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ScriptOperations.java
@@ -29,7 +29,9 @@ import com.mongodb.DB;
* @author Christoph Strobl
* @author Oliver Gierke
* @since 1.7
+ * @deprecated since 2.2. The {@code eval} command has been removed without replacement in MongoDB Server 4.2.0.
*/
+@Deprecated
public interface ScriptOperations {
/**
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Aggregation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Aggregation.java
index dead6dae9..5cf5c852c 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Aggregation.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Aggregation.java
@@ -612,7 +612,7 @@ public class Aggregation {
}
/**
- * Creates a new {@link GeoNearOperation} instance from the given {@link NearQuery} and the{@code distanceField}. The
+ * Creates a new {@link GeoNearOperation} instance from the given {@link NearQuery} and the {@code distanceField}. The
* {@code distanceField} defines output field that contains the calculated distance.
*
* @param query must not be {@literal null}.
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOperation.java
index 4439d9023..932fbaf6b 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOperation.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOperation.java
@@ -15,6 +15,9 @@
*/
package org.springframework.data.mongodb.core.aggregation;
+import java.util.Collections;
+import java.util.List;
+
import org.bson.Document;
/**
@@ -32,7 +35,23 @@ public interface AggregationOperation {
* Turns the {@link AggregationOperation} into a {@link Document} by using the given
* {@link AggregationOperationContext}.
*
+ * @param context the {@link AggregationOperationContext} to operate within. Must not be {@literal null}.
* @return the Document
+ * @deprecated since 2.2 in favor of {@link #toPipelineStages(AggregationOperationContext)}.
*/
+ @Deprecated
Document toDocument(AggregationOperationContext context);
+
+ /**
+ * Turns the {@link AggregationOperation} into list of {@link Document stages} by using the given
+ * {@link AggregationOperationContext}. This allows a single {@link AggregationOptions} to add additional stages for
+ * eg. {@code $sort} or {@code $limit}.
+ *
+ * @param context the {@link AggregationOperationContext} to operate within. Must not be {@literal null}.
+ * @return the pipeline stages to run through. Never {@literal null}.
+ * @since 2.2
+ */
+ default List toPipelineStages(AggregationOperationContext context) {
+ return Collections.singletonList(toDocument(context));
+ }
}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOperationRenderer.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOperationRenderer.java
index 827496aa3..b77200682 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOperationRenderer.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOperationRenderer.java
@@ -52,7 +52,7 @@ class AggregationOperationRenderer {
for (AggregationOperation operation : operations) {
- operationDocuments.add(operation.toDocument(contextToUse));
+ operationDocuments.addAll(operation.toPipelineStages(contextToUse));
if (operation instanceof FieldsExposingAggregationOperation) {
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/GeoNearOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/GeoNearOperation.java
index cc3e574a1..6295cd45e 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/GeoNearOperation.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/GeoNearOperation.java
@@ -15,10 +15,15 @@
*/
package org.springframework.data.mongodb.core.aggregation;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
import org.bson.Document;
import org.springframework.data.mongodb.core.query.NearQuery;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
+import org.springframework.util.NumberUtils;
import org.springframework.util.StringUtils;
/**
@@ -89,6 +94,15 @@ public class GeoNearOperation implements AggregationOperation {
public Document toDocument(AggregationOperationContext context) {
Document command = context.getMappedObject(nearQuery.toDocument());
+
+ if(command.containsKey("query")) {
+ command.replace("query", context.getMappedObject(command.get("query", Document.class)));
+ }
+
+ if(command.containsKey("collation")) {
+ command.remove("collation");
+ }
+
command.put("distanceField", distanceField);
if (StringUtils.hasText(indexKey)) {
@@ -97,4 +111,28 @@ public class GeoNearOperation implements AggregationOperation {
return new Document("$geoNear", command);
}
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mongodb.core.aggregation.AggregationOperation#toPipelineStages(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
+ */
+ @Override
+ public List toPipelineStages(AggregationOperationContext context) {
+
+ Document command = toDocument(context);
+ Number limit = (Number) command.get("$geoNear", Document.class).remove("num");
+
+ List stages = new ArrayList<>();
+ stages.add(command);
+
+ if(nearQuery.getSkip() != null && nearQuery.getSkip() > 0){
+ stages.add(new Document("$skip", nearQuery.getSkip()));
+ }
+
+ if(limit != null) {
+ stages.add(new Document("$limit", limit.longValue()));
+ }
+
+ return stages;
+ }
}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/GraphLookupOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/GraphLookupOperation.java
index 0e49dfff9..b5fa88c6d 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/GraphLookupOperation.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/GraphLookupOperation.java
@@ -128,7 +128,13 @@ public class GraphLookupOperation implements InheritsFieldsAggregationOperation
*/
@Override
public ExposedFields getFields() {
- return ExposedFields.from(new ExposedField(as, true));
+
+ List fields = new ArrayList<>(2);
+ fields.add(new ExposedField(as, true));
+ if(depthField != null) {
+ fields.add(new ExposedField(depthField, true));
+ }
+ return ExposedFields.from(fields.toArray(new ExposedField[0]));
}
/**
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/TypeBasedAggregationOperationContext.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/TypeBasedAggregationOperationContext.java
index 51b58dca2..dc4207d82 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/TypeBasedAggregationOperationContext.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/TypeBasedAggregationOperationContext.java
@@ -79,8 +79,6 @@ public class TypeBasedAggregationOperationContext implements AggregationOperatio
*/
@Override
public FieldReference getReference(Field field) {
-
- PropertyPath.from(field.getTarget(), type);
return getReferenceFor(field);
}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapreduce/GroupBy.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapreduce/GroupBy.java
index c0046de0f..0520a14ea 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapreduce/GroupBy.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapreduce/GroupBy.java
@@ -29,7 +29,9 @@ import org.springframework.lang.Nullable;
* @author Mark Pollack
* @author Christoph Strobl
* @author Mark Paluch
+ * @deprecated since 2.2. The {@code group} command has been removed in MongoDB Server 4.2.0.
*/
+@Deprecated
public class GroupBy {
private @Nullable Document initialDocument;
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapreduce/GroupByResults.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapreduce/GroupByResults.java
index 8085c609c..e81a44f42 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapreduce/GroupByResults.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapreduce/GroupByResults.java
@@ -29,7 +29,9 @@ import org.springframework.util.Assert;
* @author Christoph Strobl
* @author Mark Paluch
* @param The class in which the results are mapped onto, accessible via an {@link Iterator}.
+ * @deprecated since 2.2. The {@code group} command has been removed in MongoDB Server 4.2.0.
*/
+@Deprecated
public class GroupByResults implements Iterable {
private final List mappedResults;
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Meta.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Meta.java
index 3740a7f05..1a5a73cf3 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Meta.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Meta.java
@@ -110,8 +110,10 @@ public class Meta {
/**
* @return {@literal null} if not set.
+ * @deprecated since 2.2. {@code $maxScan} has been removed without replacement in MongoDB 4.2.
*/
@Nullable
+ @Deprecated
public Long getMaxScan() {
return getValue(MetaKey.MAX_SCAN.key);
}
@@ -120,7 +122,7 @@ public class Meta {
* Only scan the specified number of documents.
*
* @param maxScan
- * @deprecated since 2.1 due to deprecation in MongoDB 4.0.
+ * @deprecated since 2.1. {@code $maxScan} has been removed without replacement in MongoDB 4.2.
*/
@Deprecated
public void setMaxScan(long maxScan) {
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/NearQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/NearQuery.java
index 318550c73..4a9a309c6 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/NearQuery.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/NearQuery.java
@@ -179,7 +179,7 @@ public final class NearQuery {
private @Nullable Distance minDistance;
private Metric metric;
private boolean spherical;
- private @Nullable Long num;
+ private @Nullable Long limit;
private @Nullable Long skip;
/**
@@ -269,9 +269,22 @@ public final class NearQuery {
*
* @param num
* @return
+ * @deprecated since 2.2. Please use {@link #limit(long)} instead.
*/
+ @Deprecated
public NearQuery num(long num) {
- this.num = num;
+ return limit(num);
+ }
+
+ /**
+ * Configures the maximum number of results to return.
+ *
+ * @param limit
+ * @return
+ * @since 2.2
+ */
+ public NearQuery limit(long limit) {
+ this.limit = limit;
return this;
}
@@ -296,8 +309,8 @@ public final class NearQuery {
Assert.notNull(pageable, "Pageable must not be 'null'.");
if (pageable.isPaged()) {
- this.num = pageable.getOffset() + pageable.getPageSize();
this.skip = pageable.getOffset();
+ this.limit = (long) pageable.getPageSize();
}
return this;
}
@@ -530,7 +543,7 @@ public final class NearQuery {
this.skip = query.getSkip();
if (query.getLimit() != 0) {
- this.num = (long) query.getLimit();
+ this.limit = (long) query.getLimit();
}
return this;
}
@@ -543,6 +556,17 @@ public final class NearQuery {
return skip;
}
+ /**
+ * Get the {@link Collation} to use along with the {@link #query(Query)}.
+ *
+ * @return the {@link Collation} if set. {@literal null} otherwise.
+ * @since 2.2
+ */
+ @Nullable
+ public Collation getCollation() {
+ return query != null ? query.getCollation().orElse(null) : null;
+ }
+
/**
* Returns the {@link Document} built by the {@link NearQuery}.
*
@@ -570,8 +594,8 @@ public final class NearQuery {
document.put("distanceMultiplier", getDistanceMultiplier());
}
- if (num != null) {
- document.put("num", num);
+ if (limit != null) {
+ document.put("num", limit);
}
if (usesGeoJson()) {
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/script/ExecutableMongoScript.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/script/ExecutableMongoScript.java
index b762ab09f..a0ecfe19b 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/script/ExecutableMongoScript.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/script/ExecutableMongoScript.java
@@ -23,7 +23,9 @@ import org.springframework.util.Assert;
* @author Christoph Strobl
* @author Oliver Gierke
* @since 1.7
+ * @deprecated since 2.2. The {@code eval} command has been removed without replacement in MongoDB Server 4.2.0.
*/
+@Deprecated
public class ExecutableMongoScript {
private final String code;
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/script/NamedMongoScript.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/script/NamedMongoScript.java
index 2a844b9cc..d02dd7966 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/script/NamedMongoScript.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/script/NamedMongoScript.java
@@ -25,7 +25,9 @@ import org.springframework.util.Assert;
* @author Christoph Strobl
* @author Oliver Gierke
* @since 1.7
+ * @deprecated since 2.2. The {@code eval} command has been removed without replacement in MongoDB Server 4.2.0.
*/
+@Deprecated
public class NamedMongoScript {
private final @Id String name;
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/monitor/OperationCounters.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/monitor/OperationCounters.java
index 556311f4b..86c005c63 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/monitor/OperationCounters.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/monitor/OperationCounters.java
@@ -19,6 +19,7 @@ import org.bson.Document;
import org.springframework.jmx.export.annotation.ManagedMetric;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.jmx.support.MetricType;
+import org.springframework.util.NumberUtils;
import com.mongodb.MongoClient;
@@ -66,6 +67,6 @@ public class OperationCounters extends AbstractMonitor {
private int getOpCounter(String key) {
Document opCounters = (Document) getServerStatus().get("opcounters");
- return (Integer) opCounters.get(key);
+ return NumberUtils.convertNumberToTargetClass((Number) opCounters.get(key), Integer.class);
}
}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/Meta.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/Meta.java
index dde664b16..704cf9aac 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/Meta.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/Meta.java
@@ -46,7 +46,9 @@ public @interface Meta {
* Only scan the specified number of documents.
*
* @return
+ * @deprecated since 2.2. {@code $maxScan} has been removed without replacement in MongoDB 4.2.
*/
+ @Deprecated
long maxScanDocuments() default -1;
/**
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/IndexEnsuringQueryCreationListener.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/IndexEnsuringQueryCreationListener.java
index 62753898d..8a33392bc 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/IndexEnsuringQueryCreationListener.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/IndexEnsuringQueryCreationListener.java
@@ -21,10 +21,10 @@ import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.domain.Sort.Order;
+import org.springframework.data.mongodb.UncategorizedMongoDbException;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.index.Index;
import org.springframework.data.mongodb.core.index.IndexOperationsProvider;
@@ -37,6 +37,8 @@ import org.springframework.data.repository.query.parser.Part.Type;
import org.springframework.data.repository.query.parser.PartTree;
import org.springframework.util.Assert;
+import com.mongodb.MongoException;
+
/**
* {@link QueryCreationListener} inspecting {@link PartTreeMongoQuery}s and creating an index for the properties it
* refers to.
@@ -104,7 +106,26 @@ class IndexEnsuringQueryCreationListener implements QueryCreationListener metadata = query.getQueryMethod().getEntityInformation();
- indexOperationsProvider.indexOps(metadata.getCollectionName()).ensureIndex(index);
+ try {
+ indexOperationsProvider.indexOps(metadata.getCollectionName()).ensureIndex(index);
+ } catch (UncategorizedMongoDbException e) {
+
+ if (e.getCause() instanceof MongoException) {
+
+ /*
+ * As of MongoDB 4.2 index creation raises an error when creating an index for the very same keys with
+ * different name, whereas previous versions silently ignored this.
+ * Because an index is by default named after the repository finder method it is not uncommon that an index
+ * for the very same property combination might already exist with a different name.
+ * So you see, that's why we need to ignore the error here.
+ *
+ * For details please see: https://docs.mongodb.com/master/release-notes/4.2-compatibility/#indexes
+ */
+ if (((MongoException) e.getCause()).getCode() != 85) {
+ throw e;
+ }
+ }
+ }
LOG.debug(String.format("Created %s!", index));
}
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultScriptOperationsTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultScriptOperationsTests.java
index f7b8490cc..b6af070e6 100644
--- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultScriptOperationsTests.java
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultScriptOperationsTests.java
@@ -23,6 +23,7 @@ import static org.springframework.data.mongodb.core.query.Query.*;
import org.bson.Document;
import org.junit.Before;
+import org.junit.ClassRule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
@@ -32,6 +33,8 @@ import org.springframework.dao.UncategorizedDataAccessException;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.script.ExecutableMongoScript;
import org.springframework.data.mongodb.core.script.NamedMongoScript;
+import org.springframework.data.mongodb.test.util.MongoVersionRule;
+import org.springframework.data.util.Version;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@@ -48,6 +51,8 @@ import com.mongodb.MongoClient;
@ContextConfiguration
public class DefaultScriptOperationsTests {
+ public static @ClassRule MongoVersionRule REQUIRES_AT_MOST_4_0 = MongoVersionRule.atMost(Version.parse("4.0.999"));
+
@Configuration
static class Config {
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java
index c067e90bc..f4e8f4cff 100644
--- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java
@@ -27,6 +27,7 @@ import static org.springframework.data.mongodb.core.query.Query.*;
import static org.springframework.data.mongodb.core.query.Update.*;
import com.mongodb.MongoClient;
+import com.mongodb.client.model.IndexOptions;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -45,6 +46,7 @@ import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.assertj.core.api.Assertions;
+import org.bson.Document;
import org.bson.types.ObjectId;
import org.hamcrest.collection.IsMapContaining;
import org.joda.time.DateTime;
@@ -415,16 +417,15 @@ public class MongoTemplateTests {
assertThat(field, is(IndexField.create("age", Direction.DESC)));
}
- @Test // DATAMONGO-746
+ @Test // DATAMONGO-746, DATAMONGO-2264
public void testReadIndexInfoForIndicesCreatedViaMongoShellCommands() throws Exception {
- String command = "db." + template.getCollectionName(Person.class)
- + ".createIndex({'age':-1}, {'unique':true, 'sparse':true}), 1";
template.indexOps(Person.class).dropAllIndexes();
assertThat(template.indexOps(Person.class).getIndexInfo().isEmpty(), is(true));
- factory.getDb().runCommand(new org.bson.Document("eval", command));
+ factory.getDb().getCollection(template.getCollectionName(Person.class))
+ .createIndex(new org.bson.Document("age", -1), new IndexOptions().name("age_-1").unique(true).sparse(true));
ListIndexesIterable indexInfo = template.getCollection(template.getCollectionName(Person.class))
.listIndexes();
@@ -443,7 +444,7 @@ public class MongoTemplateTests {
}
}
- assertThat(indexKey, IsMapContaining. hasEntry("age", -1D));
+ assertThat(indexKey, IsMapContaining. hasEntry("age", -1));
assertThat(unique, is(true));
IndexInfo info = template.indexOps(Person.class).getIndexInfo().get(1);
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnitTests.java
index f99db1bab..edddcd53d 100644
--- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnitTests.java
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnitTests.java
@@ -425,29 +425,24 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
verify(collection, never()).withReadPreference(any());
}
- @Test // DATAMONGO-1166
+ @Test // DATAMONGO-1166, DATAMONGO-2264
public void geoNearShouldHonorReadPreferenceWhenSet() {
- when(db.runCommand(any(org.bson.Document.class), any(ReadPreference.class), eq(Document.class)))
- .thenReturn(mock(Document.class));
template.setReadPreference(ReadPreference.secondary());
NearQuery query = NearQuery.near(new Point(1, 1));
template.geoNear(query, Wrapper.class);
- verify(this.db, times(1)).runCommand(any(org.bson.Document.class), eq(ReadPreference.secondary()),
- eq(Document.class));
+ verify(collection).withReadPreference(eq(ReadPreference.secondary()));
}
- @Test // DATAMONGO-1166
+ @Test // DATAMONGO-1166, DATAMONGO-2264
public void geoNearShouldIgnoreReadPreferenceWhenNotSet() {
- when(db.runCommand(any(Document.class), eq(Document.class))).thenReturn(mock(Document.class));
-
NearQuery query = NearQuery.near(new Point(1, 1));
template.geoNear(query, Wrapper.class);
- verify(this.db, times(1)).runCommand(any(Document.class), eq(Document.class));
+ verify(collection, never()).withReadPreference(any());
}
@Test // DATAMONGO-1334
@@ -886,16 +881,13 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
verify(mapReduceIterable, never()).databaseName(any());
}
- @Test // DATAMONGO-1518
+ @Test // DATAMONGO-1518, DATAMONGO-2264
public void geoNearShouldUseCollationWhenPresent() {
NearQuery query = NearQuery.near(0D, 0D).query(new BasicQuery("{}").collation(Collation.of("fr")));
template.geoNear(query, AutogenerateableId.class);
- ArgumentCaptor cmd = ArgumentCaptor.forClass(Document.class);
- verify(db).runCommand(cmd.capture(), any(Class.class));
-
- assertThat(cmd.getValue().get("collation", Document.class), equalTo(new Document("locale", "fr")));
+ verify(aggregateIterable).collation(eq(com.mongodb.client.model.Collation.builder().locale("fr").build()));
}
@Test // DATAMONGO-1518
@@ -980,38 +972,38 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
verify(findIterable).projection(eq(new Document()));
}
- @Test // DATAMONGO-1348
+ @Test // DATAMONGO-1348, DATAMONGO-2264
public void geoNearShouldMapQueryCorrectly() {
- when(db.runCommand(any(Document.class), eq(Document.class))).thenReturn(mock(Document.class));
-
NearQuery query = NearQuery.near(new Point(1, 1));
query.query(Query.query(Criteria.where("customName").is("rand al'thor")));
template.geoNear(query, WithNamedFields.class);
- ArgumentCaptor capture = ArgumentCaptor.forClass(Document.class);
- verify(this.db, times(1)).runCommand(capture.capture(), any(Class.class));
+ ArgumentCaptor> capture = ArgumentCaptor.forClass(List.class);
- assertThat(capture.getValue(), IsBsonObject.isBsonObject().containing("query.custom-named-field", "rand al'thor")
+ verify(collection).aggregate(capture.capture(), eq(Document.class));
+ Document $geoNear = capture.getValue().iterator().next();
+
+ assertThat($geoNear, IsBsonObject.isBsonObject().containing("$geoNear.query.custom-named-field", "rand al'thor")
.notContaining("query.customName"));
}
- @Test // DATAMONGO-1348
+ @Test // DATAMONGO-1348, DATAMONGO-2264
public void geoNearShouldMapGeoJsonPointCorrectly() {
- when(db.runCommand(any(Document.class), eq(Document.class))).thenReturn(mock(Document.class));
-
NearQuery query = NearQuery.near(new GeoJsonPoint(1, 2));
query.query(Query.query(Criteria.where("customName").is("rand al'thor")));
template.geoNear(query, WithNamedFields.class);
- ArgumentCaptor capture = ArgumentCaptor.forClass(Document.class);
- verify(this.db, times(1)).runCommand(capture.capture(), any(Class.class));
+ ArgumentCaptor> capture = ArgumentCaptor.forClass(List.class);
- assertThat(capture.getValue(), IsBsonObject.isBsonObject().containing("near.type", "Point")
- .containing("near.coordinates.[0]", 1D).containing("near.coordinates.[1]", 2D));
+ verify(collection).aggregate(capture.capture(), eq(Document.class));
+ Document $geoNear = capture.getValue().iterator().next();
+
+ assertThat($geoNear, IsBsonObject.isBsonObject().containing("$geoNear.near.type", "Point")
+ .containing("$geoNear.near.coordinates.[0]", 1D).containing("$geoNear.near.coordinates.[1]", 2D));
}
@Test // DATAMONGO-2155
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateIndexTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateIndexTests.java
index 8b7ed9fd8..e7cebb108 100644
--- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateIndexTests.java
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateIndexTests.java
@@ -44,6 +44,7 @@ import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import com.mongodb.client.model.IndexOptions;
import com.mongodb.reactivestreams.client.ListIndexesPublisher;
import com.mongodb.reactivestreams.client.MongoClient;
import com.mongodb.reactivestreams.client.Success;
@@ -140,12 +141,9 @@ public class ReactiveMongoTemplateIndexTests {
}).verifyComplete();
}
- @Test // DATAMONGO-1444
+ @Test // DATAMONGO-1444, DATAMONGO-2264
public void testReadIndexInfoForIndicesCreatedViaMongoShellCommands() {
- String command = "db." + template.getCollectionName(Person.class)
- + ".createIndex({'age':-1}, {'unique':true, 'sparse':true}), 1";
-
template.indexOps(Person.class).dropAllIndexes() //
.as(StepVerifier::create) //
.verifyComplete();
@@ -154,7 +152,8 @@ public class ReactiveMongoTemplateIndexTests {
.as(StepVerifier::create) //
.verifyComplete();
- Flux.from(factory.getMongoDatabase().runCommand(new org.bson.Document("eval", command))) //
+ Flux.from(factory.getMongoDatabase().getCollection(template.getCollectionName(Person.class))
+ .createIndex(new Document("age", -1), new IndexOptions().unique(true).sparse(true))) //
.as(StepVerifier::create) //
.expectNextCount(1) //
.verifyComplete();
@@ -177,7 +176,7 @@ public class ReactiveMongoTemplateIndexTests {
}
}
- assertThat(indexKey).containsEntry("age", -1D);
+ assertThat(indexKey).containsEntry("age", -1);
assertThat(unique).isTrue();
}).verifyComplete();
@@ -207,12 +206,11 @@ public class ReactiveMongoTemplateIndexTests {
.verifyComplete();
}
- @Test // DATAMONGO-1928
+ @Test // DATAMONGO-1928, DATAMONGO-2264
public void indexCreationShouldFail() throws InterruptedException {
- String command = "db.indexfail" + ".createIndex({'field':1}, {'name':'foo', 'unique':true, 'sparse':true}), 1";
-
- Flux.from(factory.getMongoDatabase().runCommand(new org.bson.Document("eval", command))) //
+ Flux.from(factory.getMongoDatabase().getCollection("indexfail") //
+ .createIndex(new Document("field", 1), new IndexOptions().name("foo").unique(true).sparse(true)))
.as(StepVerifier::create) //
.expectNextCount(1) //
.verifyComplete();
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateUnitTests.java
index 0acb22832..a015c1db8 100644
--- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateUnitTests.java
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateUnitTests.java
@@ -40,7 +40,6 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.reactivestreams.Publisher;
-
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.MongoTemplateUnitTests.AutogenerateableId;
@@ -299,16 +298,13 @@ public class ReactiveMongoTemplateUnitTests {
verify(mapReducePublisher).collation(eq(com.mongodb.client.model.Collation.builder().locale("fr").build()));
}
- @Test // DATAMONGO-1518
+ @Test // DATAMONGO-1518, DATAMONGO-2264
public void geoNearShouldUseCollationWhenPresent() {
NearQuery query = NearQuery.near(0D, 0D).query(new BasicQuery("{}").collation(Collation.of("fr")));
template.geoNear(query, AutogenerateableId.class).subscribe();
- ArgumentCaptor cmd = ArgumentCaptor.forClass(Document.class);
- verify(db).runCommand(cmd.capture(), any(Class.class));
-
- assertThat(cmd.getValue().get("collation", Document.class), equalTo(new Document("locale", "fr")));
+ verify(aggregatePublisher).collation(eq(com.mongodb.client.model.Collation.builder().locale("fr").build()));
}
@Test // DATAMONGO-1719
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveSessionBoundMongoTemplateUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveSessionBoundMongoTemplateUnitTests.java
index 9f4d03308..b0233611f 100644
--- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveSessionBoundMongoTemplateUnitTests.java
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveSessionBoundMongoTemplateUnitTests.java
@@ -30,7 +30,6 @@ import org.bson.Document;
import org.bson.codecs.BsonValueCodec;
import org.bson.codecs.configuration.CodecRegistry;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -257,21 +256,12 @@ public class ReactiveSessionBoundMongoTemplateUnitTests {
verify(collection).distinct(eq(clientSession), anyString(), any(), any());
}
- @Test // DATAMONGO-1880
+ @Test // DATAMONGO-1880, DATAMONGO-2264
public void geoNearShouldUseProxiedDatabase() {
template.geoNear(NearQuery.near(new Point(0, 0), Metrics.NEUTRAL), Person.class).subscribe();
- verify(database).runCommand(eq(clientSession), any(), eq(Document.class));
- }
-
- @Test // DATAMONGO-1880, DATAMONGO-1889
- @Ignore("No group by yet - DATAMONGO-1889")
- public void groupShouldUseProxiedDatabase() {
-
- // template.group(COLLECTION_NAME, GroupBy.key("firstName"), Person.class).subscribe();
-
- verify(database).runCommand(eq(clientSession), any(), eq(Document.class));
+ verify(collection).aggregate(eq(clientSession), anyList(), eq(Document.class));
}
@Test // DATAMONGO-1880, DATAMONGO-1890, DATAMONGO-257
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/SessionBoundMongoTemplateUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/SessionBoundMongoTemplateUnitTests.java
index 38ac9bd40..ef8b23dc6 100644
--- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/SessionBoundMongoTemplateUnitTests.java
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/SessionBoundMongoTemplateUnitTests.java
@@ -258,14 +258,14 @@ public class SessionBoundMongoTemplateUnitTests {
verify(collection).distinct(eq(clientSession), anyString(), any(), any());
}
- @Test // DATAMONGO-1880
+ @Test // DATAMONGO-1880, DATAMONGO-2264
public void geoNearShouldUseProxiedDatabase() {
when(database.runCommand(any(ClientSession.class), any(), eq(Document.class)))
.thenReturn(new Document("results", Collections.emptyList()));
template.geoNear(NearQuery.near(new Point(0, 0), Metrics.NEUTRAL), Person.class);
- verify(database).runCommand(eq(clientSession), any(), eq(Document.class));
+ verify(collection).aggregate(eq(clientSession), anyList(), eq(Document.class));
}
@Test // DATAMONGO-1880
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java
index 37056d788..7e481e511 100644
--- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java
@@ -52,11 +52,11 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.dao.DataAccessException;
import org.springframework.data.annotation.Id;
+import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.geo.Box;
import org.springframework.data.geo.Metrics;
import org.springframework.data.geo.Point;
-import org.springframework.data.mapping.MappingException;
import org.springframework.data.mongodb.core.CollectionCallback;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.TestEntities;
@@ -779,8 +779,9 @@ public class AggregationTests {
group("make").avg(ConditionalOperators //
.when(Criteria.where("year").gte(2012)) //
.then(1) //
- .otherwise(9000)).as("score"),
- sort(ASC, "make"));
+ .otherwise(9000)) //
+ .as("score"),
+ sort(ASC, "score"));
AggregationResults result = mongoTemplate.aggregate(agg, Document.class);
@@ -795,7 +796,7 @@ public class AggregationTests {
assertThat(((Number) good.get("score")).longValue()).isEqualTo(9000L);
}
- @Test // DATAMONGO-1784
+ @Test // DATAMONGO-1784, DATAMONGO-2264
public void shouldAllowSumUsingConditionalExpressions() {
mongoTemplate.dropCollection(CarPerson.class);
@@ -823,7 +824,7 @@ public class AggregationTests {
.when(Criteria.where("year").gte(2012)) //
.then(1) //
.otherwise(9000)).as("score"),
- sort(ASC, "make"));
+ sort(ASC, "score"));
AggregationResults result = mongoTemplate.aggregate(agg, Document.class);
@@ -995,23 +996,6 @@ public class AggregationTests {
.isEqualTo((product.netPrice * (1 - product.discountRate) + shippingCosts) * (1 + product.taxRate));
}
- @Test
- public void shouldThrowExceptionIfUnknownFieldIsReferencedInArithmenticExpressionsInProjection() {
-
- exception.expect(MappingException.class);
- exception.expectMessage("unknown");
-
- Product product = new Product("P1", "A", 1.99, 3, 0.05, 0.19);
- mongoTemplate.insert(product);
-
- TypedAggregation agg = newAggregation(Product.class, //
- project("name", "netPrice") //
- .andExpression("unknown + 1").as("netPricePlus1") //
- );
-
- mongoTemplate.aggregate(agg, Document.class);
- }
-
/**
* @see Spring
@@ -1027,18 +1011,19 @@ public class AggregationTests {
unwind("pd"), //
group("pd.pDch") // the nested field expression
.sum("pd.up").as("uplift"), //
- project("_id", "uplift"));
+ project("_id", "uplift"), //
+ sort(Sort.by("uplift")));
AggregationResults result = mongoTemplate.aggregate(agg, Document.class);
List stats = result.getMappedResults();
assertThat(stats.size()).isEqualTo(3);
- assertThat(stats.get(0).get("_id").toString()).isEqualTo("C");
- assertThat((Integer) stats.get(0).get("uplift")).isEqualTo(2);
- assertThat(stats.get(1).get("_id").toString()).isEqualTo("B");
- assertThat((Integer) stats.get(1).get("uplift")).isEqualTo(3);
- assertThat(stats.get(2).get("_id").toString()).isEqualTo("A");
- assertThat((Integer) stats.get(2).get("uplift")).isEqualTo(1);
+ assertThat(stats.get(0).get("_id").toString()).isEqualTo("A");
+ assertThat((Integer) stats.get(0).get("uplift")).isEqualTo(1);
+ assertThat(stats.get(1).get("_id").toString()).isEqualTo("C");
+ assertThat((Integer) stats.get(1).get("uplift")).isEqualTo(2);
+ assertThat(stats.get(2).get("_id").toString()).isEqualTo("B");
+ assertThat((Integer) stats.get(2).get("uplift")).isEqualTo(3);
}
/**
@@ -1151,7 +1136,7 @@ public class AggregationTests {
assertThat((Integer) resultDocument.keySet().size()).isEqualTo(1);
}
- @Test // DATAMONGO-788
+ @Test // DATAMONGO-788, DATAMONGO-2264
public void referencesToGroupIdsShouldBeRenderedProperly() {
mongoTemplate.insert(new DATAMONGO788(1, 1));
@@ -1165,7 +1150,7 @@ public class AggregationTests {
AggregationOperation project = Aggregation.project("xPerY", "x", "y").andExclude("_id");
TypedAggregation aggregation = Aggregation.newAggregation(DATAMONGO788.class, projectFirst, group,
- project);
+ project, Aggregation.sort(Sort.by("xPerY")));
AggregationResults aggResults = mongoTemplate.aggregate(aggregation, Document.class);
List items = aggResults.getMappedResults();
@@ -1347,7 +1332,7 @@ public class AggregationTests {
assertThat(rawResult.containsKey("stages")).isEqualTo(true);
}
- @Test // DATAMONGO-954
+ @Test // DATAMONGO-954, DATAMONGO-2264
@MongoVersion(asOf = "2.6")
public void shouldSupportReturningCurrentAggregationRoot() {
@@ -1359,11 +1344,11 @@ public class AggregationTests {
List personsWithAge25 = mongoTemplate.find(Query.query(where("age").is(25)), Document.class,
mongoTemplate.getCollectionName(Person.class));
- Aggregation agg = newAggregation(group("age").push(Aggregation.ROOT).as("users"));
+ Aggregation agg = newAggregation(group("age").push(Aggregation.ROOT).as("users"), sort(Sort.by("_id")));
AggregationResults result = mongoTemplate.aggregate(agg, Person.class, Document.class);
assertThat(result.getMappedResults()).hasSize(3);
- Document o = result.getMappedResults().get(2);
+ Document o = result.getMappedResults().get(1);
assertThat(o.get("_id")).isEqualTo((Object) 25);
assertThat((List>) o.get("users")).hasSize(2);
@@ -1373,7 +1358,7 @@ public class AggregationTests {
/**
* {@link https://stackoverflow.com/questions/24185987/using-root-inside-spring-data-mongodb-for-retrieving-whole-document}
*/
- @Test // DATAMONGO-954
+ @Test // DATAMONGO-954, DATAMONGO-2264
@MongoVersion(asOf = "2.6")
public void shouldSupportReturningCurrentAggregationRootInReference() {
@@ -1771,7 +1756,7 @@ public class AggregationTests {
new Document("_id", "2").append("finalTotal", 10.25D));
}
- @Test // DATAMONGO-1551
+ @Test // DATAMONGO-1551, DATAMONGO-2264
@MongoVersion(asOf = "3.4")
public void graphLookupShouldBeAppliedCorrectly() {
@@ -1789,16 +1774,18 @@ public class AggregationTests {
.connectTo("name") //
.depthField("depth") //
.maxDepth(5) //
- .as("reportingHierarchy"));
+ .as("reportingHierarchy"), //
+ project("id", "depth", "name", "reportsTo", "reportingHierarchy"));
AggregationResults result = mongoTemplate.aggregate(agg, Document.class);
Document object = result.getUniqueMappedResult();
List list = (List) object.get("reportingHierarchy");
- Assert.assertThat(object, isBsonObject().containing("reportingHierarchy", List.class));
- assertThat((Document) list.get(0)).containsEntry("name", "Dev").containsEntry("depth", 1L);
- assertThat((Document) list.get(1)).containsEntry("name", "Eliot").containsEntry("depth", 0L);
+ assertThat(object).containsEntry("name", "Andrew").containsEntry("reportsTo", "Eliot");
+ assertThat(list).containsOnly(
+ new Document("_id", 2).append("name", "Eliot").append("reportsTo", "Dev").append("depth", 0L).append("_class", Employee.class.getName()),
+ new Document("_id", 1).append("name", "Dev").append("depth", 1L).append("_class", Employee.class.getName()));
}
@Test // DATAMONGO-1552
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/GeoNearOperationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/GeoNearOperationUnitTests.java
index 08e7a4760..094b3279c 100644
--- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/GeoNearOperationUnitTests.java
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/GeoNearOperationUnitTests.java
@@ -17,10 +17,23 @@ package org.springframework.data.mongodb.core.aggregation;
import static org.assertj.core.api.Assertions.*;
+import java.util.Arrays;
+
import org.bson.Document;
import org.junit.Test;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.geo.Distance;
import org.springframework.data.mongodb.core.DocumentTestUtils;
+import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
+import org.springframework.data.mongodb.core.convert.MongoConverter;
+import org.springframework.data.mongodb.core.convert.NoOpDbRefResolver;
+import org.springframework.data.mongodb.core.convert.QueryMapper;
+import org.springframework.data.mongodb.core.mapping.Field;
+import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
+import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.NearQuery;
+import org.springframework.data.mongodb.core.query.Query;
+import org.springframework.lang.Nullable;
/**
* Unit tests for {@link GeoNearOperation}.
@@ -53,4 +66,201 @@ public class GeoNearOperationUnitTests {
assertThat(DocumentTestUtils.getAsDocument(document, "$geoNear")).containsEntry("key", "geo-index-1");
}
+
+ @Test // DATAMONGO-2264
+ public void rendersMaxDistanceCorrectly() {
+
+ NearQuery query = NearQuery.near(10.0, 20.0).maxDistance(new Distance(30.0));
+
+ assertThat(new GeoNearOperation(query, "distance").toPipelineStages(Aggregation.DEFAULT_CONTEXT))
+ .containsExactly($geoNear().near(10.0, 20.0).maxDistance(30.0).doc());
+ }
+
+ @Test // DATAMONGO-2264
+ public void rendersMinDistanceCorrectly() {
+
+ NearQuery query = NearQuery.near(10.0, 20.0).minDistance(new Distance(30.0));
+
+ assertThat(new GeoNearOperation(query, "distance").toPipelineStages(Aggregation.DEFAULT_CONTEXT))
+ .containsExactly($geoNear().near(10.0, 20.0).minDistance(30.0).doc());
+ }
+
+ @Test // DATAMONGO-2264
+ public void rendersSphericalCorrectly() {
+
+ NearQuery query = NearQuery.near(10.0, 20.0).spherical(true);
+
+ assertThat(new GeoNearOperation(query, "distance").toPipelineStages(Aggregation.DEFAULT_CONTEXT))
+ .containsExactly($geoNear().near(10.0, 20.0).spherical(true).doc());
+ }
+
+ @Test // DATAMONGO-2264
+ public void rendersDistanceMultiplier() {
+
+ NearQuery query = NearQuery.near(10.0, 20.0).inKilometers();
+
+ assertThat(new GeoNearOperation(query, "distance").toPipelineStages(Aggregation.DEFAULT_CONTEXT))
+ .containsExactly($geoNear().near(10.0, 20.0).spherical(true).distanceMultiplier(6378.137).doc());
+ }
+
+ @Test // DATAMONGO-2264
+ public void rendersIndexKey() {
+
+ NearQuery query = NearQuery.near(10.0, 20.0);
+
+ assertThat(new GeoNearOperation(query, "distance").useIndex("index-1").toPipelineStages(Aggregation.DEFAULT_CONTEXT))
+ .containsExactly($geoNear().near(10.0, 20.0).key("index-1").doc());
+ }
+
+ @Test // DATAMONGO-2264
+ public void rendersQuery() {
+
+ NearQuery query = NearQuery.near(10.0, 20.0).query(Query.query(Criteria.where("city").is("Austin")));
+
+ assertThat(new GeoNearOperation(query, "distance").toPipelineStages(Aggregation.DEFAULT_CONTEXT))
+ .containsExactly($geoNear().near(10.0, 20.0).query(new Document("city", "Austin")).doc());
+ }
+
+ @Test // DATAMONGO-2264
+ public void rendersMappedQuery() {
+
+ NearQuery query = NearQuery.near(10.0, 20.0).query(Query.query(Criteria.where("city").is("Austin")));
+
+ assertThat(
+ new GeoNearOperation(query, "distance").toPipelineStages(typedAggregationOperationContext(GeoDocument.class)))
+ .containsExactly($geoNear().near(10.0, 20.0).query(new Document("ci-ty", "Austin")).doc());
+ }
+
+ @Test // DATAMONGO-2264
+ public void appliesSkipFromNearQuery() {
+
+ NearQuery query = NearQuery.near(10.0, 20.0).skip(10L);
+
+ assertThat(new GeoNearOperation(query, "distance").toPipelineStages(Aggregation.DEFAULT_CONTEXT))
+ .containsExactly($geoNear().near(10.0, 20.0).doc(), new Document("$skip", 10L));
+ }
+
+ @Test // DATAMONGO-2264
+ public void appliesLimitFromNearQuery() {
+
+ NearQuery query = NearQuery.near(10.0, 20.0).limit(10L);
+
+ assertThat(new GeoNearOperation(query, "distance").toPipelineStages(Aggregation.DEFAULT_CONTEXT))
+ .containsExactly($geoNear().near(10.0, 20.0).doc(), new Document("$limit", 10L));
+ }
+
+ @Test // DATAMONGO-2264
+ public void appliesSkipAndLimitInOrder() {
+
+ NearQuery query = NearQuery.near(10.0, 20.0).limit(10L).skip(3L);
+
+ assertThat(new GeoNearOperation(query, "distance").toPipelineStages(Aggregation.DEFAULT_CONTEXT))
+ .containsExactly($geoNear().near(10.0, 20.0).doc(), new Document("$skip", 3L), new Document("$limit", 10L));
+ }
+
+ private TypeBasedAggregationOperationContext typedAggregationOperationContext(Class> type) {
+
+ MongoMappingContext mappingContext = new MongoMappingContext();
+ MongoConverter converter = new MappingMongoConverter(NoOpDbRefResolver.INSTANCE, mappingContext);
+ return new TypeBasedAggregationOperationContext(type, mappingContext, new QueryMapper(converter));
+ }
+
+ GeoNearDocumentBuilder $geoNear() {
+ return new GeoNearDocumentBuilder();
+ }
+
+ static class GeoDocument {
+
+ @Id String id;
+ @Field("ci-ty") String city;
+ }
+
+ static class GeoNearDocumentBuilder {
+
+ Document target = new Document("distanceField", "distance").append("distanceMultiplier", 1.0D).append("spherical",
+ false);
+
+ GeoNearDocumentBuilder maxDistance(@Nullable Number value) {
+
+ if (value != null) {
+ target.put("maxDistance", value);
+ } else {
+ target.remove("maxDistance");
+ }
+ return this;
+ }
+
+ GeoNearDocumentBuilder minDistance(@Nullable Number value) {
+
+ if (value != null) {
+ target.put("minDistance", value);
+ } else {
+ target.remove("minDistance");
+ }
+ return this;
+ }
+
+ GeoNearDocumentBuilder near(Number... coordinates) {
+
+ target.put("near", Arrays.asList(coordinates));
+ return this;
+ }
+
+ GeoNearDocumentBuilder spherical(@Nullable Boolean value) {
+
+ if (value != null) {
+ target.put("spherical", value);
+ } else {
+ target.remove("spherical");
+ }
+ return this;
+ }
+
+ GeoNearDocumentBuilder distanceField(@Nullable String value) {
+
+ if (value != null) {
+ target.put("distanceField", value);
+ } else {
+ target.remove("distanceField");
+ }
+ return this;
+ }
+
+ GeoNearDocumentBuilder distanceMultiplier(Number value) {
+
+ if (value != null) {
+ target.put("distanceMultiplier", value);
+ } else {
+ target.remove("distanceMultiplier");
+ }
+ return this;
+ }
+
+ GeoNearDocumentBuilder key(String value) {
+
+ if (value != null) {
+ target.put("key", value);
+ } else {
+ target.remove("key");
+ }
+ return this;
+ }
+
+ GeoNearDocumentBuilder query(Document value) {
+
+ if (value != null) {
+ target.put("query", value);
+ } else {
+ target.remove("query");
+ }
+ return this;
+ }
+
+ Document doc() {
+ return new Document("$geoNear", new Document(target));
+ }
+
+ }
+
+ // TODO: we need to test this to the full extend
}
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoJsonTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoJsonTests.java
index a31fdf450..b2e780fc2 100644
--- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoJsonTests.java
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoJsonTests.java
@@ -24,11 +24,11 @@ import lombok.Data;
import java.util.Arrays;
import java.util.List;
+import org.assertj.core.data.Percentage;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.dao.DataAccessException;
@@ -95,7 +95,7 @@ public class GeoJsonTests {
removeCollections();
}
- @Test // DATAMONGO-1135
+ @Test // DATAMONGO-1135, DATAMONGO-2264
public void geoNear() {
NearQuery geoNear = NearQuery.near(new GeoJsonPoint(-73, 40), Metrics.KILOMETERS).num(10).maxDistance(150);
@@ -104,6 +104,42 @@ public class GeoJsonTests {
assertThat(result.getContent()).isNotEmpty();
assertThat(result.getAverageDistance().getMetric()).isEqualTo(Metrics.KILOMETERS);
+ assertThat(result.getAverageDistance().getValue()).isCloseTo(117.84629457941556, Percentage.withPercentage(0.001));
+ }
+
+ @Test // DATAMONGO-2264
+ public void geoNearShouldNotOverridePropertyWithDefaultNameForCalculatedDistance/* namely "dis" */() {
+
+ NearQuery geoNear = NearQuery.near(new GeoJsonPoint(-73, 40), Metrics.KILOMETERS).num(10).maxDistance(150);
+
+ GeoResults result = template.geoNear(geoNear, VenueWithDistanceField.class);
+
+ assertThat(result.getContent()).isNotEmpty();
+ assertThat(result.getAverageDistance().getMetric()).isEqualTo(Metrics.KILOMETERS);
+ assertThat(result.getAverageDistance().getValue()).isCloseTo(117.84629457941556, Percentage.withPercentage(0.001));
+ result.getContent().forEach(it -> {
+
+ assertThat(it.getDistance().getValue()).isNotZero();
+ assertThat(it.getContent().getDis()).isNull();
+ });
+ }
+
+ @Test // DATAMONGO-2264
+ public void geoNearShouldAllowToReadBackCalculatedDistanceIntoTargetTypeProperty/* namely "dis" */() {
+
+ NearQuery geoNear = NearQuery.near(new GeoJsonPoint(-73, 40), Metrics.KILOMETERS).num(10).maxDistance(150);
+
+ GeoResults result = template.geoNear(geoNear, Venue2DSphere.class,
+ template.getCollectionName(Venue2DSphere.class), VenueWithDistanceField.class);
+
+ assertThat(result.getContent()).isNotEmpty();
+ assertThat(result.getAverageDistance().getMetric()).isEqualTo(Metrics.KILOMETERS);
+ assertThat(result.getAverageDistance().getValue()).isCloseTo(117.84629457941556, Percentage.withPercentage(0.001));
+ result.getContent().forEach(it -> {
+
+ assertThat(it.getDistance().getValue()).isNotZero();
+ assertThat(it.getContent().getDis()).isEqualTo(it.getDistance().getValue());
+ });
}
@Test // DATAMONGO-1148
@@ -124,8 +160,7 @@ public class GeoJsonTests {
@Test // DATAMONGO-1348
public void geoNearShouldReturnDistanceCorrectly/*which is using the meters*/() {
- NearQuery geoNear = NearQuery.near(new Point(-73.99171, 40.738868), Metrics.KILOMETERS).num(10)
- .maxDistance(0.4);
+ NearQuery geoNear = NearQuery.near(new Point(-73.99171, 40.738868), Metrics.KILOMETERS).num(10).maxDistance(0.4);
GeoResults result = template.geoNear(geoNear, Venue2DSphere.class);
@@ -469,6 +504,23 @@ public class GeoJsonTests {
}
}
+ static class VenueWithDistanceField extends Venue2DSphere {
+
+ private Double dis; // geoNear command default distance field name
+
+ public VenueWithDistanceField(String name, double[] location) {
+ super(name, location);
+ }
+
+ public Double getDis() {
+ return dis;
+ }
+
+ public void setDis(Double dis) {
+ this.dis = dis;
+ }
+ }
+
static class DocumentWithPropertyUsingGeoJsonType {
String id;
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapreduce/GroupByTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapreduce/GroupByTests.java
index 462be4768..038a3245e 100644
--- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapreduce/GroupByTests.java
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapreduce/GroupByTests.java
@@ -24,10 +24,13 @@ import org.bson.Document;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
+import org.junit.ClassRule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
+import org.springframework.data.mongodb.test.util.MongoVersionRule;
+import org.springframework.data.util.Version;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@@ -44,6 +47,8 @@ import com.mongodb.client.MongoCollection;
@ContextConfiguration("classpath:infrastructure.xml")
public class GroupByTests {
+ public static @ClassRule MongoVersionRule REQUIRES_AT_MOST_4_0 = MongoVersionRule.atMost(Version.parse("4.0.999"));
+
@Autowired MongoTemplate mongoTemplate;
@Before
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/NearQueryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/NearQueryUnitTests.java
index e45f858cc..49b8f2b48 100644
--- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/NearQueryUnitTests.java
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/NearQueryUnitTests.java
@@ -89,15 +89,14 @@ public class NearQueryUnitTests {
assertThat(query.getMetric(), is((Metric) Metrics.MILES));
}
- @Test // DATAMONGO-445
+ @Test // DATAMONGO-445, DATAMONGO-2264
public void shouldTakeSkipAndLimitSettingsFromGivenPageable() {
Pageable pageable = PageRequest.of(3, 5);
NearQuery query = NearQuery.near(new Point(1, 1)).with(pageable);
assertThat(query.getSkip(), is((long) pageable.getPageNumber() * pageable.getPageSize()));
- assertThat((Long) query.toDocument().get("num"),
- is((long) (pageable.getPageNumber() + 1) * pageable.getPageSize()));
+ assertThat(query.toDocument().get("num"), is((long) pageable.getPageSize()));
}
@Test // DATAMONGO-445
@@ -112,7 +111,7 @@ public class NearQueryUnitTests {
assertThat((Long) query.toDocument().get("num"), is((long) limit));
}
- @Test // DATAMONGO-445
+ @Test // DATAMONGO-445, DATAMONGO-2264
public void shouldTakeSkipAndLimitSettingsFromPageableEvenIfItWasSpecifiedOnQuery() {
int limit = 10;
@@ -122,8 +121,7 @@ public class NearQueryUnitTests {
.query(Query.query(Criteria.where("foo").is("bar")).limit(limit).skip(skip)).with(pageable);
assertThat(query.getSkip(), is((long) pageable.getPageNumber() * pageable.getPageSize()));
- assertThat((Long) query.toDocument().get("num"),
- is((long) (pageable.getPageNumber() + 1) * pageable.getPageSize()));
+ assertThat(query.toDocument().get("num"), is((long) pageable.getPageSize()));
}
@Test // DATAMONGO-829
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/ComplexIdRepositoryIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/ComplexIdRepositoryIntegrationTests.java
index 8b92abd59..135cc7e17 100644
--- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/ComplexIdRepositoryIntegrationTests.java
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/ComplexIdRepositoryIntegrationTests.java
@@ -25,6 +25,7 @@ import java.util.Optional;
import org.hamcrest.Matchers;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
@@ -32,6 +33,8 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.config.AbstractMongoConfiguration;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
+import org.springframework.data.mongodb.test.util.MongoVersion;
+import org.springframework.data.mongodb.test.util.MongoVersionRule;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@@ -46,6 +49,8 @@ import com.mongodb.MongoClient;
@ContextConfiguration
public class ComplexIdRepositoryIntegrationTests {
+ public @Rule MongoVersionRule mongoVersionRule = MongoVersionRule.any();
+
@Configuration
@EnableMongoRepositories
static class Config extends AbstractMongoConfiguration {
@@ -129,6 +134,7 @@ public class ComplexIdRepositoryIntegrationTests {
}
@Test // DATAMONGO-1373
+ @MongoVersion(until = "4.0.999")
public void composedAnnotationFindMetaShouldWorkWhenUsingComplexId() {
repo.save(userWithId);
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/ReactiveQuerydslMongoPredicateExecutorTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/ReactiveQuerydslMongoPredicateExecutorTests.java
index c619982a6..47ca7c549 100644
--- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/ReactiveQuerydslMongoPredicateExecutorTests.java
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/ReactiveQuerydslMongoPredicateExecutorTests.java
@@ -15,12 +15,14 @@
*/
package org.springframework.data.mongodb.repository.support;
+import org.springframework.data.mongodb.core.query.BasicQuery;
import reactor.core.publisher.Flux;
import reactor.test.StepVerifier;
import java.util.Arrays;
import java.util.LinkedHashSet;
+import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
@@ -100,19 +102,20 @@ public class ReactiveQuerydslMongoPredicateExecutorTests {
MongoEntityInformation entityInformation = factory.getEntityInformation(Person.class);
repository = new ReactiveQuerydslMongoPredicateExecutor<>(entityInformation, operations);
- operations.dropCollection(Person.class) //
- .as(StepVerifier::create) //
- .verifyComplete();
-
dave = new Person("Dave", "Matthews", 42);
oliver = new Person("Oliver August", "Matthews", 4);
carter = new Person("Carter", "Beauford", 49);
person = new QPerson("person");
- operations.insertAll(Arrays.asList(oliver, dave, carter)).as(StepVerifier::create) //
- .expectNextCount(3) //
- .verifyComplete();
+ Flux.merge(operations.insert(oliver), operations.insert(dave), operations.insert(carter)).then() //
+ .as(StepVerifier::create).verifyComplete();
+ }
+
+ @After
+ public void tearDown() {
+ operations.remove(new BasicQuery("{}"), "person").then().as(StepVerifier::create).verifyComplete();
+ operations.remove(new BasicQuery("{}"), "uer").then().as(StepVerifier::create).verifyComplete();
}
@Test // DATAMONGO-2182
@@ -200,10 +203,10 @@ public class ReactiveQuerydslMongoPredicateExecutorTests {
User user3 = new User();
user3.setUsername("user-3");
- operations.insertAll(Arrays.asList(user1, user2, user3)) //
+ Flux.merge(operations.save(user1), operations.save(user2), operations.save(user3)) //
+ .then() //
.as(StepVerifier::create) //
- .expectNextCount(3) //
- .verifyComplete();
+ .verifyComplete(); //
Person person1 = new Person("Max", "The Mighty");
person1.setCoworker(user1);
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/MongoTestUtils.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/MongoTestUtils.java
index f489b7402..9c28be7e8 100644
--- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/MongoTestUtils.java
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/MongoTestUtils.java
@@ -18,6 +18,8 @@ package org.springframework.data.mongodb.test.util;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
+import java.time.Duration;
+
import org.bson.Document;
import com.mongodb.MongoClientURI;
@@ -78,8 +80,9 @@ public class MongoTestUtils {
com.mongodb.reactivestreams.client.MongoDatabase database = client.getDatabase(dbName)
.withWriteConcern(WriteConcern.MAJORITY).withReadPreference(ReadPreference.primary());
- return Mono.from(database.getCollection(collectionName).drop())
- .then(Mono.from(database.createCollection(collectionName)));
+ return Mono.from(database.getCollection(collectionName).drop()) //
+ .then(Mono.from(database.createCollection(collectionName))) //
+ .delayElement(Duration.ofMillis(10)); // server replication time
}
/**
diff --git a/src/main/asciidoc/new-features.adoc b/src/main/asciidoc/new-features.adoc
index 8050d212f..c4adf5561 100644
--- a/src/main/asciidoc/new-features.adoc
+++ b/src/main/asciidoc/new-features.adoc
@@ -3,6 +3,7 @@
[[new-features.2-2-0]]
== What's New in Spring Data MongoDB 2.2
+* Compatible with MongoDB 4.2.
* <>
* <> via `ReactiveQuerydslPredicateExecutor`.
* <>.
diff --git a/src/main/asciidoc/reference/mongodb.adoc b/src/main/asciidoc/reference/mongodb.adoc
index c168d27f6..d6f94af34 100644
--- a/src/main/asciidoc/reference/mongodb.adoc
+++ b/src/main/asciidoc/reference/mongodb.adoc
@@ -1333,6 +1333,33 @@ List venues =
[[mongo.geo-near]]
==== Geo-near Queries
+[WARNING]
+====
+*Changed in 2.2!* +
+https://docs.mongodb.com/master/release-notes/4.2-compatibility/[MongoDB 4.2] removed support for the
+`geoNear` command which had been previously used to run the `NearQuery`.
+
+Spring Data MongoDB 2.2 `MongoOperations#geoNear` uses the `$geoNear` https://docs.mongodb.com/manual/reference/operator/aggregation/geoNear/[aggregation]
+instead of the `geoNear` command to run a `NearQuery`.
+
+The calculated distance (the `dis` when using a geoNear command) previously returned within a wrapper type now is embedded
+into the resulting document. If the given domain type already contains a property with equal name, the calculated distance
+is named `calculated-distance` or if that name is taken as well, postfixed with a random hash.
+
+Target types may contain a property named after the returned distance to (additionally) read it back directly into the domain
+type as shown below.
+
+[source,java]
+----
+GeoResults = template.query(Venue.class) <1>
+ .as(VenueWithDisField.class) <2>
+ .near(NearQuery.near(new GeoJsonPoint(-73.99, 40.73), KILOMETERS))
+ .all();
+----
+<1> Domain type used to identify the target collection and potential query mapping.
+<2> Target type containing a `dis` field of type `Number`.
+====
+
MongoDB supports querying the database for geo locations and calculating the distance from a given origin at the same time. With geo-near queries, you can express queries such as "find all restaurants in the surrounding 10 miles". To let you do so, `MongoOperations` provides `geoNear(…)` methods that take a `NearQuery` as an argument (as well as the already familiar entity type and collection), as shown in the following example:
[source,java]
@@ -2226,6 +2253,13 @@ Note that you can specify additional limit and sort values on the query, but you
[[mongo.server-side-scripts]]
== Script Operations
+[WARNING]
+====
+https://docs.mongodb.com/master/release-notes/4.2-compatibility/[MongoDB 4.2] removed support for the `eval` command used
+by `ScriptOperations`. +
+There is no replacement for the removed functionallity.
+====
+
MongoDB allows executing JavaScript functions on the server by either directly sending the script or calling a stored one. `ScriptOperations` can be accessed through `MongoTemplate` and provides basic abstraction for `JavaScript` usage. The following example shows how to us the `ScriptOperations` class:
====