From 909c51d00a735f57f6cf0c4f6bf74e018bd5723f Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Thu, 26 Sep 2019 13:00:14 +0200 Subject: [PATCH] DATAMONGO-2059 - Replace usage of deprecated collection.count() with collection.countDocuments(). This commit switches from simple collection.count(), operating on potentially false collection statistic, to countDocuments() using an aggregation for accurate results. The transition required query modifications at some points because $match does not support $near and $nearSphere but require $geoWithin along with $center or $centerSphere which does not support $minDistance (see https://jira.mongodb.org/browse/SERVER-37043). $geoWithin further more does not sort results by distance, but this fact can be ignored when just counting matches. Examples: { location : { $near : [-73.99171, 40.738868], $maxDistance : 1.1 } } { location : { $geoWithin : { $center: [ [-73.99171, 40.738868], 1.1] } } } { location : { $near : [-73.99171, 40.738868], $minDistance : 0.1, $maxDistance : 1.1 } } {$and :[ { $nor :[ { location :{ $geoWithin :{ $center :[ [-73.99171, 40.738868 ], 0.01] } } } ]}, { location :{ $geoWithin :{ $center :[ [-73.99171, 40.738868 ], 1.1] } } } ] } Original pull request: #604. --- .../data/mongodb/core/MongoTemplate.java | 21 +--- .../mongodb/core/ReactiveMongoTemplate.java | 19 +--- .../mongodb/core/convert/QueryMapper.java | 105 ++++++++++++++++-- ...essionAwareMethodInterceptorUnitTests.java | 4 +- .../mongodb/core/MongoTemplateUnitTests.java | 14 +-- .../core/ReactiveMongoTemplateUnitTests.java | 8 +- ...iveSessionBoundMongoTemplateUnitTests.java | 4 +- .../core/SessionBoundMongoTemplateTests.java | 1 - .../SessionBoundMongoTemplateUnitTests.java | 2 +- .../core/convert/QueryMapperUnitTests.java | 90 +++++++++++++++ .../core/geo/AbstractGeoSpatialTests.java | 21 +++- .../core/geo/GeoSpatial2DSphereTests.java | 16 ++- .../mongodb/core/geo/GeoSpatial2DTests.java | 7 +- 13 files changed, 243 insertions(+), 69 deletions(-) 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 79e1d603a..9fd71d2b4 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 @@ -53,6 +53,7 @@ import org.springframework.data.geo.Distance; import org.springframework.data.geo.GeoResult; import org.springframework.data.geo.GeoResults; import org.springframework.data.geo.Metric; +import org.springframework.data.geo.Point; import org.springframework.data.mapping.PropertyPath; import org.springframework.data.mapping.PropertyReferenceException; import org.springframework.data.mapping.callback.EntityCallbacks; @@ -1191,11 +1192,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, LOGGER.debug("Executing count: {} in collection: {}", serializeToJsonSafely(filter), collectionName); } - if (MongoDatabaseUtils.isTransactionActive(getMongoDbFactory())) { - return execute(collectionName, collection -> collection.countDocuments(filter, options)); - } - - return execute(collectionName, collection -> collection.count(filter, options)); + return execute(collectionName, collection -> collection.countDocuments(QueryMapper.processCountFilter(filter), options)); } /* @@ -3523,19 +3520,5 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, // native MongoDB objects that offer methods with ClientSession must not be proxied. return delegate.getDb(); } - - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.MongoTemplate#doCount(java.lang.String, org.bson.Document, com.mongodb.client.model.CountOptions) - */ - @Override - protected long doCount(String collectionName, Document filter, CountOptions options) { - - if (!session.hasActiveTransaction()) { - return super.doCount(collectionName, filter, options); - } - - return execute(collectionName, collection -> collection.countDocuments(filter, options)); - } } } 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 0dcab3e87..8c53d8eeb 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 @@ -1300,9 +1300,8 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati */ protected Mono doCount(String collectionName, Document filter, CountOptions options) { - return ReactiveMongoDatabaseUtils.isTransactionActive(mongoDatabaseFactory) // - .flatMap(txActive -> createMono(collectionName, - collection -> txActive ? collection.countDocuments(filter, options) : collection.count(filter, options))); + return createMono(collectionName, + collection -> collection.countDocuments(QueryMapper.processCountFilter(filter), options)); } /* @@ -3323,20 +3322,6 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati // native MongoDB objects that offer methods with ClientSession must not be proxied. return delegate.getMongoDatabase(); } - - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.ReactiveMongoTemplate#count(java.lang.String, org.bson.Document, com.mongodb.client.model.CountOptions) - */ - @Override - public Mono doCount(String collectionName, Document filter, CountOptions options) { - - if (!session.hasActiveTransaction()) { - return super.doCount(collectionName, filter, options); - } - - return createMono(collectionName, collection -> collection.countDocuments(filter, options)); - } } @RequiredArgsConstructor diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java index b1f984a3e..674c49fb3 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java @@ -15,17 +15,8 @@ */ package org.springframework.data.mongodb.core.convert; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.Map.Entry; -import java.util.Optional; -import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -36,6 +27,7 @@ import org.bson.types.ObjectId; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.converter.Converter; import org.springframework.data.domain.Example; +import org.springframework.data.geo.Point; import org.springframework.data.mapping.Association; import org.springframework.data.mapping.MappingException; import org.springframework.data.mapping.PersistentEntity; @@ -1290,4 +1282,97 @@ public class QueryMapper { public MappingContext, MongoPersistentProperty> getMappingContext() { return mappingContext; } + + public static Document processCountFilter(Document source) { + + Document target = new Document(); + for (Entry entry : source.entrySet()) { + + if (entry.getValue() instanceof Document) { + + Document theValue = (Document) entry.getValue(); + if (containsNear(theValue)) { + target.putAll(createGeoWithin(entry.getKey(), theValue)); + } else { + target.put(entry.getKey(), entry.getValue()); + } + } else if (entry.getValue() instanceof Collection) { + + Collection tmp = new ArrayList<>(); + for (Object val : (Collection) entry.getValue()) { + if (val instanceof Document) { + tmp.add(processCountFilter((Document) val)); + } else { + tmp.add(val); + } + } + target.put(entry.getKey(), tmp); + } else { + target.put(entry.getKey(), entry.getValue()); + } + } + return target; + } + + private static Document createGeoWithin(String key, Document source) { + + boolean spheric = source.containsKey("$nearSphere"); + Object $near = spheric ? source.get("$nearSphere") : source.get("$near"); + + Number maxDistance = source.containsKey("$maxDistance") ? (Number) source.get("$maxDistance") : Double.MAX_VALUE; + List $centerMax = Arrays.asList(toCenterCoordinates($near), maxDistance); + Document $geoWithinMax = new Document("$geoWithin", + new Document(spheric ? "$centerSphere" : "$center", $centerMax)); + + if (!containsNearWithMinDistance(source)) { + return new Document(key, $geoWithinMax); + } + + Number minDistance = (Number) source.get("$minDistance"); + List $centerMin = Arrays.asList(toCenterCoordinates($near), minDistance); + Document $geoWithinMin = new Document("$geoWithin", + new Document(spheric ? "$centerSphere" : "$center", $centerMin)); + + List criteria = new ArrayList<>(); + criteria.add(new Document("$nor", Arrays.asList(new Document(key, $geoWithinMin)))); + criteria.add(new Document(key, $geoWithinMax)); + return new Document("$and", criteria); + } + + private static boolean containsNear(Document source) { + + if (source.containsKey("$near") || source.containsKey("$nearSphere")) { + return true; + } + + return false; + } + + private static boolean containsNearWithMinDistance(Document source) { + + if (!containsNear(source)) { + return false; + } + + return source.containsKey("$minDistance"); + } + + private static Object toCenterCoordinates(Object value) { + + if (ObjectUtils.isArray(value)) { + return value; + } + + if (value instanceof Point) { + return Arrays.asList(((Point) value).getX(), ((Point) value).getY()); + } + + if (value instanceof Document && ((Document) value).containsKey("x")) { + + Document point = (Document) value; + return Arrays.asList(point.get("x"), point.get("y")); + } + + return value; + } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/SessionAwareMethodInterceptorUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/SessionAwareMethodInterceptorUnitTests.java index cd221cb03..29a7a432c 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/SessionAwareMethodInterceptorUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/SessionAwareMethodInterceptorUnitTests.java @@ -107,11 +107,11 @@ public class SessionAwareMethodInterceptorUnitTests { public void usesCacheForMethodLookup() { MethodCache cache = (MethodCache) ReflectionTestUtils.getField(SessionAwareMethodInterceptor.class, "METHOD_CACHE"); - Method countMethod = ClassUtils.getMethod(MongoCollection.class, "count"); + Method countMethod = ClassUtils.getMethod(MongoCollection.class, "countDocuments"); assertThat(cache.contains(countMethod, MongoCollection.class)).isFalse(); - collection.count(); + collection.countDocuments(); assertThat(cache.contains(countMethod, MongoCollection.class)).isTrue(); } 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 eed5cbd9d..6b8813734 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 @@ -154,7 +154,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests { when(db.runCommand(any(), any(Class.class))).thenReturn(commandResultDocument); when(collection.find(any(org.bson.Document.class), any(Class.class))).thenReturn(findIterable); when(collection.mapReduce(any(), any(), eq(Document.class))).thenReturn(mapReduceIterable); - when(collection.count(any(Bson.class), any(CountOptions.class))).thenReturn(1L); // TODO: MongoDB 4 - fix me + when(collection.countDocuments(any(Bson.class), any(CountOptions.class))).thenReturn(1L); // TODO: MongoDB 4 - fix me when(collection.getNamespace()).thenReturn(new MongoNamespace("db.mock-collection")); when(collection.aggregate(any(List.class), any())).thenReturn(aggregateIterable); when(collection.withReadPreference(any())).thenReturn(collection); @@ -735,7 +735,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests { template.exists(new BasicQuery("{}").collation(Collation.of("fr")), AutogenerateableId.class); ArgumentCaptor options = ArgumentCaptor.forClass(CountOptions.class); - verify(collection).count(any(), options.capture()); + verify(collection).countDocuments(any(), options.capture()); assertThat(options.getValue().getCollation()) .isEqualTo(com.mongodb.client.model.Collation.builder().locale("fr").build()); @@ -926,7 +926,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests { template.count(new BasicQuery("{}").collation(Collation.of("fr")), AutogenerateableId.class); ArgumentCaptor options = ArgumentCaptor.forClass(CountOptions.class); - verify(collection).count(any(), options.capture()); + verify(collection).countDocuments(any(), options.capture()); assertThat(options.getValue().getCollation()) .isEqualTo(com.mongodb.client.model.Collation.builder().locale("fr").build()); @@ -939,7 +939,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests { template.count(new BasicQuery("{}").withHint(queryHint), AutogenerateableId.class); ArgumentCaptor options = ArgumentCaptor.forClass(CountOptions.class); - verify(collection).count(any(), options.capture()); + verify(collection).countDocuments(any(), options.capture()); assertThat(options.getValue().getHint()).isEqualTo(queryHint); } @@ -1068,7 +1068,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests { template.count(new BasicQuery("{}").skip(100), AutogenerateableId.class); ArgumentCaptor options = ArgumentCaptor.forClass(CountOptions.class); - verify(collection).count(any(), options.capture()); + verify(collection).countDocuments(any(), options.capture()); assertThat(options.getValue().getSkip()).isEqualTo(100); } @@ -1079,7 +1079,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests { template.count(new BasicQuery("{}").limit(10), AutogenerateableId.class); ArgumentCaptor options = ArgumentCaptor.forClass(CountOptions.class); - verify(collection).count(any(), options.capture()); + verify(collection).countDocuments(any(), options.capture()); assertThat(options.getValue().getLimit()).isEqualTo(10); } @@ -1150,7 +1150,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests { template.exists(new BasicQuery("{}"), Sith.class); ArgumentCaptor options = ArgumentCaptor.forClass(CountOptions.class); - verify(collection).count(any(), options.capture()); + verify(collection).countDocuments(any(), options.capture()); assertThat(options.getValue().getCollation()) .isEqualTo(com.mongodb.client.model.Collation.builder().locale("de_AT").build()); 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 2daed014b..6e5becebd 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 @@ -126,7 +126,7 @@ public class ReactiveMongoTemplateUnitTests { when(collection.find(any(Document.class), any(Class.class))).thenReturn(findPublisher); when(collection.aggregate(anyList())).thenReturn(aggregatePublisher); when(collection.aggregate(anyList(), any(Class.class))).thenReturn(aggregatePublisher); - when(collection.count(any(), any(CountOptions.class))).thenReturn(Mono.just(0L)); + when(collection.countDocuments(any(), any(CountOptions.class))).thenReturn(Mono.just(0L)); when(collection.updateOne(any(), any(Bson.class), any(UpdateOptions.class))).thenReturn(updateResultPublisher); when(collection.updateMany(any(Bson.class), any(Bson.class), any())).thenReturn(updateResultPublisher); when(collection.findOneAndUpdate(any(), any(Bson.class), any(FindOneAndUpdateOptions.class))) @@ -391,7 +391,7 @@ public class ReactiveMongoTemplateUnitTests { template.count(new Query().skip(10), Person.class, "star-wars").subscribe(); ArgumentCaptor options = ArgumentCaptor.forClass(CountOptions.class); - verify(collection).count(any(), options.capture()); + verify(collection).countDocuments(any(), options.capture()); assertThat(options.getValue().getSkip()).isEqualTo(10); } @@ -402,7 +402,7 @@ public class ReactiveMongoTemplateUnitTests { template.count(new Query().limit(100), Person.class, "star-wars").subscribe(); ArgumentCaptor options = ArgumentCaptor.forClass(CountOptions.class); - verify(collection).count(any(), options.capture()); + verify(collection).countDocuments(any(), options.capture()); assertThat(options.getValue().getLimit()).isEqualTo(100); } @@ -414,7 +414,7 @@ public class ReactiveMongoTemplateUnitTests { template.count(new Query().withHint(queryHint), Person.class, "star-wars").subscribe(); ArgumentCaptor options = ArgumentCaptor.forClass(CountOptions.class); - verify(collection).count(any(), options.capture()); + verify(collection).countDocuments(any(), options.capture()); assertThat(options.getValue().getHint()).isEqualTo(queryHint); } 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 bee7bee0f..a35d32c10 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 @@ -111,7 +111,7 @@ public class ReactiveSessionBoundMongoTemplateUnitTests { when(collection.deleteMany(any(ClientSession.class), any(), any())).thenReturn(resultPublisher); when(collection.insertOne(any(ClientSession.class), any(Document.class))).thenReturn(resultPublisher); when(collection.aggregate(any(ClientSession.class), anyList(), any(Class.class))).thenReturn(aggregatePublisher); - when(collection.count(any(ClientSession.class), any(), any(CountOptions.class))).thenReturn(resultPublisher); + when(collection.countDocuments(any(ClientSession.class), any(), any(CountOptions.class))).thenReturn(resultPublisher); when(collection.drop(any(ClientSession.class))).thenReturn(resultPublisher); when(collection.findOneAndUpdate(any(ClientSession.class), any(), any(Bson.class), any())) .thenReturn(resultPublisher); @@ -224,7 +224,7 @@ public class ReactiveSessionBoundMongoTemplateUnitTests { template.count(new Query(), Person.class).subscribe(); - verify(collection).count(eq(clientSession), any(), any(CountOptions.class)); + verify(collection).countDocuments(eq(clientSession), any(), any(CountOptions.class)); } @Test // DATAMONGO-1880 diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/SessionBoundMongoTemplateTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/SessionBoundMongoTemplateTests.java index 1fe289f6f..fd0adfe6c 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/SessionBoundMongoTemplateTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/SessionBoundMongoTemplateTests.java @@ -284,7 +284,6 @@ public class SessionBoundMongoTemplateTests { } @Test // DATAMONGO-2012 - @Ignore("error 2 (BadValue): $match does not support $geoNear, $near, and $nearSphere") public void countWithGeoInTransaction() { if (!template.collectionExists(Person.class)) { 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 08f1660bf..fe9027ace 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 @@ -226,7 +226,7 @@ public class SessionBoundMongoTemplateUnitTests { template.count(new Query(), Person.class); - verify(collection).count(eq(clientSession), any(), any(CountOptions.class)); + verify(collection).countDocuments(eq(clientSession), any(), any(CountOptions.class)); } @Test // DATAMONGO-1880 diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/QueryMapperUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/QueryMapperUnitTests.java index d1fd28622..9ec635474 100755 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/QueryMapperUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/QueryMapperUnitTests.java @@ -890,6 +890,96 @@ public class QueryMapperUnitTests { assertThat(target).isEqualTo(new org.bson.Document("_id", "id-1")); } + @Test // DATAMONGO-2059 + public void nearToGeoWithinWithoutDistance() { + + Query source = query(where("location").near(new Point(-73.99171, 40.738868))); + org.bson.Document target = postProcessQueryForCount(source); + + assertThat(target).isEqualTo(org.bson.Document + .parse("{\"location\": {\"$geoWithin\": {\"$center\": [[-73.99171, 40.738868], 1.7976931348623157E308]}}}")); + } + + @Test // DATAMONGO-2059 + public void nearSphereToGeoWithinWithoutDistance() { + + Query source = query(where("location").nearSphere(new Point(-73.99171, 40.738868))); + org.bson.Document target = postProcessQueryForCount(source); + + assertThat(target).isEqualTo(org.bson.Document.parse( + "{\"location\": {\"$geoWithin\": {\"$centerSphere\": [[-73.99171, 40.738868], 1.7976931348623157E308]}}}")); + } + + @Test // DATAMONGO-2059 + public void nearToGeoWithinWithMaxDistance() { + + Query source = query(where("location").near(new Point(-73.99171, 40.738868)).maxDistance(10)); + org.bson.Document target = postProcessQueryForCount(source); + + assertThat(target).isEqualTo( + org.bson.Document.parse("{\"location\": {\"$geoWithin\": {\"$center\": [[-73.99171, 40.738868], 10.0]}}}")); + } + + @Test // DATAMONGO-2059 + public void nearSphereToGeoWithinWithMaxDistance() { + + Query source = query(where("location").nearSphere(new Point(-73.99171, 40.738868)).maxDistance(10)); + org.bson.Document target = postProcessQueryForCount(source); + + assertThat(target).isEqualTo(org.bson.Document + .parse("{\"location\": {\"$geoWithin\": {\"$centerSphere\": [[-73.99171, 40.738868], 10.0]}}}")); + } + + @Test // DATAMONGO-2059 + public void nearToGeoWithinWithMinDistance() { + + Query source = query(where("location").near(new Point(-73.99171, 40.738868)).minDistance(0.01)); + org.bson.Document target = postProcessQueryForCount(source); + + assertThat(target).isEqualTo(org.bson.Document.parse( + "{\"$and\":[{\"$nor\":[{\"location\":{\"$geoWithin\":{\"$center\":[ [ -73.99171, 40.738868 ], 0.01]}}}]}," + + " {\"location\":{\"$geoWithin\":{\"$center\":[ [ -73.99171, 40.738868 ], 1.7976931348623157E308]}}}]}")); + } + + @Test // DATAMONGO-2059 + public void nearToGeoWithinWithMaxDistanceAndCombinedWithOtherCriteria() { + + Query source = query( + where("name").is("food").and("location").near(new Point(-73.99171, 40.738868)).maxDistance(10)); + org.bson.Document target = postProcessQueryForCount(source); + + assertThat(target).isEqualTo(org.bson.Document + .parse("{\"name\": \"food\", \"location\": {\"$geoWithin\": {\"$center\": [[-73.99171, 40.738868], 10.0]}}}")); + } + + @Test // DATAMONGO-2059 + public void nearToGeoWithinWithMinDistanceOrCombinedWithOtherCriteria() { + + Query source = query(new Criteria().orOperator(where("name").is("food"), + where("location").near(new Point(-73.99171, 40.738868)).minDistance(0.01))); + org.bson.Document target = postProcessQueryForCount(source); + + assertThat(target).isEqualTo(org.bson.Document.parse( + "{\"$or\" : [ { \"name\": \"food\" }, {\"$and\":[{\"$nor\":[{\"location\":{\"$geoWithin\":{\"$center\":[ [ -73.99171, 40.738868 ], 0.01]}}}]},{\"location\":{\"$geoWithin\":{\"$center\":[ [ -73.99171, 40.738868 ], 1.7976931348623157E308]}}}]} ]}")); + } + + @Test // DATAMONGO-2059 + public void nearToGeoWithinWithMaxDistanceOrCombinedWithOtherCriteria() { + + Query source = query(new Criteria().orOperator(where("name").is("food"), + where("location").near(new Point(-73.99171, 40.738868)).maxDistance(10))); + org.bson.Document target = postProcessQueryForCount(source); + + assertThat(target).isEqualTo(org.bson.Document.parse( + "{\"$or\" : [ { \"name\": \"food\" }, {\"location\": {\"$geoWithin\": {\"$center\": [[-73.99171, 40.738868], 10.0]}}} ]}")); + } + + private org.bson.Document postProcessQueryForCount(Query source) { + + org.bson.Document intermediate = mapper.getMappedObject(source.getQueryObject(), (MongoPersistentEntity) null); + return QueryMapper.processCountFilter(intermediate); + } + @Document public class Foo { @Id private ObjectId id; diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/AbstractGeoSpatialTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/AbstractGeoSpatialTests.java index a96489eda..8355c60f3 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/AbstractGeoSpatialTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/AbstractGeoSpatialTests.java @@ -120,24 +120,33 @@ public abstract class AbstractGeoSpatialTests { public void withinCenter() { Circle circle = new Circle(-73.99171, 40.738868, 0.01); - List venues = template.find(query(where("location").within(circle)), Venue.class); + Query query = query(where("location").within(circle)); + List venues = template.find(query, Venue.class); + assertThat(venues).hasSize(7); + assertThat(template.count(query, Venue.class)).isEqualTo(7); } @Test public void withinCenterSphere() { Circle circle = new Circle(-73.99171, 40.738868, 0.003712240453784); - List venues = template.find(query(where("location").withinSphere(circle)), Venue.class); + Query query = query(where("location").withinSphere(circle)); + + List venues = template.find(query, Venue.class); assertThat(venues).hasSize(11); + assertThat(template.count(query, Venue.class)).isEqualTo(11); } @Test public void withinBox() { Box box = new Box(new Point(-73.99756, 40.73083), new Point(-73.988135, 40.741404)); - List venues = template.find(query(where("location").within(box)), Venue.class); + Query query = query(where("location").within(box)); + + List venues = template.find(query, Venue.class); assertThat(venues).hasSize(4); + assertThat(template.count(query, Venue.class)).isEqualTo(4); } @Test @@ -150,8 +159,10 @@ public abstract class AbstractGeoSpatialTests { Polygon polygon = new Polygon(first, second, third, fourth); - List venues = template.find(query(where("location").within(polygon)), Venue.class); + Query query = query(where("location").within(polygon)); + List venues = template.find(query, Venue.class); assertThat(venues).hasSize(4); + assertThat(template.count(query, Venue.class)).isEqualTo(4); } @Test @@ -159,8 +170,10 @@ public abstract class AbstractGeoSpatialTests { Point point = new Point(-73.99171, 40.738868); Query query = query(where("location").nearSphere(point).maxDistance(0.003712240453784)); + List venues = template.find(query, Venue.class); assertThat(venues).hasSize(11); + assertThat(template.count(query, Venue.class)).isEqualTo(11); } @Test // DATAMONGO-1360 diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoSpatial2DSphereTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoSpatial2DSphereTests.java index f58ef2ceb..1240bcdaf 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoSpatial2DSphereTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoSpatial2DSphereTests.java @@ -22,6 +22,7 @@ import static org.springframework.data.mongodb.core.query.Query.*; import java.util.List; +import org.junit.Ignore; import org.junit.Test; import org.springframework.data.domain.Sort.Direction; @@ -36,6 +37,7 @@ import org.springframework.data.mongodb.core.index.IndexField; import org.springframework.data.mongodb.core.index.IndexInfo; import org.springframework.data.mongodb.core.index.IndexOperations; import org.springframework.data.mongodb.core.query.NearQuery; +import org.springframework.data.mongodb.core.query.Query; /** * @author Christoph Strobl @@ -72,11 +74,23 @@ public class GeoSpatial2DSphereTests extends AbstractGeoSpatialTests { @Test // DATAMONGO-1110 public void nearSphereWithMinDistance() { + Point point = new Point(-73.99171, 40.738868); - List venues = template.find(query(where("location").nearSphere(point).minDistance(0.01)), Venue.class); + Query query = query(where("location").nearSphere(point).minDistance(0.01)); + + List venues = template.find(query, Venue.class); assertThat(venues.size()).isEqualTo(1); } + @Test + public void countNearSphereWithMinDistance() { + + Point point = new Point(-73.99171, 40.738868); + Query query = query(where("location").nearSphere(point).minDistance(0.01)); + + assertThat(template.count(query, Venue.class)).isEqualTo(1); + } + @Override protected void createIndex() { template.indexOps(Venue.class).ensureIndex(new GeospatialIndex("location").typed(GeoSpatialIndexType.GEO_2DSPHERE)); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoSpatial2DTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoSpatial2DTests.java index 74ce4af6c..1adb751f5 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoSpatial2DTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoSpatial2DTests.java @@ -32,6 +32,7 @@ import org.springframework.data.mongodb.core.index.GeospatialIndex; import org.springframework.data.mongodb.core.index.IndexField; import org.springframework.data.mongodb.core.index.IndexInfo; import org.springframework.data.mongodb.core.index.IndexOperations; +import org.springframework.data.mongodb.core.query.Query; /** * Modified from https://github.com/deftlabs/mongo-java-geospatial-example @@ -45,9 +46,13 @@ public class GeoSpatial2DTests extends AbstractGeoSpatialTests { @Test public void nearPoint() { + Point point = new Point(-73.99171, 40.738868); - List venues = template.find(query(where("location").near(point).maxDistance(0.01)), Venue.class); + Query query = query(where("location").near(point).maxDistance(0.01)); + + List venues = template.find(query, Venue.class); assertThat(venues.size()).isEqualTo(7); + assertThat(template.count(query, Venue.class)).isEqualTo(7); } @Test // DATAMONGO-360