Browse Source

Prevent key extraction if a keyset value is null.

Follow the changes in data commons that renamed scroll to window.
Also error when a certain scroll position does not allow creating a query out of it because of null values.

See: #4308
Original Pull Request: #4317
pull/4334/head
Christoph Strobl 3 years ago
parent
commit
9d0afc975a
No known key found for this signature in database
GPG Key ID: 8CC1AB53391458C8
  1. 27
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/EntityOperations.java
  2. 9
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableFindOperation.java
  3. 4
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableFindOperationSupport.java
  4. 20
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoOperations.java
  5. 12
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java
  6. 4
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveFindOperation.java
  7. 4
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveFindOperationSupport.java
  8. 14
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoOperations.java
  9. 12
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java
  10. 21
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ScrollUtils.java
  11. 4
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslMongoPredicateExecutor.java
  12. 4
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/ReactiveQuerydslMongoPredicateExecutor.java
  13. 4
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/ReactiveSpringDataMongodbQuery.java
  14. 4
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SimpleMongoRepository.java
  15. 4
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SimpleReactiveMongoRepository.java
  16. 6
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SpringDataMongodbQuery.java
  17. 52
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateScrollTests.java
  18. 10
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateScrollTests.java
  19. 12
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java
  20. 10
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java
  21. 18
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/ReactiveMongoRepositoryTests.java

27
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/EntityOperations.java

@ -21,6 +21,7 @@ import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import org.bson.BsonNull;
import org.bson.Document; import org.bson.Document;
import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.ConversionService;
import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.dao.InvalidDataAccessApiUsageException;
@ -471,6 +472,7 @@ class EntityOperations {
* @param sortObject * @param sortObject
* @return * @return
* @since 3.1 * @since 3.1
* @throws IllegalStateException if a sort key yields {@literal null}.
*/ */
Map<String, Object> extractKeys(Document sortObject); Map<String, Object> extractKeys(Document sortObject);
@ -600,7 +602,14 @@ class EntityOperations {
keyset.put(ID_FIELD, getId()); keyset.put(ID_FIELD, getId());
for (String key : sortObject.keySet()) { for (String key : sortObject.keySet()) {
keyset.put(key, BsonUtils.resolveValue(map, key)); Object value = BsonUtils.resolveValue(map, key);
if (value == null) {
throw new IllegalStateException(
String.format("Cannot extract value for key %s because its value is null", key));
}
keyset.put(key, value);
} }
return keyset; return keyset;
@ -756,14 +765,22 @@ class EntityOperations {
for (String key : sortObject.keySet()) { for (String key : sortObject.keySet()) {
Object value;
if (key.indexOf('.') != -1) { if (key.indexOf('.') != -1) {
// follow the path across nested levels. // follow the path across nested levels.
// TODO: We should have a MongoDB-specific property path abstraction to allow diving into Document. // TODO: We should have a MongoDB-specific property path abstraction to allow diving into Document.
keyset.put(key, getNestedPropertyValue(key)); value = getNestedPropertyValue(key);
} else { } else {
keyset.put(key, getPropertyValue(key)); value = getPropertyValue(key);
} }
if (value == null) {
throw new IllegalStateException(
String.format("Cannot extract value for key %s because its value is null", key));
}
keyset.put(key, value);
} }
return keyset; return keyset;
@ -774,7 +791,7 @@ class EntityOperations {
String[] segments = key.split("\\."); String[] segments = key.split("\\.");
Entity<?> currentEntity = this; Entity<?> currentEntity = this;
Object currentValue = null; Object currentValue = BsonNull.VALUE;
for (int i = 0; i < segments.length; i++) { for (int i = 0; i < segments.length; i++) {
@ -786,7 +803,7 @@ class EntityOperations {
} }
} }
return currentValue; return currentValue != null ? currentValue : BsonNull.VALUE;
} }
} }

9
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableFindOperation.java

