DATAMONGO-2264 - Support upcoming MongoDB 4.2 Server.
Deprecate and guard tests for removed commands:
- eval
- group
- maxScan
- geoNear
Transition from removed geoNear command to $geoNear aggregation pipeline stage.
This allows users to still use NearQuery along with the template that now transforms the query into a GeoNearOperation applying skip (previously in memory skipping) and limit (previous num parameter) as additional aggregation pipeline stages.
Tested against:
- 4.1.10
- 4.0.4
- 3.6.12
- 4.4.20
Original pull request: #744.
This commit is contained in:
committed by
Mark Paluch
parent
e683c8f08f
commit
33d69abd53
@@ -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:
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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. <br />
|
||||
* Please use {@link #aggregate(TypedAggregation, String, Class) } with a
|
||||
* {@link org.springframework.data.mongodb.core.aggregation.GroupOperation} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
<T> GroupByResults<T> group(String inputCollectionName, GroupBy groupBy, Class<T> 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. <br />
|
||||
* 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
|
||||
<T> GroupByResults<T> group(@Nullable Criteria criteria, String inputCollectionName, GroupBy groupBy,
|
||||
Class<T> 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
|
||||
* <a href="https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation" > Spring's Type
|
||||
* Conversion"</a> for more details.
|
||||
* <a href="https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation" > Spring's
|
||||
* Type Conversion"</a> for more details.
|
||||
* <p/>
|
||||
* <p/>
|
||||
* 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
|
||||
* <a href="https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation" > Spring's Type
|
||||
* Conversion"</a> for more details.
|
||||
* <a href="https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation" > Spring's
|
||||
* Type Conversion"</a> for more details.
|
||||
*
|
||||
* @param objectToSave the object to store in the collection. Must not be {@literal null}.
|
||||
* @return the saved object.
|
||||
|
||||
@@ -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 <T> GeoResults<T> geoNear(NearQuery near, Class<?> domainType, String collectionName, Class<T> 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<Document> results = aggregate($geoNear, collection, Document.class);
|
||||
|
||||
DocumentCallback<GeoResult<T>> callback = new GeoNearResultDocumentCallback<>(distanceField,
|
||||
new ProjectingReadCallback<>(mongoConverter, domainType, returnType, collection), near.getMetric());
|
||||
|
||||
List<GeoResult<T>> result = new ArrayList<>();
|
||||
|
||||
BigDecimal aggregate = BigDecimal.ZERO;
|
||||
for (Document element : results) {
|
||||
|
||||
GeoResult<T> 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<Object> results = (List<Object>) commandResult.get("results");
|
||||
results = results == null ? Collections.emptyList() : results;
|
||||
|
||||
DocumentCallback<GeoResult<T>> callback = new GeoNearResultDocumentCallback<>(
|
||||
new ProjectingReadCallback<>(mongoConverter, domainType, returnType, collectionName), near.getMetric());
|
||||
List<GeoResult<T>> 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 <a href="https://jira.mongodb.org/browse/SERVER-3925">MongoDB Jira: SERVER-3925</a>
|
||||
*/
|
||||
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<T> implements DocumentCallback<GeoResult<T>> {
|
||||
|
||||
private final String distanceField;
|
||||
private final DocumentCallback<T> 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<T> delegate, Metric metric) {
|
||||
GeoNearResultDocumentCallback(String distanceField, DocumentCallback<T> 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<T> 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));
|
||||
}
|
||||
|
||||
@@ -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<T> 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<T> callback = new GeoNearResultDocumentCallback<>(
|
||||
new ProjectingReadCallback<>(mongoConverter, entityClass, returnType, collectionName), near.getMetric());
|
||||
|
||||
return executeCommand(command, this.readPreference).flatMapMany(document -> {
|
||||
|
||||
List<Document> 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<T> implements DocumentCallback<GeoResult<T>> {
|
||||
|
||||
private final String distanceField;
|
||||
private final DocumentCallback<T> 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<T> delegate, Metric metric) {
|
||||
GeoNearResultDocumentCallback(String distanceField, DocumentCallback<T> delegate, Metric metric) {
|
||||
|
||||
Assert.notNull(delegate, "DocumentCallback must not be null!");
|
||||
|
||||
this.distanceField = distanceField;
|
||||
this.delegate = delegate;
|
||||
this.metric = metric;
|
||||
}
|
||||
|
||||
public GeoResult<T> 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));
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
/**
|
||||
|
||||
@@ -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}.
|
||||
|
||||
@@ -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<Document> toPipelineStages(AggregationOperationContext context) {
|
||||
return Collections.singletonList(toDocument(context));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ class AggregationOperationRenderer {
|
||||
|
||||
for (AggregationOperation operation : operations) {
|
||||
|
||||
operationDocuments.add(operation.toDocument(contextToUse));
|
||||
operationDocuments.addAll(operation.toPipelineStages(contextToUse));
|
||||
|
||||
if (operation instanceof FieldsExposingAggregationOperation) {
|
||||
|
||||
|
||||
@@ -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<Document> toPipelineStages(AggregationOperationContext context) {
|
||||
|
||||
Document command = toDocument(context);
|
||||
Number limit = (Number) command.get("$geoNear", Document.class).remove("num");
|
||||
|
||||
List<Document> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,7 +128,13 @@ public class GraphLookupOperation implements InheritsFieldsAggregationOperation
|
||||
*/
|
||||
@Override
|
||||
public ExposedFields getFields() {
|
||||
return ExposedFields.from(new ExposedField(as, true));
|
||||
|
||||
List<ExposedField> 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]));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -79,8 +79,6 @@ public class TypeBasedAggregationOperationContext implements AggregationOperatio
|
||||
*/
|
||||
@Override
|
||||
public FieldReference getReference(Field field) {
|
||||
|
||||
PropertyPath.from(field.getTarget(), type);
|
||||
return getReferenceFor(field);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -29,7 +29,9 @@ import org.springframework.util.Assert;
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @param <T> 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<T> implements Iterable<T> {
|
||||
|
||||
private final List<T> mappedResults;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
|
||||
@@ -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<PartTr
|
||||
}
|
||||
|
||||
MongoEntityMetadata<?> 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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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<org.bson.Document> indexInfo = template.getCollection(template.getCollectionName(Person.class))
|
||||
.listIndexes();
|
||||
@@ -443,7 +444,7 @@ public class MongoTemplateTests {
|
||||
}
|
||||
}
|
||||
|
||||
assertThat(indexKey, IsMapContaining.<String, Object> hasEntry("age", -1D));
|
||||
assertThat(indexKey, IsMapContaining.<String, Object> hasEntry("age", -1));
|
||||
assertThat(unique, is(true));
|
||||
|
||||
IndexInfo info = template.indexOps(Person.class).getIndexInfo().get(1);
|
||||
|
||||
@@ -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<Document> 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<Document> capture = ArgumentCaptor.forClass(Document.class);
|
||||
verify(this.db, times(1)).runCommand(capture.capture(), any(Class.class));
|
||||
ArgumentCaptor<List<Document>> 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<Document> capture = ArgumentCaptor.forClass(Document.class);
|
||||
verify(this.db, times(1)).runCommand(capture.capture(), any(Class.class));
|
||||
ArgumentCaptor<List<Document>> 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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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<Document> 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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<Document> 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<Document> 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<Product> agg = newAggregation(Product.class, //
|
||||
project("name", "netPrice") //
|
||||
.andExpression("unknown + 1").as("netPricePlus1") //
|
||||
);
|
||||
|
||||
mongoTemplate.aggregate(agg, Document.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see <a href=
|
||||
* "https://stackoverflow.com/questions/18653574/spring-data-mongodb-aggregation-framework-invalid-reference-in-group-operati">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<Document> result = mongoTemplate.aggregate(agg, Document.class);
|
||||
List<Document> 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<DATAMONGO788> aggregation = Aggregation.newAggregation(DATAMONGO788.class, projectFirst, group,
|
||||
project);
|
||||
project, Aggregation.sort(Sort.by("xPerY")));
|
||||
AggregationResults<Document> aggResults = mongoTemplate.aggregate(aggregation, Document.class);
|
||||
List<Document> 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<Document> 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<Document> 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<Document> result = mongoTemplate.aggregate(agg, Document.class);
|
||||
|
||||
Document object = result.getUniqueMappedResult();
|
||||
List<Object> list = (List<Object>) 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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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<VenueWithDistanceField> 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<VenueWithDistanceField> 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<Venue2DSphere> 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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<Person, String> 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);
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
[[new-features.2-2-0]]
|
||||
== What's New in Spring Data MongoDB 2.2
|
||||
* Compatible with MongoDB 4.2.
|
||||
* <<mongo.query.kotlin-support,Type-safe Queries for Kotlin>>
|
||||
* <<mongodb.reactive.repositories.queries.type-safe,Querydsl support for reactive repositories>> via `ReactiveQuerydslPredicateExecutor`.
|
||||
* <<reactive.gridfs,Reactive GridFS support>>.
|
||||
|
||||
@@ -1333,6 +1333,33 @@ List<Venue> 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<VenueWithDisField> = 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:
|
||||
|
||||
====
|
||||
|
||||
Reference in New Issue
Block a user