diff --git a/pom.xml b/pom.xml index 15b2d67f47..17b30d0981 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-mongodb-parent - 2.3.0.BUILD-SNAPSHOT + 2.3.0.DATAMONGO-2059-SNAPSHOT pom Spring Data MongoDB diff --git a/spring-data-mongodb-benchmarks/pom.xml b/spring-data-mongodb-benchmarks/pom.xml index c4766040c1..9cf397da49 100644 --- a/spring-data-mongodb-benchmarks/pom.xml +++ b/spring-data-mongodb-benchmarks/pom.xml @@ -7,7 +7,7 @@ org.springframework.data spring-data-mongodb-parent - 2.3.0.BUILD-SNAPSHOT + 2.3.0.DATAMONGO-2059-SNAPSHOT ../pom.xml diff --git a/spring-data-mongodb-distribution/pom.xml b/spring-data-mongodb-distribution/pom.xml index ed39c63e76..0c2d12f21b 100644 --- a/spring-data-mongodb-distribution/pom.xml +++ b/spring-data-mongodb-distribution/pom.xml @@ -14,7 +14,7 @@ org.springframework.data spring-data-mongodb-parent - 2.3.0.BUILD-SNAPSHOT + 2.3.0.DATAMONGO-2059-SNAPSHOT ../pom.xml diff --git a/spring-data-mongodb/pom.xml b/spring-data-mongodb/pom.xml index 25cf02b5d5..c54007a4bb 100644 --- a/spring-data-mongodb/pom.xml +++ b/spring-data-mongodb/pom.xml @@ -11,7 +11,7 @@ org.springframework.data spring-data-mongodb-parent - 2.3.0.BUILD-SNAPSHOT + 2.3.0.DATAMONGO-2059-SNAPSHOT ../pom.xml diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/CountQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/CountQuery.java new file mode 100644 index 0000000000..4504597490 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/CountQuery.java @@ -0,0 +1,227 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mongodb.core; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.bson.Document; + +import org.springframework.data.geo.Point; +import org.springframework.lang.Nullable; +import org.springframework.util.ObjectUtils; + +/** + * Value object representing a count query. Count queries using {@code $near} or {@code $nearSphere} require a rewrite + * to {@code $geoWithin}. + * + * @author Christoph Strobl + * @author Mark Paluch + * @since 2.3 + */ +class CountQuery { + + private Document source; + + private CountQuery(Document source) { + this.source = source; + } + + public static CountQuery of(Document source) { + return new CountQuery(source); + } + + /** + * Returns the query {@link Document} that can be used with {@code countDocuments()}. Potentially rewrites the query + * to be usable with {@code countDocuments()}. + * + * @return the query {@link Document} that can be used with {@code countDocuments()}. + */ + public Document toQueryDocument() { + + if (!requiresRewrite(source)) { + return source; + } + + Document target = new Document(); + + for (Map.Entry entry : source.entrySet()) { + + if (entry.getValue() instanceof Document && requiresRewrite(entry.getValue())) { + + Document theValue = (Document) entry.getValue(); + target.putAll(createGeoWithin(entry.getKey(), theValue, source.get("$and"))); + continue; + } + + if (entry.getValue() instanceof Collection && requiresRewrite(entry.getValue())) { + + Collection source = (Collection) entry.getValue(); + + target.put(entry.getKey(), rewriteCollection(source)); + continue; + } + + if ("$and".equals(entry.getKey()) && target.containsKey("$and")) { + // Expect $and to be processed with Document and createGeoWithin. + continue; + } + + target.put(entry.getKey(), entry.getValue()); + } + + return target; + } + + /** + * @param valueToInspect + * @return {@code true} if the enclosing element needs to be rewritten. + */ + private boolean requiresRewrite(Object valueToInspect) { + + if (valueToInspect instanceof Document) { + return requiresRewrite((Document) valueToInspect); + } + + if (valueToInspect instanceof Collection) { + return requiresRewrite((Collection) valueToInspect); + } + + return false; + } + + private boolean requiresRewrite(Collection collection) { + + for (Object o : collection) { + if (o instanceof Document && requiresRewrite((Document) o)) { + return true; + } + } + + return false; + } + + private boolean requiresRewrite(Document document) { + + if (containsNear(document)) { + return true; + } + + for (Object entry : document.values()) { + + if (requiresRewrite(entry)) { + return true; + } + } + + return false; + } + + private Collection rewriteCollection(Collection source) { + + Collection rewrittenCollection = new ArrayList<>(source.size()); + + for (Object item : source) { + if (item instanceof Document && requiresRewrite(item)) { + rewrittenCollection.add(CountQuery.of((Document) item).toQueryDocument()); + } else { + rewrittenCollection.add(item); + } + } + + return rewrittenCollection; + } + + /** + * Rewrite the near query for field {@code key} to {@code $geoWithin}. + * + * @param key the queried field. + * @param source source {@link Document}. + * @param $and potentially existing {@code $and} condition. + * @return the rewritten query {@link Document}. + */ + private static Document createGeoWithin(String key, Document source, @Nullable Object $and) { + + 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<>(); + + if ($and != null) { + if ($and instanceof Collection) { + criteria.addAll((Collection) $and); + } else { + throw new IllegalArgumentException( + "Cannot rewrite query as it contains an '$and' element that is not a Collection!: Offending element: " + + $and); + } + } + + criteria.add(new Document("$nor", Collections.singletonList(new Document(key, $geoWithinMin)))); + criteria.add(new Document(key, $geoWithinMax)); + return new Document("$and", criteria); + } + + private static boolean containsNear(Document source) { + return source.containsKey("$near") || source.containsKey("$nearSphere"); + } + + 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/main/java/org/springframework/data/mongodb/core/MongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java index 79e1d603ac..1cc07789c3 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.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,8 @@ protected long doCount(String collectionName, Document filter, CountOptions opti 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(CountQuery.of(filter).toQueryDocument(), options)); } /* @@ -3523,19 +3521,5 @@ public MongoDatabase getDb() { // 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 0dcab3e87b..de3883f04f 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 Mono count(Query query, @Nullable Class entityClass, String coll */ 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(CountQuery.of(filter).toQueryDocument(), options)); } /* @@ -3323,20 +3322,6 @@ public MongoDatabase getMongoDatabase() { // 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 b1f984a3ec..efb3368fca 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 @@ -33,6 +33,7 @@ import org.bson.Document; import org.bson.conversions.Bson; import org.bson.types.ObjectId; + import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.converter.Converter; import org.springframework.data.domain.Example; 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 cd221cb038..29a7a432c8 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 void justMoveOnIfNoOverloadWithSessionAvailable() { 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/CountQueryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/CountQueryUnitTests.java new file mode 100644 index 0000000000..648b2b654c --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/CountQueryUnitTests.java @@ -0,0 +1,164 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mongodb.core; + +import static org.mockito.Mockito.*; +import static org.springframework.data.mongodb.core.query.Criteria.*; +import static org.springframework.data.mongodb.core.query.Query.*; +import static org.springframework.data.mongodb.test.util.Assertions.*; + +import org.junit.Before; +import org.junit.Test; + +import org.springframework.data.geo.Point; +import org.springframework.data.mongodb.MongoDbFactory; +import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver; +import org.springframework.data.mongodb.core.convert.MappingMongoConverter; +import org.springframework.data.mongodb.core.convert.QueryMapper; +import org.springframework.data.mongodb.core.mapping.MongoMappingContext; +import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Query; + +/** + * Unit tests for {@link CountQuery}. + * + * @author Mark Paluch + * @author Christoph Strobl + */ +public class CountQueryUnitTests { + + QueryMapper mapper; + MongoMappingContext context; + MappingMongoConverter converter; + + MongoDbFactory factory = mock(MongoDbFactory.class); + + @Before + public void setUp() { + + this.context = new MongoMappingContext(); + + this.converter = new MappingMongoConverter(new DefaultDbRefResolver(factory), context); + this.converter.afterPropertiesSet(); + + this.mapper = new QueryMapper(converter); + } + + @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 nearAndExisting$and() { + + Query source = query(where("location").near(new Point(-73.99171, 40.738868)).minDistance(0.01)) + .addCriteria(new Criteria().andOperator(where("foo").is("bar"))); + org.bson.Document target = postProcessQueryForCount(source); + + assertThat(target).isEqualTo(org.bson.Document.parse("{\"$and\":[" // + + "{\"foo\":\"bar\"}" // + + "{\"$nor\":[{\"location\":{\"$geoWithin\":{\"$center\":[ [ -73.99171, 40.738868 ], 0.01]}}}]},"// + + " {\"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 CountQuery.of(intermediate).toQueryDocument(); + } +} 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 eed5cbd9df..6b88137344 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 void setUp() { 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 void existsShouldUseCollationWhenPresent() { 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 void countShouldUseCollationWhenPresent() { 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 void countShouldApplyQueryHintIfPresent() { 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 void usesQueryOffsetForCountOperation() { 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 void usesQueryLimitForCountOperation() { 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 void existsShouldUseDefaultCollationWhenPresent() { 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 2daed014b9..6e5becebd9 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 void setUp() { 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 void countShouldUseSkipFromQuery() { 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 void countShouldUseLimitFromQuery() { 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 void countShouldApplyQueryHintIfPresent() { 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 bee7bee0f1..a35d32c103 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 void setUp() { 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 void countShouldUseProxiedCollection() { 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 1fe289f6fc..fd0adfe6ca 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 void countShouldWorkInTransactions() { } @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 08f1660bfe..fe9027acea 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 void countShouldUseProxiedCollection() { 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/geo/AbstractGeoSpatialTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/AbstractGeoSpatialTests.java index a96489eda8..8355c60f31 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 void geoNear() { 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 void withinPolygon() { 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 void nearSphere() { 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 f58ef2ceb2..1240bcdaf8 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 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.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 void geoNearWithMinDistance() { @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 74ce4af6cb..1adb751f55 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.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