@ -20,7 +20,7 @@ import java.util.Optional;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.springframework.dao.DataAccessException; import org.springframework.dao.DataAccessException;
import org.springframework.data.domain.Scroll; import org.springframework.data.domain.Window;
import org.springframework.data.domain.ScrollPosition; import org.springframework.data.domain.ScrollPosition;
import org.springframework.data.geo.GeoResults; import org.springframework.data.geo.GeoResults;
import org.springframework.data.mongodb.core.query.CriteriaDefinition; import org.springframework.data.mongodb.core.query.CriteriaDefinition;
@ -126,16 +126,17 @@ public interface ExecutableFindOperation {
Stream<T> stream(); Stream<T> stream();
/** /**
* Return a scroll of elements either starting or resuming at * Return a window of elements either starting or resuming at
* {@link org.springframework.data.domain.ScrollPosition}. * {@link org.springframework.data.domain.ScrollPosition}.
* *
* @param scrollPosition the scroll position. * @param scrollPosition the scroll position.
* @return a scroll of the resulting elements. * @return a window of the resulting elements.
* @throws IllegalStateException if a potential {@literal KeysetScrollPosition} contains an invalid position.
* @since 4.1 * @since 4.1
* @see org.springframework.data.domain.OffsetScrollPosition * @see org.springframework.data.domain.OffsetScrollPosition
* @see org.springframework.data.domain.KeysetScrollPosition * @see org.springframework.data.domain.KeysetScrollPosition
*/ */
Scroll<T> scroll(ScrollPosition scrollPosition); Window<T> scroll(ScrollPosition scrollPosition);
/** /**
* Get the number of matching elements. <br /> * Get the number of matching elements. <br />

4
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableFindOperationSupport.java

@ -21,7 +21,7 @@ import java.util.stream.Stream;
import org.bson.Document; import org.bson.Document;
import org.springframework.dao.IncorrectResultSizeDataAccessException; import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.data.domain.Scroll; import org.springframework.data.domain.Window;
import org.springframework.data.domain.ScrollPosition; import org.springframework.data.domain.ScrollPosition;
import org.springframework.data.mongodb.core.query.NearQuery; import org.springframework.data.mongodb.core.query.NearQuery;
import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.mongodb.core.query.Query;
@ -141,7 +141,7 @@ class ExecutableFindOperationSupport implements ExecutableFindOperation {
} }
@Override @Override
public Scroll<T> scroll(ScrollPosition scrollPosition) { public Window<T> scroll(ScrollPosition scrollPosition) {
return template.doScroll(query.with(scrollPosition), domainType, returnType, getCollectionName()); return template.doScroll(query.with(scrollPosition), domainType, returnType, getCollectionName());
} }

20
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoOperations.java

@ -24,7 +24,7 @@ import java.util.stream.Stream;
import org.bson.Document; import org.bson.Document;
import org.springframework.data.domain.KeysetScrollPosition; import org.springframework.data.domain.KeysetScrollPosition;
import org.springframework.data.domain.Scroll; import org.springframework.data.domain.Window;
import org.springframework.data.geo.GeoResults; import org.springframework.data.geo.GeoResults;
import org.springframework.data.mongodb.core.BulkOperations.BulkMode; import org.springframework.data.mongodb.core.BulkOperations.BulkMode;
import org.springframework.data.mongodb.core.aggregation.Aggregation; import org.springframework.data.mongodb.core.aggregation.Aggregation;
@ -807,7 +807,7 @@ public interface MongoOperations extends FluentMongoOperations {
<T> List<T> find(Query query, Class<T> entityClass, String collectionName); <T> List<T> find(Query query, Class<T> entityClass, String collectionName);
/** /**
* Query for a scroll window of objects of type T from the specified collection. <br /> * Query for a window window of objects of type T from the specified collection. <br />
* Make sure to either set {@link Query#skip(long)} or {@link Query#with(KeysetScrollPosition)} along with * Make sure to either set {@link Query#skip(long)} or {@link Query#with(KeysetScrollPosition)} along with
* {@link Query#limit(int)} to limit large query results for efficient scrolling. <br /> * {@link Query#limit(int)} to limit large query results for efficient scrolling. <br />
* Result objects are converted from the MongoDB native representation using an instance of {@see MongoConverter}. * Result objects are converted from the MongoDB native representation using an instance of {@see MongoConverter}.
@ -817,16 +817,17 @@ public interface MongoOperations extends FluentMongoOperations {
* *
* @param query the query class that specifies the criteria used to find a record and also an optional fields * @param query the query class that specifies the criteria used to find a record and also an optional fields
* specification. Must not be {@literal null}. * specification. Must not be {@literal null}.
* @param entityType the parametrized type of the returned list. * @param entityType the parametrized type of the returned window.
* @return the converted scroll. * @return the converted window.
* @throws IllegalStateException if a potential {@link Query#getKeyset() KeysetScrollPosition} contains an invalid position.
* @since 4.1 * @since 4.1
* @see Query#with(org.springframework.data.domain.OffsetScrollPosition) * @see Query#with(org.springframework.data.domain.OffsetScrollPosition)
* @see Query#with(org.springframework.data.domain.KeysetScrollPosition) * @see Query#with(org.springframework.data.domain.KeysetScrollPosition)
*/ */
<T> Scroll<T> scroll(Query query, Class<T> entityType); <T> Window<T> scroll(Query query, Class<T> entityType);
/** /**
* Query for a scroll of objects of type T from the specified collection. <br /> * Query for a window of objects of type T from the specified collection. <br />
* Make sure to either set {@link Query#skip(long)} or {@link Query#with(KeysetScrollPosition)} along with * Make sure to either set {@link Query#skip(long)} or {@link Query#with(KeysetScrollPosition)} along with
* {@link Query#limit(int)} to limit large query results for efficient scrolling. <br /> * {@link Query#limit(int)} to limit large query results for efficient scrolling. <br />
* Result objects are converted from the MongoDB native representation using an instance of {@see MongoConverter}. * Result objects are converted from the MongoDB native representation using an instance of {@see MongoConverter}.
@ -836,14 +837,15 @@ public interface MongoOperations extends FluentMongoOperations {
* *
* @param query the query class that specifies the criteria used to find a record and also an optional fields * @param query the query class that specifies the criteria used to find a record and also an optional fields
* specification. Must not be {@literal null}. * specification. Must not be {@literal null}.
* @param entityType the parametrized type of the returned list. * @param entityType the parametrized type of the returned window.
* @param collectionName name of the collection to retrieve the objects from. * @param collectionName name of the collection to retrieve the objects from.
* @return the converted scroll. * @return the converted window.
* @throws IllegalStateException if a potential {@link Query#getKeyset() KeysetScrollPosition} contains an invalid position.
* @since 4.1 * @since 4.1
* @see Query#with(org.springframework.data.domain.OffsetScrollPosition) * @see Query#with(org.springframework.data.domain.OffsetScrollPosition)
* @see Query#with(org.springframework.data.domain.KeysetScrollPosition) * @see Query#with(org.springframework.data.domain.KeysetScrollPosition)
*/ */
<T> Scroll<T> scroll(Query query, Class<T> entityType, String collectionName); <T> Window<T> scroll(Query query, Class<T> entityType, String collectionName);
/** /**
* Returns a document with the given id mapped onto the given class. The collection the query is ran against will be * Returns a document with the given id mapped onto the given class. The collection the query is ran against will be

12
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java

@ -45,7 +45,7 @@ import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.dao.support.PersistenceExceptionTranslator; import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.data.convert.EntityReader; import org.springframework.data.convert.EntityReader;
import org.springframework.data.domain.OffsetScrollPosition; import org.springframework.data.domain.OffsetScrollPosition;
import org.springframework.data.domain.Scroll; import org.springframework.data.domain.Window;
import org.springframework.data.geo.Distance; import org.springframework.data.geo.Distance;
import org.springframework.data.geo.GeoResult; import org.springframework.data.geo.GeoResult;
import org.springframework.data.geo.GeoResults; import org.springframework.data.geo.GeoResults;
@ -66,7 +66,7 @@ import org.springframework.data.mongodb.core.QueryOperations.DeleteContext;
import org.springframework.data.mongodb.core.QueryOperations.DistinctQueryContext; import org.springframework.data.mongodb.core.QueryOperations.DistinctQueryContext;
import org.springframework.data.mongodb.core.QueryOperations.QueryContext; import org.springframework.data.mongodb.core.QueryOperations.QueryContext;
import org.springframework.data.mongodb.core.QueryOperations.UpdateContext; import org.springframework.data.mongodb.core.QueryOperations.UpdateContext;
import org.springframework.data.mongodb.core.ScrollUtils.KeySetCursorQuery; import org.springframework.data.mongodb.core.ScrollUtils.KeySetScrollQuery;
import org.springframework.data.mongodb.core.aggregation.Aggregation; import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationOperationContext; import org.springframework.data.mongodb.core.aggregation.AggregationOperationContext;
import org.springframework.data.mongodb.core.aggregation.AggregationOptions; import org.springframework.data.mongodb.core.aggregation.AggregationOptions;
@ -851,7 +851,7 @@ public class MongoTemplate
} }
@Override @Override
public <T> Scroll<T> scroll(Query query, Class<T> entityType) { public <T> Window<T> scroll(Query query, Class<T> entityType) {
Assert.notNull(entityType, "Entity type must not be null"); Assert.notNull(entityType, "Entity type must not be null");
@ -859,11 +859,11 @@ public class MongoTemplate
} }
@Override @Override
public <T> Scroll<T> scroll(Query query, Class<T> entityType, String collectionName) { public <T> Window<T> scroll(Query query, Class<T> entityType, String collectionName) {
return doScroll(query, entityType, entityType, collectionName); return doScroll(query, entityType, entityType, collectionName);
} }
<T> Scroll<T> doScroll(Query query, Class<?> sourceClass, Class<T> targetClass, String collectionName) { <T> Window<T> doScroll(Query query, Class<?> sourceClass, Class<T> targetClass, String collectionName) {
Assert.notNull(query, "Query must not be null"); Assert.notNull(query, "Query must not be null");
Assert.notNull(collectionName, "CollectionName must not be null"); Assert.notNull(collectionName, "CollectionName must not be null");
@ -875,7 +875,7 @@ public class MongoTemplate
if (query.hasKeyset()) { if (query.hasKeyset()) {
KeySetCursorQuery keysetPaginationQuery = ScrollUtils.createKeysetPaginationQuery(query, KeySetScrollQuery keysetPaginationQuery = ScrollUtils.createKeysetPaginationQuery(query,
operations.getIdPropertyName(sourceClass)); operations.getIdPropertyName(sourceClass));
List<T> result = doFind(collectionName, createDelegate(query), keysetPaginationQuery.query(), List<T> result = doFind(collectionName, createDelegate(query), keysetPaginationQuery.query(),

4
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveFindOperation.java

@ -18,7 +18,7 @@ package org.springframework.data.mongodb.core;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import org.springframework.data.domain.Scroll; import org.springframework.data.domain.Window;
import org.springframework.data.domain.ScrollPosition; import org.springframework.data.domain.ScrollPosition;
import org.springframework.data.geo.GeoResult; import org.springframework.data.geo.GeoResult;
import org.springframework.data.mongodb.core.query.CriteriaDefinition; import org.springframework.data.mongodb.core.query.CriteriaDefinition;
@ -98,7 +98,7 @@ public interface ReactiveFindOperation {
* @see org.springframework.data.domain.OffsetScrollPosition * @see org.springframework.data.domain.OffsetScrollPosition
* @see org.springframework.data.domain.KeysetScrollPosition * @see org.springframework.data.domain.KeysetScrollPosition
*/ */
Mono<Scroll<T>> scroll(ScrollPosition scrollPosition); Mono<Window<T>> scroll(ScrollPosition scrollPosition);
/** /**
* Get all matching elements using a {@link com.mongodb.CursorType#TailableAwait tailable cursor}. The stream will * Get all matching elements using a {@link com.mongodb.CursorType#TailableAwait tailable cursor}. The stream will

4
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveFindOperationSupport.java

@ -20,7 +20,7 @@ import reactor.core.publisher.Mono;
import org.bson.Document; import org.bson.Document;
import org.springframework.dao.IncorrectResultSizeDataAccessException; import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.data.domain.Scroll; import org.springframework.data.domain.Window;
import org.springframework.data.domain.ScrollPosition; import org.springframework.data.domain.ScrollPosition;
import org.springframework.data.mongodb.core.CollectionPreparerSupport.ReactiveCollectionPreparerDelegate; import org.springframework.data.mongodb.core.CollectionPreparerSupport.ReactiveCollectionPreparerDelegate;
import org.springframework.data.mongodb.core.query.NearQuery; import org.springframework.data.mongodb.core.query.NearQuery;
@ -140,7 +140,7 @@ class ReactiveFindOperationSupport implements ReactiveFindOperation {
} }
@Override @Override
public Mono<Scroll<T>> scroll(ScrollPosition scrollPosition) { public Mono<Window<T>> scroll(ScrollPosition scrollPosition) {
return template.doScroll(query.with(scrollPosition), domainType, returnType, getCollectionName()); return template.doScroll(query.with(scrollPosition), domainType, returnType, getCollectionName());
} }

14
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoOperations.java

@ -26,7 +26,7 @@ import org.bson.Document;
import org.reactivestreams.Publisher; import org.reactivestreams.Publisher;
import org.reactivestreams.Subscription; import org.reactivestreams.Subscription;
import org.springframework.data.domain.KeysetScrollPosition; import org.springframework.data.domain.KeysetScrollPosition;
import org.springframework.data.domain.Scroll; import org.springframework.data.domain.Window;
import org.springframework.data.geo.GeoResult; import org.springframework.data.geo.GeoResult;
import org.springframework.data.mongodb.ReactiveMongoDatabaseFactory; import org.springframework.data.mongodb.ReactiveMongoDatabaseFactory;
import org.springframework.data.mongodb.core.aggregation.Aggregation; import org.springframework.data.mongodb.core.aggregation.Aggregation;
@ -477,15 +477,16 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
* @param query the query class that specifies the criteria used to find a record and also an optional fields * @param query the query class that specifies the criteria used to find a record and also an optional fields
* specification. Must not be {@literal null}. * specification. Must not be {@literal null}.
* @param entityType the parametrized type of the returned list. * @param entityType the parametrized type of the returned list.
* @return {@link Mono} emitting the converted scroll. * @return {@link Mono} emitting the converted window.
* @throws IllegalStateException if a potential {@link Query#getKeyset() KeysetScrollPosition} contains an invalid position.
* @since 4.1 * @since 4.1
* @see Query#with(org.springframework.data.domain.OffsetScrollPosition) * @see Query#with(org.springframework.data.domain.OffsetScrollPosition)
* @see Query#with(org.springframework.data.domain.KeysetScrollPosition) * @see Query#with(org.springframework.data.domain.KeysetScrollPosition)
*/ */
<T> Mono<Scroll<T>> scroll(Query query, Class<T> entityType); <T> Mono<Window<T>> scroll(Query query, Class<T> entityType);
/** /**
* Query for a scroll of objects of type T from the specified collection. <br /> * Query for a window of objects of type T from the specified collection. <br />
* Make sure to either set {@link Query#skip(long)} or {@link Query#with(KeysetScrollPosition)} along with * Make sure to either set {@link Query#skip(long)} or {@link Query#with(KeysetScrollPosition)} along with
* {@link Query#limit(int)} to limit large query results for efficient scrolling. <br /> * {@link Query#limit(int)} to limit large query results for efficient scrolling. <br />
* Result objects are converted from the MongoDB native representation using an instance of {@see MongoConverter}. * Result objects are converted from the MongoDB native representation using an instance of {@see MongoConverter}.
@ -497,12 +498,13 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
* specification. Must not be {@literal null}. * specification. Must not be {@literal null}.
* @param entityType the parametrized type of the returned list. * @param entityType the parametrized type of the returned list.
* @param collectionName name of the collection to retrieve the objects from. * @param collectionName name of the collection to retrieve the objects from.
* @return {@link Mono} emitting the converted scroll window. * @return {@link Mono} emitting the converted window.
* @throws IllegalStateException if a potential {@link Query#getKeyset() KeysetScrollPosition} contains an invalid position.
* @since 4.1 * @since 4.1
* @see Query#with(org.springframework.data.domain.OffsetScrollPosition) * @see Query#with(org.springframework.data.domain.OffsetScrollPosition)
* @see Query#with(org.springframework.data.domain.KeysetScrollPosition) * @see Query#with(org.springframework.data.domain.KeysetScrollPosition)
*/ */
<T> Mono<Scroll<T>> scroll(Query query, Class<T> entityType, String collectionName); <T> Mono<Window<T>> scroll(Query query, Class<T> entityType, String collectionName);
/** /**
* Returns a document with the given id mapped onto the given class. The collection the query is ran against will be * Returns a document with the given id mapped onto the given class. The collection the query is ran against will be

12
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java

@ -59,7 +59,7 @@ import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.dao.support.PersistenceExceptionTranslator; import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.data.convert.EntityReader; import org.springframework.data.convert.EntityReader;
import org.springframework.data.domain.OffsetScrollPosition; import org.springframework.data.domain.OffsetScrollPosition;
import org.springframework.data.domain.Scroll; import org.springframework.data.domain.Window;
import org.springframework.data.geo.Distance; import org.springframework.data.geo.Distance;
import org.springframework.data.geo.GeoResult; import org.springframework.data.geo.GeoResult;
import org.springframework.data.geo.Metric; import org.springframework.data.geo.Metric;
@ -80,7 +80,7 @@ import org.springframework.data.mongodb.core.QueryOperations.DeleteContext;
import org.springframework.data.mongodb.core.QueryOperations.DistinctQueryContext; import org.springframework.data.mongodb.core.QueryOperations.DistinctQueryContext;
import org.springframework.data.mongodb.core.QueryOperations.QueryContext; import org.springframework.data.mongodb.core.QueryOperations.QueryContext;
import org.springframework.data.mongodb.core.QueryOperations.UpdateContext; import org.springframework.data.mongodb.core.QueryOperations.UpdateContext;
import org.springframework.data.mongodb.core.ScrollUtils.KeySetCursorQuery; import org.springframework.data.mongodb.core.ScrollUtils.KeySetScrollQuery;
import org.springframework.data.mongodb.core.aggregation.Aggregation; import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationOperationContext; import org.springframework.data.mongodb.core.aggregation.AggregationOperationContext;
import org.springframework.data.mongodb.core.aggregation.AggregationOptions; import org.springframework.data.mongodb.core.aggregation.AggregationOptions;
@ -830,7 +830,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
} }
@Override @Override
public <T> Mono<Scroll<T>> scroll(Query query, Class<T> entityType) { public <T> Mono<Window<T>> scroll(Query query, Class<T> entityType) {
Assert.notNull(entityType, "Entity type must not be null"); Assert.notNull(entityType, "Entity type must not be null");
@ -838,11 +838,11 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
} }
@Override @Override
public <T> Mono<Scroll<T>> scroll(Query query, Class<T> entityType, String collectionName) { public <T> Mono<Window<T>> scroll(Query query, Class<T> entityType, String collectionName) {
return doScroll(query, entityType, entityType, collectionName); return doScroll(query, entityType, entityType, collectionName);
} }
<T> Mono<Scroll<T>> doScroll(Query query, Class<?> sourceClass, Class<T> targetClass, String collectionName) { <T> Mono<Window<T>> doScroll(Query query, Class<?> sourceClass, Class<T> targetClass, String collectionName) {
Assert.notNull(query, "Query must not be null"); Assert.notNull(query, "Query must not be null");
Assert.notNull(collectionName, "CollectionName must not be null"); Assert.notNull(collectionName, "CollectionName must not be null");
@ -853,7 +853,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
if (query.hasKeyset()) { if (query.hasKeyset()) {
KeySetCursorQuery keysetPaginationQuery = ScrollUtils.createKeysetPaginationQuery(query, KeySetScrollQuery keysetPaginationQuery = ScrollUtils.createKeysetPaginationQuery(query,
operations.getIdPropertyName(sourceClass)); operations.getIdPropertyName(sourceClass));
Mono<List<T>> result = doFind(collectionName, ReactiveCollectionPreparerDelegate.of(query), Mono<List<T>> result = doFind(collectionName, ReactiveCollectionPreparerDelegate.of(query),

21
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ScrollUtils.java

@ -20,17 +20,19 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.IntFunction; import java.util.function.IntFunction;
import org.bson.BsonNull;
import org.bson.Document; import org.bson.Document;
import org.springframework.data.domain.KeysetScrollPosition; import org.springframework.data.domain.KeysetScrollPosition;
import org.springframework.data.domain.Scroll;
import org.springframework.data.domain.ScrollPosition; import org.springframework.data.domain.ScrollPosition;
import org.springframework.data.domain.Window;
import org.springframework.data.mongodb.core.EntityOperations.Entity; import org.springframework.data.mongodb.core.EntityOperations.Entity;
import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.mongodb.core.query.Query;
/** /**
* Utilities to run scroll queries and create {@link Scroll} results. * Utilities to run scroll queries and create {@link Window} results.
* *
* @author Mark Paluch * @author Mark Paluch
* @author Christoph Strobl
* @since 4.1 * @since 4.1
*/ */
class ScrollUtils { class ScrollUtils {
@ -42,7 +44,7 @@ class ScrollUtils {
* @param idPropertyName * @param idPropertyName
* @return * @return
*/ */
static KeySetCursorQuery createKeysetPaginationQuery(Query query, String idPropertyName) { static KeySetScrollQuery createKeysetPaginationQuery(Query query, String idPropertyName) {
Document sortObject = query.isSorted() ? query.getSortObject() : new Document(); Document sortObject = query.isSorted() ? query.getSortObject() : new Document();
sortObject.put(idPropertyName, 1); sortObject.put(idPropertyName, 1);
@ -84,6 +86,9 @@ class ScrollUtils {
Object o = keysetValues.get(sortSegment); Object o = keysetValues.get(sortSegment);
if (j >= i) { // tail segment if (j >= i) { // tail segment
if(o instanceof BsonNull) {
throw new IllegalStateException("Cannot resume from KeysetScrollPosition. Offending key: '%s' is 'null'".formatted(sortSegment));
}
sortConstraint.put(sortSegment, new Document(sortOrder == 1 ? "$gt" : "$lt", o)); sortConstraint.put(sortSegment, new Document(sortOrder == 1 ? "$gt" : "$lt", o));
break; break;
} }
@ -104,10 +109,10 @@ class ScrollUtils {
queryObject.put("$or", or); queryObject.put("$or", or);
} }
return new KeySetCursorQuery(queryObject, fieldsObject, sortObject); return new KeySetScrollQuery(queryObject, fieldsObject, sortObject);
} }
static <T> Scroll<T> createWindow(Document sortObject, int limit, List<T> result, EntityOperations operations) { static <T> Window<T> createWindow(Document sortObject, int limit, List<T> result, EntityOperations operations) {
IntFunction<KeysetScrollPosition> positionFunction = value -> { IntFunction<KeysetScrollPosition> positionFunction = value -> {
@ -121,8 +126,8 @@ class ScrollUtils {
return createWindow(result, limit, positionFunction); return createWindow(result, limit, positionFunction);
} }
static <T> Scroll<T> createWindow(List<T> result, int limit, IntFunction<? extends ScrollPosition> positionFunction) { static <T> Window<T> createWindow(List<T> result, int limit, IntFunction<? extends ScrollPosition> positionFunction) {
return Scroll.from(getSubList(result, limit), positionFunction, hasMoreElements(result, limit)); return Window.from(getSubList(result, limit), positionFunction, hasMoreElements(result, limit));
} }
static boolean hasMoreElements(List<?> result, int limit) { static boolean hasMoreElements(List<?> result, int limit) {
@ -138,7 +143,7 @@ class ScrollUtils {
return result; return result;
} }
record KeySetCursorQuery(Document query, Document fields, Document sort) { record KeySetScrollQuery(Document query, Document fields, Document sort) {
} }

4
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslMongoPredicateExecutor.java

@ -25,7 +25,7 @@ import org.bson.Document;
import org.springframework.dao.IncorrectResultSizeDataAccessException; import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Scroll; import org.springframework.data.domain.Window;
import org.springframework.data.domain.ScrollPosition; import org.springframework.data.domain.ScrollPosition;
import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoOperations; import org.springframework.data.mongodb.core.MongoOperations;
@ -259,7 +259,7 @@ public class QuerydslMongoPredicateExecutor<T> extends QuerydslPredicateExecutor
} }
@Override @Override
public Scroll<T> scroll(ScrollPosition scrollPosition) { public Window<T> scroll(ScrollPosition scrollPosition) {
return createQuery().scroll(scrollPosition); return createQuery().scroll(scrollPosition);
} }

4
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/ReactiveQuerydslMongoPredicateExecutor.java

@ -26,7 +26,7 @@ import org.bson.Document;
import org.reactivestreams.Publisher; import org.reactivestreams.Publisher;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Scroll; import org.springframework.data.domain.Window;
import org.springframework.data.domain.ScrollPosition; import org.springframework.data.domain.ScrollPosition;
import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.ReactiveMongoOperations; import org.springframework.data.mongodb.core.ReactiveMongoOperations;
@ -227,7 +227,7 @@ public class ReactiveQuerydslMongoPredicateExecutor<T> extends QuerydslPredicate
} }
@Override @Override
public Mono<Scroll<T>> scroll(ScrollPosition scrollPosition) { public Mono<Window<T>> scroll(ScrollPosition scrollPosition) {
return createQuery().scroll(scrollPosition); return createQuery().scroll(scrollPosition);
} }

4
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/ReactiveSpringDataMongodbQuery.java

@ -26,7 +26,7 @@ import java.util.function.Consumer;
import org.bson.Document; import org.bson.Document;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Scroll; import org.springframework.data.domain.Window;
import org.springframework.data.domain.ScrollPosition; import org.springframework.data.domain.ScrollPosition;
import org.springframework.data.mongodb.core.MongoOperations; import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.ReactiveFindOperation; import org.springframework.data.mongodb.core.ReactiveFindOperation;
@ -91,7 +91,7 @@ class ReactiveSpringDataMongodbQuery<K> extends SpringDataMongodbQuerySupport<Re
return createQuery().flatMapMany(it -> find.matching(it).all()); return createQuery().flatMapMany(it -> find.matching(it).all());
} }
Mono<Scroll<K>> scroll(ScrollPosition scrollPosition) { Mono<Window<K>> scroll(ScrollPosition scrollPosition) {
return createQuery().flatMap(it -> find.matching(it).scroll(scrollPosition)); return createQuery().flatMap(it -> find.matching(it).scroll(scrollPosition));
} }

4
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SimpleMongoRepository.java

@ -32,7 +32,7 @@ import org.springframework.data.domain.Example;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Scroll; import org.springframework.data.domain.Window;
import org.springframework.data.domain.ScrollPosition; import org.springframework.data.domain.ScrollPosition;
import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.ExecutableFindOperation; import org.springframework.data.mongodb.core.ExecutableFindOperation;
@ -392,7 +392,7 @@ public class SimpleMongoRepository<T, ID> implements MongoRepository<T, ID> {
} }
@Override @Override
public Scroll<T> scroll(ScrollPosition scrollPosition) { public Window<T> scroll(ScrollPosition scrollPosition) {
return createQuery().scroll(scrollPosition); return createQuery().scroll(scrollPosition);
} }

