diff --git a/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/ScrollSubrangeMethodArgumentResolver.java b/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/ScrollSubrangeMethodArgumentResolver.java index 2dd72a5ca..fbc259b7a 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/ScrollSubrangeMethodArgumentResolver.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/ScrollSubrangeMethodArgumentResolver.java @@ -44,8 +44,9 @@ public boolean supportsParameter(MethodParameter parameter) { return parameter.getParameterType().equals(ScrollSubrange.class); } - protected ScrollSubrange createSubrange(@Nullable ScrollPosition pos, @Nullable Integer size, boolean forward) { - return new ScrollSubrange(pos, size, forward); + @Override + protected ScrollSubrange createSubrange(@Nullable ScrollPosition pos, @Nullable Integer count, boolean forward) { + return ScrollSubrange.create(pos, count, forward); } } diff --git a/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/SubrangeMethodArgumentResolver.java b/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/SubrangeMethodArgumentResolver.java index 202340636..2bb37b93d 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/SubrangeMethodArgumentResolver.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/SubrangeMethodArgumentResolver.java @@ -62,8 +62,8 @@ public Object resolveArgument(MethodParameter parameter, DataFetchingEnvironment /** * Allows subclasses to create an extension of {@link Subrange}. */ - protected Subrange

createSubrange(@Nullable P pos, @Nullable Integer size, boolean forward) { - return new Subrange<>(pos, size, forward); + protected Subrange

createSubrange(@Nullable P pos, @Nullable Integer count, boolean forward) { + return new Subrange<>(pos, count, forward); } } diff --git a/spring-graphql/src/main/java/org/springframework/graphql/data/query/RepositoryUtils.java b/spring-graphql/src/main/java/org/springframework/graphql/data/query/RepositoryUtils.java index 060468113..612b71092 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/data/query/RepositoryUtils.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/data/query/RepositoryUtils.java @@ -85,7 +85,7 @@ public static CursorStrategy defaultCursorStrategy() { } public static ScrollSubrange defaultScrollSubrange() { - return new ScrollSubrange(ScrollPosition.offset(), 20, true); + return ScrollSubrange.create(ScrollPosition.offset(), 20, true); } public static ScrollSubrange buildScrollSubrange( @@ -96,7 +96,7 @@ public static ScrollSubrange buildScrollSubrange( Integer count = environment.getArgument(forward ? "first" : "last"); String cursor = environment.getArgument(forward ? "after" : "before"); ScrollPosition position = (cursor != null ? cursorStrategy.fromCursor(cursor) : null); - return new ScrollSubrange(position, count, forward); + return ScrollSubrange.create(position, count, forward); } } diff --git a/spring-graphql/src/main/java/org/springframework/graphql/data/query/ScrollSubrange.java b/spring-graphql/src/main/java/org/springframework/graphql/data/query/ScrollSubrange.java index 7d89d4eeb..79c89b2e4 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/data/query/ScrollSubrange.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/data/query/ScrollSubrange.java @@ -39,6 +39,19 @@ public final class ScrollSubrange extends Subrange { + @SuppressWarnings("unused") + private ScrollSubrange( + @Nullable ScrollPosition pos, @Nullable Integer count, boolean forward, + @Nullable Object unused /* temporarily to differentiate from deprecated constructor */) { + + super(pos, count, forward); + } + + /** + * Public constructor. + * @deprecated in favor of {@link #create}, to be removed in 1.3. + */ + @Deprecated(since = "1.2.4", forRemoval = true) public ScrollSubrange(@Nullable ScrollPosition pos, @Nullable Integer count, boolean forward) { super(initPosition(pos, count, forward), count, (pos instanceof OffsetScrollPosition || forward)); } @@ -56,4 +69,54 @@ else if (pos instanceof KeysetScrollPosition keysetPosition) { return pos; } + + /** + * Create a {@link ScrollSubrange} instance. + * @param position the position relative to which to scroll, or {@code null} + * for scrolling from the beginning + * @param count the number of elements requested + * @param forward whether to return elements after (true) or before (false) + * the element at the given position + * @return the created subrange + * @since 1.2.4 + */ + public static ScrollSubrange create(@Nullable ScrollPosition position, @Nullable Integer count, boolean forward) { + if (position instanceof OffsetScrollPosition offsetScrollPosition) { + return initFromOffsetPosition(offsetScrollPosition, count, forward); + } + else if (position instanceof KeysetScrollPosition keysetScrollPosition) { + return initFromKeysetPosition(keysetScrollPosition, count, forward); + } + else { + return new ScrollSubrange(position, count, forward, null); + } + } + + private static ScrollSubrange initFromOffsetPosition( + OffsetScrollPosition position, @Nullable Integer count, boolean forward) { + + if (!forward) { + if (count != null) { + if (position.getOffset() == 0) { + count = 0; + } + else if (count >= position.getOffset()) { + count = (int) (position.getOffset() - 1); + } + position = position.advanceBy(-count - 1); + } + forward = true; + } + return new ScrollSubrange(position, count, forward, null); + } + + private static ScrollSubrange initFromKeysetPosition( + KeysetScrollPosition position, @Nullable Integer count, boolean forward) { + + if (!forward) { + position = position.backward(); + } + return new ScrollSubrange(position, count, forward, null); + } + } diff --git a/spring-graphql/src/test/java/org/springframework/graphql/data/query/ScrollSubrangeTests.java b/spring-graphql/src/test/java/org/springframework/graphql/data/query/ScrollSubrangeTests.java index 81aa01fe7..a829751d8 100644 --- a/spring-graphql/src/test/java/org/springframework/graphql/data/query/ScrollSubrangeTests.java +++ b/spring-graphql/src/test/java/org/springframework/graphql/data/query/ScrollSubrangeTests.java @@ -41,13 +41,13 @@ void offset() { ScrollPosition position = ScrollPosition.offset(30); int count = 10; - ScrollSubrange subrange = new ScrollSubrange(position, count, true); + ScrollSubrange subrange = ScrollSubrange.create(position, count, true); assertThat(((OffsetScrollPosition) subrange.position().get())).isEqualTo(position); assertThat(subrange.count().orElse(0)).isEqualTo(count); assertThat(subrange.forward()).isTrue(); - subrange = new ScrollSubrange(position, count, false); - assertThat(((OffsetScrollPosition) subrange.position().get()).getOffset()).isEqualTo(20); + subrange = ScrollSubrange.create(position, count, false); + assertThat(((OffsetScrollPosition) subrange.position().get()).getOffset()).isEqualTo(19); assertThat(subrange.count().orElse(0)).isEqualTo(count); assertThat(subrange.forward()).isTrue(); } @@ -62,14 +62,14 @@ void keyset() { ScrollPosition position = ScrollPosition.forward(keys); int count = 10; - ScrollSubrange subrange = new ScrollSubrange(position, count, true); + ScrollSubrange subrange = ScrollSubrange.create(position, count, true); KeysetScrollPosition actualPosition = (KeysetScrollPosition) subrange.position().get(); assertThat(actualPosition.getKeys()).isEqualTo(keys); assertThat(actualPosition.getDirection()).isEqualTo(Direction.FORWARD); assertThat(subrange.count().orElse(0)).isEqualTo(count); assertThat(subrange.forward()).isTrue(); - subrange = new ScrollSubrange(position, count, false); + subrange = ScrollSubrange.create(position, count, false); actualPosition = (KeysetScrollPosition) subrange.position().get(); assertThat(actualPosition.getKeys()).isEqualTo(keys); assertThat(actualPosition.getDirection()).isEqualTo(Direction.BACKWARD); @@ -79,7 +79,7 @@ void keyset() { @Test void nullInput() { - ScrollSubrange subrange = new ScrollSubrange(null, null, true); + ScrollSubrange subrange = ScrollSubrange.create(null, null, true); assertThat(subrange.position()).isNotPresent(); assertThat(subrange.count()).isNotPresent(); @@ -87,9 +87,29 @@ void nullInput() { } @Test - void offsetBackwardPaginationNullSize() { + void offsetBackwardPaginationWithInsufficientCount() { + ScrollPosition position = ScrollPosition.offset(5); + ScrollSubrange subrange = ScrollSubrange.create(position, 10, false); + + assertThat(((OffsetScrollPosition) subrange.position().get()).getOffset()).isEqualTo(0); + assertThat(subrange.count().getAsInt()).isEqualTo(4); + assertThat(subrange.forward()).isTrue(); + } + + @Test + void offsetBackwardPaginationWithOffsetZero() { + ScrollPosition position = ScrollPosition.offset(0); + ScrollSubrange subrange = ScrollSubrange.create(position, 10, false); + + assertThat(((OffsetScrollPosition) subrange.position().get()).getOffset()).isEqualTo(0); + assertThat(subrange.count().getAsInt()).isEqualTo(0); + assertThat(subrange.forward()).isTrue(); + } + + @Test + void offsetBackwardPaginationWithNullCount() { ScrollPosition position = ScrollPosition.offset(30); - ScrollSubrange subrange = new ScrollSubrange(position, null, false); + ScrollSubrange subrange = ScrollSubrange.create(position, null, false); assertThat(((OffsetScrollPosition) subrange.position().get())).isEqualTo(position); assertThat(subrange.count()).isNotPresent(); diff --git a/spring-graphql/src/test/java/org/springframework/graphql/data/query/jpa/QueryByExampleDataFetcherJpaTests.java b/spring-graphql/src/test/java/org/springframework/graphql/data/query/jpa/QueryByExampleDataFetcherJpaTests.java index 64353dd05..1820978a3 100644 --- a/spring-graphql/src/test/java/org/springframework/graphql/data/query/jpa/QueryByExampleDataFetcherJpaTests.java +++ b/spring-graphql/src/test/java/org/springframework/graphql/data/query/jpa/QueryByExampleDataFetcherJpaTests.java @@ -256,7 +256,7 @@ private static RuntimeWiringConfigurer createRuntimeWiringConfigurer(QueryByExam executor != null ? Collections.singletonList(executor) : Collections.emptyList(), Collections.emptyList(), new ScrollPositionCursorStrategy(), - new ScrollSubrange(ScrollPosition.offset(), 10, true)); + ScrollSubrange.create(ScrollPosition.offset(), 10, true)); } private WebGraphQlRequest request(String query) { diff --git a/spring-graphql/src/test/java/org/springframework/graphql/data/query/mongo/QueryByExampleDataFetcherMongoDbTests.java b/spring-graphql/src/test/java/org/springframework/graphql/data/query/mongo/QueryByExampleDataFetcherMongoDbTests.java index 1829aede8..8fcb22423 100644 --- a/spring-graphql/src/test/java/org/springframework/graphql/data/query/mongo/QueryByExampleDataFetcherMongoDbTests.java +++ b/spring-graphql/src/test/java/org/springframework/graphql/data/query/mongo/QueryByExampleDataFetcherMongoDbTests.java @@ -236,7 +236,7 @@ private static RuntimeWiringConfigurer createRuntimeWiringConfigurer(QueryByExam (executor != null ? Collections.singletonList(executor) : Collections.emptyList()), Collections.emptyList(), new ScrollPositionCursorStrategy(), - new ScrollSubrange(ScrollPosition.offset(), 10, true)); + ScrollSubrange.create(ScrollPosition.offset(), 10, true)); } private WebGraphQlRequest request(String query) { diff --git a/spring-graphql/src/test/java/org/springframework/graphql/data/query/mongo/QueryByExampleDataFetcherReactiveMongoDbTests.java b/spring-graphql/src/test/java/org/springframework/graphql/data/query/mongo/QueryByExampleDataFetcherReactiveMongoDbTests.java index 2dc8c65cc..d5858345a 100644 --- a/spring-graphql/src/test/java/org/springframework/graphql/data/query/mongo/QueryByExampleDataFetcherReactiveMongoDbTests.java +++ b/spring-graphql/src/test/java/org/springframework/graphql/data/query/mongo/QueryByExampleDataFetcherReactiveMongoDbTests.java @@ -207,7 +207,7 @@ private static RuntimeWiringConfigurer createRuntimeWiringConfigurer(ReactiveQue Collections.emptyList(), (executor != null ? Collections.singletonList(executor) : Collections.emptyList()), new ScrollPositionCursorStrategy(), - new ScrollSubrange(ScrollPosition.offset(), 10, true)); + ScrollSubrange.create(ScrollPosition.offset(), 10, true)); } private WebGraphQlRequest request(String query) { diff --git a/spring-graphql/src/test/java/org/springframework/graphql/data/query/neo4j/QueryByExampleDataFetcherNeo4jTests.java b/spring-graphql/src/test/java/org/springframework/graphql/data/query/neo4j/QueryByExampleDataFetcherNeo4jTests.java index 10eefad38..ade944451 100644 --- a/spring-graphql/src/test/java/org/springframework/graphql/data/query/neo4j/QueryByExampleDataFetcherNeo4jTests.java +++ b/spring-graphql/src/test/java/org/springframework/graphql/data/query/neo4j/QueryByExampleDataFetcherNeo4jTests.java @@ -235,7 +235,7 @@ private static RuntimeWiringConfigurer createRuntimeWiringConfigurer(QueryByExam (executor != null ? Collections.singletonList(executor) : Collections.emptyList()), Collections.emptyList(), new ScrollPositionCursorStrategy(), - new ScrollSubrange(ScrollPosition.offset(), 10, true)); + ScrollSubrange.create(ScrollPosition.offset(), 10, true)); } private WebGraphQlRequest request(String query) { diff --git a/spring-graphql/src/test/java/org/springframework/graphql/data/query/neo4j/QueryByExampleDataFetcherReactiveNeo4jDbTests.java b/spring-graphql/src/test/java/org/springframework/graphql/data/query/neo4j/QueryByExampleDataFetcherReactiveNeo4jDbTests.java index 0de50aa10..5a9795c37 100644 --- a/spring-graphql/src/test/java/org/springframework/graphql/data/query/neo4j/QueryByExampleDataFetcherReactiveNeo4jDbTests.java +++ b/spring-graphql/src/test/java/org/springframework/graphql/data/query/neo4j/QueryByExampleDataFetcherReactiveNeo4jDbTests.java @@ -219,7 +219,7 @@ private static RuntimeWiringConfigurer createRuntimeWiringConfigurer(ReactiveQue Collections.emptyList(), (executor != null ? Collections.singletonList(executor) : Collections.emptyList()), new ScrollPositionCursorStrategy(), - new ScrollSubrange(ScrollPosition.offset(), 10, true)); + ScrollSubrange.create(ScrollPosition.offset(), 10, true)); } private WebGraphQlRequest request(String query) {