4
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SimpleReactiveMongoRepository.java

@ -34,7 +34,7 @@ import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.data.domain.Example; import org.springframework.data.domain.Example;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Scroll; import org.springframework.data.domain.Window;
import org.springframework.data.domain.ScrollPosition; import org.springframework.data.domain.ScrollPosition;
import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.ReactiveFindOperation; import org.springframework.data.mongodb.core.ReactiveFindOperation;
@ -436,7 +436,7 @@ public class SimpleReactiveMongoRepository<T, ID extends Serializable> implement
} }
@Override @Override
public Mono<Scroll<T>> scroll(ScrollPosition scrollPosition) { public Mono<Window<T>> scroll(ScrollPosition scrollPosition) {
return createQuery().scroll(scrollPosition); return createQuery().scroll(scrollPosition);
} }

6
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SpringDataMongodbQuery.java

@ -25,7 +25,7 @@ import org.bson.Document;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Scroll; import org.springframework.data.domain.Window;
import org.springframework.data.domain.ScrollPosition; import org.springframework.data.domain.ScrollPosition;
import org.springframework.data.mongodb.core.ExecutableFindOperation; import org.springframework.data.mongodb.core.ExecutableFindOperation;
import org.springframework.data.mongodb.core.MongoOperations; import org.springframework.data.mongodb.core.MongoOperations;
@ -133,12 +133,12 @@ public class SpringDataMongodbQuery<T> extends SpringDataMongodbQuerySupport<Spr
} }
} }
public Scroll<T> scroll(ScrollPosition scrollPosition) { public Window<T> scroll(ScrollPosition scrollPosition) {
try { try {
return find.matching(createQuery()).scroll(scrollPosition); return find.matching(createQuery()).scroll(scrollPosition);
} catch (RuntimeException e) { } catch (RuntimeException e) {
return handleException(e, Scroll.from(Collections.emptyList(), value -> { return handleException(e, Window.from(Collections.emptyList(), value -> {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
})); }));
} }

52
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateScrollTests.java

@ -38,9 +38,9 @@ import org.springframework.data.annotation.PersistenceCreator;
import org.springframework.data.auditing.IsNewAwareAuditingHandler; import org.springframework.data.auditing.IsNewAwareAuditingHandler;
import org.springframework.data.domain.KeysetScrollPosition; import org.springframework.data.domain.KeysetScrollPosition;
import org.springframework.data.domain.OffsetScrollPosition; import org.springframework.data.domain.OffsetScrollPosition;
import org.springframework.data.domain.Scroll;
import org.springframework.data.domain.ScrollPosition; import org.springframework.data.domain.ScrollPosition;
import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Window;
import org.springframework.data.mapping.context.PersistentEntities; import org.springframework.data.mapping.context.PersistentEntities;
import org.springframework.data.mongodb.core.MongoTemplateTests.PersonWithIdPropertyOfTypeUUIDListener; import org.springframework.data.mongodb.core.MongoTemplateTests.PersonWithIdPropertyOfTypeUUIDListener;
import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.mongodb.core.query.Query;
@ -51,9 +51,10 @@ import org.springframework.data.mongodb.test.util.MongoTestTemplate;
import com.mongodb.client.MongoClient; import com.mongodb.client.MongoClient;
/** /**
* Integration tests for {@link Scroll} queries. * Integration tests for {@link org.springframework.data.domain.Window} queries.
* *
* @author Mark Paluch * @author Mark Paluch
* @author Christoph Strobl
*/ */
@ExtendWith(MongoClientExtension.class) @ExtendWith(MongoClientExtension.class)
class MongoTemplateScrollTests { class MongoTemplateScrollTests {
@ -94,7 +95,7 @@ class MongoTemplateScrollTests {
template.remove(WithNestedDocument.class).all(); template.remove(WithNestedDocument.class).all();
} }
@Test @Test // GH-4308
void shouldUseKeysetScrollingWithNestedSort() { void shouldUseKeysetScrollingWithNestedSort() {
WithNestedDocument john20 = new WithNestedDocument(null, "John", 120, new WithNestedDocument("John", 20), WithNestedDocument john20 = new WithNestedDocument(null, "John", 120, new WithNestedDocument("John", 20),
@ -110,20 +111,55 @@ class MongoTemplateScrollTests {
.limit(2); .limit(2);
q.with(KeysetScrollPosition.initial()); q.with(KeysetScrollPosition.initial());
Scroll<WithNestedDocument> scroll = template.scroll(q, WithNestedDocument.class); Window<WithNestedDocument> scroll = template.scroll(q, WithNestedDocument.class);
assertThat(scroll.hasNext()).isTrue(); assertThat(scroll.hasNext()).isTrue();
assertThat(scroll.isLast()).isFalse(); assertThat(scroll.isLast()).isFalse();
assertThat(scroll).hasSize(2); assertThat(scroll).hasSize(2);
assertThat(scroll).containsOnly(john20, john40); assertThat(scroll).containsOnly(john20, john40);
scroll = template.scroll(q.with(scroll.lastPosition()), WithNestedDocument.class); scroll = template.scroll(q.with(scroll.positionAt(scroll.size()-1)), WithNestedDocument.class);
assertThat(scroll.hasNext()).isFalse(); assertThat(scroll.hasNext()).isFalse();
assertThat(scroll.isLast()).isTrue(); assertThat(scroll.isLast()).isTrue();
assertThat(scroll).hasSize(1); assertThat(scroll).hasSize(1);
assertThat(scroll).containsOnly(john41); assertThat(scroll).containsOnly(john41);
}
@Test // GH-4308
void shouldErrorOnNullValueForQuery() {
WithNestedDocument john20 = new WithNestedDocument(null, "John", 120, new WithNestedDocument("John", 20),
new Document("name", "bar"));
WithNestedDocument john40 = new WithNestedDocument(null, "John", 140, new WithNestedDocument("John", 41),
new Document());
WithNestedDocument john41 = new WithNestedDocument(null, "John", 140, new WithNestedDocument("John", 41),
new Document());
WithNestedDocument john42 = new WithNestedDocument(null, "John", 140, new WithNestedDocument("John", 41),
new Document());
WithNestedDocument john43 = new WithNestedDocument(null, "John", 140, new WithNestedDocument("John", 41),
new Document());
WithNestedDocument john44 = new WithNestedDocument(null, "John", 141, new WithNestedDocument("John", 41),
new Document("name", "foo"));
template.insertAll(Arrays.asList(john20, john40, john41, john42, john43, john44));
Query q = new Query(where("name").regex("J.*")).with(Sort.by("nested.name", "nested.age", "document.name"))
.limit(2);
q.with(KeysetScrollPosition.initial());
Window<WithNestedDocument> scroll = template.scroll(q, WithNestedDocument.class);
assertThat(scroll.hasNext()).isTrue();
assertThat(scroll.isLast()).isFalse();
assertThat(scroll).hasSize(2);
assertThat(scroll).containsOnly(john20, john40);
ScrollPosition startAfter = scroll.positionAt(scroll.size()-1);
assertThatExceptionOfType(IllegalStateException.class)
.isThrownBy(() -> template.scroll(q.with(startAfter), WithNestedDocument.class))
.withMessageContaining("document.name");
} }
@ParameterizedTest // GH-4308 @ParameterizedTest // GH-4308
@ -142,14 +178,14 @@ class MongoTemplateScrollTests {
Query q = new Query(where("firstName").regex("J.*")).with(Sort.by("firstName", "age")).limit(2); Query q = new Query(where("firstName").regex("J.*")).with(Sort.by("firstName", "age")).limit(2);
q.with(scrollPosition); q.with(scrollPosition);
Scroll<T> scroll = template.scroll(q, resultType, "person"); Window<T> scroll = template.scroll(q, resultType, "person");
assertThat(scroll.hasNext()).isTrue(); assertThat(scroll.hasNext()).isTrue();
assertThat(scroll.isLast()).isFalse(); assertThat(scroll.isLast()).isFalse();
assertThat(scroll).hasSize(2); assertThat(scroll).hasSize(2);
assertThat(scroll).containsOnly(assertionConverter.apply(jane_20), assertionConverter.apply(jane_40)); assertThat(scroll).containsOnly(assertionConverter.apply(jane_20), assertionConverter.apply(jane_40));
scroll = template.scroll(q.with(scroll.lastPosition()).limit(3), resultType, "person"); scroll = template.scroll(q.with(scroll.positionAt(scroll.size()-1)).limit(3), resultType, "person");
assertThat(scroll.hasNext()).isTrue(); assertThat(scroll.hasNext()).isTrue();
assertThat(scroll.isLast()).isFalse(); assertThat(scroll.isLast()).isFalse();
@ -157,7 +193,7 @@ class MongoTemplateScrollTests {
assertThat(scroll).contains(assertionConverter.apply(jane_42), assertionConverter.apply(john20)); assertThat(scroll).contains(assertionConverter.apply(jane_42), assertionConverter.apply(john20));
assertThat(scroll).containsAnyOf(assertionConverter.apply(john40_1), assertionConverter.apply(john40_2)); assertThat(scroll).containsAnyOf(assertionConverter.apply(john40_1), assertionConverter.apply(john40_2));
scroll = template.scroll(q.with(scroll.lastPosition()).limit(1), resultType, "person"); scroll = template.scroll(q.with(scroll.positionAt(scroll.size()-1)).limit(1), resultType, "person");
assertThat(scroll.hasNext()).isFalse(); assertThat(scroll.hasNext()).isFalse();
assertThat(scroll.isLast()).isTrue(); assertThat(scroll.isLast()).isTrue();

10
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateScrollTests.java

@ -35,7 +35,7 @@ import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.GenericApplicationContext; import org.springframework.context.support.GenericApplicationContext;
import org.springframework.data.domain.KeysetScrollPosition; import org.springframework.data.domain.KeysetScrollPosition;
import org.springframework.data.domain.OffsetScrollPosition; import org.springframework.data.domain.OffsetScrollPosition;
import org.springframework.data.domain.Scroll; import org.springframework.data.domain.Window;
import org.springframework.data.domain.ScrollPosition; import org.springframework.data.domain.ScrollPosition;
import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.mongodb.core.query.Query;
@ -46,7 +46,7 @@ import org.springframework.data.mongodb.test.util.ReactiveMongoTestTemplate;
import com.mongodb.reactivestreams.client.MongoClient; import com.mongodb.reactivestreams.client.MongoClient;
/** /**
* Integration tests for {@link Scroll} queries. * Integration tests for {@link Window} queries.
* *
* @author Mark Paluch * @author Mark Paluch
*/ */
@ -100,14 +100,14 @@ class ReactiveMongoTemplateScrollTests {
Query q = new Query(where("firstName").regex("J.*")).with(Sort.by("firstName", "age")).limit(2); Query q = new Query(where("firstName").regex("J.*")).with(Sort.by("firstName", "age")).limit(2);
q.with(scrollPosition); q.with(scrollPosition);
Scroll<T> scroll = template.scroll(q, resultType, "person").block(Duration.ofSeconds(10)); Window<T> scroll = template.scroll(q, resultType, "person").block(Duration.ofSeconds(10));
assertThat(scroll.hasNext()).isTrue(); assertThat(scroll.hasNext()).isTrue();
assertThat(scroll.isLast()).isFalse(); assertThat(scroll.isLast()).isFalse();
assertThat(scroll).hasSize(2); assertThat(scroll).hasSize(2);
assertThat(scroll).containsOnly(assertionConverter.apply(jane_20), assertionConverter.apply(jane_40)); assertThat(scroll).containsOnly(assertionConverter.apply(jane_20), assertionConverter.apply(jane_40));
scroll = template.scroll(q.limit(3).with(scroll.lastPosition()), resultType, "person") scroll = template.scroll(q.limit(3).with(scroll.positionAt(scroll.size() - 1)), resultType, "person")
.block(Duration.ofSeconds(10)); .block(Duration.ofSeconds(10));
assertThat(scroll.hasNext()).isTrue(); assertThat(scroll.hasNext()).isTrue();
@ -116,7 +116,7 @@ class ReactiveMongoTemplateScrollTests {
assertThat(scroll).contains(assertionConverter.apply(jane_42), assertionConverter.apply(john20)); assertThat(scroll).contains(assertionConverter.apply(jane_42), assertionConverter.apply(john20));
assertThat(scroll).containsAnyOf(assertionConverter.apply(john40_1), assertionConverter.apply(john40_2)); assertThat(scroll).containsAnyOf(assertionConverter.apply(john40_1), assertionConverter.apply(john40_2));
scroll = template.scroll(q.limit(1).with(scroll.lastPosition()), resultType, "person") scroll = template.scroll(q.limit(1).with(scroll.positionAt(scroll.size() - 1)), resultType, "person")
.block(Duration.ofSeconds(10)); .block(Duration.ofSeconds(10));
assertThat(scroll.hasNext()).isFalse(); assertThat(scroll.hasNext()).isFalse();

12
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java

@ -205,7 +205,7 @@ public abstract class AbstractPersonRepositoryIntegrationTests implements Dirtie
@Test // GH-4308 @Test // GH-4308
void appliesScrollPositionCorrectly() { void appliesScrollPositionCorrectly() {
Scroll<Person> page = repository.findTop2ByLastnameLikeOrderByLastnameAscFirstnameAsc("*a*", Window<Person> page = repository.findTop2ByLastnameLikeOrderByLastnameAscFirstnameAsc("*a*",
KeysetScrollPosition.initial()); KeysetScrollPosition.initial());
assertThat(page.isLast()).isFalse(); assertThat(page.isLast()).isFalse();
@ -216,7 +216,7 @@ public abstract class AbstractPersonRepositoryIntegrationTests implements Dirtie
@Test // GH-4308 @Test // GH-4308
void appliesScrollPositionWithProjectionCorrectly() { void appliesScrollPositionWithProjectionCorrectly() {
Scroll<PersonSummaryDto> page = repository.findCursorProjectionByLastnameLike("*a*", Window<PersonSummaryDto> page = repository.findCursorProjectionByLastnameLike("*a*",
PageRequest.of(0, 2, Sort.by(Direction.ASC, "lastname", "firstname"))); PageRequest.of(0, 2, Sort.by(Direction.ASC, "lastname", "firstname")));
assertThat(page.isLast()).isFalse(); assertThat(page.isLast()).isFalse();
@ -956,12 +956,12 @@ public abstract class AbstractPersonRepositoryIntegrationTests implements Dirtie
@Test // DATAMONGO-969 @Test // DATAMONGO-969
void shouldScrollPersonsWhenUsingQueryDslPerdicatedOnIdProperty() { void shouldScrollPersonsWhenUsingQueryDslPerdicatedOnIdProperty() {
Scroll<Person> scroll = repository.findBy(person.id.in(asList(dave.id, carter.id, boyd.id)), // Window<Person> scroll = repository.findBy(person.id.in(asList(dave.id, carter.id, boyd.id)), //
q -> q.limit(2).sortBy(Sort.by("firstname")).scroll(KeysetScrollPosition.initial())); q -> q.limit(2).sortBy(Sort.by("firstname")).scroll(KeysetScrollPosition.initial()));
assertThat(scroll).containsExactly(boyd, carter); assertThat(scroll).containsExactly(boyd, carter);
ScrollPosition resumeFrom = scroll.lastPosition(); ScrollPosition resumeFrom = scroll.positionAt(scroll.size() - 1);
scroll = repository.findBy(person.id.in(asList(dave.id, carter.id, boyd.id)), // scroll = repository.findBy(person.id.in(asList(dave.id, carter.id, boyd.id)), //
q -> q.limit(2).sortBy(Sort.by("firstname")).scroll(resumeFrom)); q -> q.limit(2).sortBy(Sort.by("firstname")).scroll(resumeFrom));
@ -1185,14 +1185,14 @@ public abstract class AbstractPersonRepositoryIntegrationTests implements Dirtie
ReflectionTestUtils.setField(sample, "createdAt", null); ReflectionTestUtils.setField(sample, "createdAt", null);
ReflectionTestUtils.setField(sample, "email", null); ReflectionTestUtils.setField(sample, "email", null);
Scroll<Person> result = repository.findBy( Window<Person> result = repository.findBy(
Example.of(sample, ExampleMatcher.matching().withMatcher("lastname", GenericPropertyMatcher::startsWith)), Example.of(sample, ExampleMatcher.matching().withMatcher("lastname", GenericPropertyMatcher::startsWith)),
q -> q.limit(2).sortBy(Sort.by("firstname")).scroll(KeysetScrollPosition.initial())); q -> q.limit(2).sortBy(Sort.by("firstname")).scroll(KeysetScrollPosition.initial()));
assertThat(result).containsOnly(dave, leroi); assertThat(result).containsOnly(dave, leroi);
assertThat(result.hasNext()).isTrue(); assertThat(result.hasNext()).isTrue();
ScrollPosition position = result.lastPosition(); ScrollPosition position = result.positionAt(result.size() - 1);
result = repository.findBy( result = repository.findBy(
Example.of(sample, ExampleMatcher.matching().withMatcher("lastname", GenericPropertyMatcher::startsWith)), Example.of(sample, ExampleMatcher.matching().withMatcher("lastname", GenericPropertyMatcher::startsWith)),
q -> q.limit(2).sortBy(Sort.by("firstname")).scroll(position)); q -> q.limit(2).sortBy(Sort.by("firstname")).scroll(position));

10
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java

@ -23,11 +23,11 @@ import java.util.UUID;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.springframework.data.domain.KeysetScrollPosition;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Range; import org.springframework.data.domain.Range;
import org.springframework.data.domain.Scroll; import org.springframework.data.domain.Window;
import org.springframework.data.domain.ScrollPosition;
import org.springframework.data.domain.Slice; import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort;
import org.springframework.data.geo.Box; import org.springframework.data.geo.Box;
@ -123,8 +123,8 @@ public interface PersonRepository extends MongoRepository<Person, String>, Query
* @param scrollPosition * @param scrollPosition
* @return * @return
*/ */
Scroll<Person> findTop2ByLastnameLikeOrderByLastnameAscFirstnameAsc(String lastname, Window<Person> findTop2ByLastnameLikeOrderByLastnameAscFirstnameAsc(String lastname,
KeysetScrollPosition scrollPosition); ScrollPosition scrollPosition);
/** /**
* Returns a scroll of {@link Person}s applying projections with a lastname matching the given one (*-wildcards * Returns a scroll of {@link Person}s applying projections with a lastname matching the given one (*-wildcards
@ -134,7 +134,7 @@ public interface PersonRepository extends MongoRepository<Person, String>, Query
* @param pageable * @param pageable
* @return * @return
*/ */
Scroll<PersonSummaryDto> findCursorProjectionByLastnameLike(String lastname, Pageable pageable); Window<PersonSummaryDto> findCursorProjectionByLastnameLike(String lastname, Pageable pageable);
/** /**
* Returns a page of {@link Person}s with a lastname matching the given one (*-wildcards supported). * Returns a page of {@link Person}s with a lastname matching the given one (*-wildcards supported).

18
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/ReactiveMongoRepositoryTests.java

@ -50,10 +50,10 @@ import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.data.domain.KeysetScrollPosition; import org.springframework.data.domain.KeysetScrollPosition;
import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Scroll;
import org.springframework.data.domain.ScrollPosition; import org.springframework.data.domain.ScrollPosition;
import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction; import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.domain.Window;
import org.springframework.data.geo.Circle; import org.springframework.data.geo.Circle;
import org.springframework.data.geo.Distance; import org.springframework.data.geo.Distance;
import org.springframework.data.geo.GeoResult; import org.springframework.data.geo.GeoResult;
@ -293,15 +293,15 @@ class ReactiveMongoRepositoryTests implements DirtiesStateExtension.StateFunctio
@Test // GH-4308 @Test // GH-4308
void appliesScrollingCorrectly() { void appliesScrollingCorrectly() {
Scroll<Person> scroll = repository Window<Person> scroll = repository
.findTop2ByLastnameLikeOrderByFirstnameAscLastnameAsc("*", KeysetScrollPosition.initial()).block(); .findTop2ByLastnameLikeOrderByFirstnameAscLastnameAsc("*", KeysetScrollPosition.initial()).block();
assertThat(scroll).hasSize(2); assertThat(scroll).hasSize(2);
assertThat(scroll).containsSequence(alicia, boyd); assertThat(scroll).containsSequence(alicia, boyd);
assertThat(scroll.isLast()).isFalse(); assertThat(scroll.isLast()).isFalse();
Scroll<Person> nextScroll = repository Window<Person> nextScroll = repository
.findTop2ByLastnameLikeOrderByFirstnameAscLastnameAsc("*", scroll.lastPosition()).block(); .findTop2ByLastnameLikeOrderByFirstnameAscLastnameAsc("*", scroll.positionAt(scroll.size() - 1)).block();
assertThat(nextScroll).hasSize(2); assertThat(nextScroll).hasSize(2);
assertThat(nextScroll).containsSequence(carter, dave); assertThat(nextScroll).containsSequence(carter, dave);
@ -474,7 +474,7 @@ class ReactiveMongoRepositoryTests implements DirtiesStateExtension.StateFunctio
@Test // GH-4308 @Test // GH-4308
void shouldScrollWithId() { void shouldScrollWithId() {
List<Scroll<Person>> capture = new ArrayList<>(); List<Window<Person>> capture = new ArrayList<>();
repository.findBy(person.id.in(Arrays.asList(dave.id, carter.id, boyd.id)), // repository.findBy(person.id.in(Arrays.asList(dave.id, carter.id, boyd.id)), //
q -> q.limit(2).sortBy(Sort.by("firstname")).scroll(KeysetScrollPosition.initial())) // q -> q.limit(2).sortBy(Sort.by("firstname")).scroll(KeysetScrollPosition.initial())) //
.as(StepVerifier::create) // .as(StepVerifier::create) //
@ -482,10 +482,10 @@ class ReactiveMongoRepositoryTests implements DirtiesStateExtension.StateFunctio
assertThat(actual).hasSize(2).containsExactly(boyd, carter); assertThat(actual).hasSize(2).containsExactly(boyd, carter);
}).verifyComplete(); }).verifyComplete();
Scroll<Person> scroll = capture.get(0); Window<Person> scroll = capture.get(0);
repository.findBy(person.id.in(Arrays.asList(dave.id, carter.id, boyd.id)), // repository.findBy(person.id.in(Arrays.asList(dave.id, carter.id, boyd.id)), //
q -> q.limit(2).sortBy(Sort.by("firstname")).scroll(scroll.lastPosition())) // q -> q.limit(2).sortBy(Sort.by("firstname")).scroll(scroll.positionAt(scroll.size() - 1))) //
.as(StepVerifier::create) // .as(StepVerifier::create) //
.recordWith(() -> capture).assertNext(actual -> { .recordWith(() -> capture).assertNext(actual -> {
assertThat(actual).containsOnly(dave); assertThat(actual).containsOnly(dave);
@ -768,10 +768,10 @@ class ReactiveMongoRepositoryTests implements DirtiesStateExtension.StateFunctio
@Query("{ lastname: { $in: ?0 }, age: { $gt : ?1 } }") @Query("{ lastname: { $in: ?0 }, age: { $gt : ?1 } }")
Flux<Person> findStringQuery(Flux<String> lastname, Mono<Integer> age); Flux<Person> findStringQuery(Flux<String> lastname, Mono<Integer> age);
Mono<Scroll<Person>> findTop2ByLastnameLikeOrderByFirstnameAscLastnameAsc(String lastname, Mono<Window<Person>> findTop2ByLastnameLikeOrderByFirstnameAscLastnameAsc(String lastname,
ScrollPosition scrollPosition); ScrollPosition scrollPosition);
Mono<Scroll<PersonSummaryDto>> findCursorProjectionByLastnameLike(String lastname, Pageable pageable); Mono<Window<PersonSummaryDto>> findCursorProjectionByLastnameLike(String lastname, Pageable pageable);
Flux<Person> findByLocationWithin(Circle circle); Flux<Person> findByLocationWithin(Circle circle);

Loading…
Cancel
Save