Skip to content

Commit 2a99c1f

Browse files
committed
Fix backward scroll calculations for offset positions
1. Offset to be advanced back by 1 more than the count in order to exclude the item at the specified offset. 2. Count to be adjusted down if offset is too low. 3. Count to be set to 0 if offset is 0. See gh-840
1 parent 7ddb1a9 commit 2a99c1f

10 files changed

+103
-19
lines changed

spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/ScrollSubrangeMethodArgumentResolver.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,9 @@ public boolean supportsParameter(MethodParameter parameter) {
4444
return parameter.getParameterType().equals(ScrollSubrange.class);
4545
}
4646

47-
protected ScrollSubrange createSubrange(@Nullable ScrollPosition pos, @Nullable Integer size, boolean forward) {
48-
return new ScrollSubrange(pos, size, forward);
47+
@Override
48+
protected ScrollSubrange createSubrange(@Nullable ScrollPosition pos, @Nullable Integer count, boolean forward) {
49+
return ScrollSubrange.create(pos, count, forward);
4950
}
5051

5152
}

spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/SubrangeMethodArgumentResolver.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,8 @@ public Object resolveArgument(MethodParameter parameter, DataFetchingEnvironment
6262
/**
6363
* Allows subclasses to create an extension of {@link Subrange}.
6464
*/
65-
protected Subrange<P> createSubrange(@Nullable P pos, @Nullable Integer size, boolean forward) {
66-
return new Subrange<>(pos, size, forward);
65+
protected Subrange<P> createSubrange(@Nullable P pos, @Nullable Integer count, boolean forward) {
66+
return new Subrange<>(pos, count, forward);
6767
}
6868

6969
}

spring-graphql/src/main/java/org/springframework/graphql/data/query/RepositoryUtils.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ public static CursorStrategy<ScrollPosition> defaultCursorStrategy() {
8585
}
8686

8787
public static ScrollSubrange defaultScrollSubrange() {
88-
return new ScrollSubrange(ScrollPosition.offset(), 20, true);
88+
return ScrollSubrange.create(ScrollPosition.offset(), 20, true);
8989
}
9090

9191
public static ScrollSubrange buildScrollSubrange(
@@ -96,7 +96,7 @@ public static ScrollSubrange buildScrollSubrange(
9696
Integer count = environment.getArgument(forward ? "first" : "last");
9797
String cursor = environment.getArgument(forward ? "after" : "before");
9898
ScrollPosition position = (cursor != null ? cursorStrategy.fromCursor(cursor) : null);
99-
return new ScrollSubrange(position, count, forward);
99+
return ScrollSubrange.create(position, count, forward);
100100
}
101101

102102
}

spring-graphql/src/main/java/org/springframework/graphql/data/query/ScrollSubrange.java

+63
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,19 @@
3939
public final class ScrollSubrange extends Subrange<ScrollPosition> {
4040

4141

42+
@SuppressWarnings("unused")
43+
private ScrollSubrange(
44+
@Nullable ScrollPosition pos, @Nullable Integer count, boolean forward,
45+
@Nullable Object unused /* temporarily to differentiate from deprecated constructor */) {
46+
47+
super(pos, count, forward);
48+
}
49+
50+
/**
51+
* Public constructor.
52+
* @deprecated in favor of {@link #create}, to be removed in 1.3.
53+
*/
54+
@Deprecated(since = "1.2.4", forRemoval = true)
4255
public ScrollSubrange(@Nullable ScrollPosition pos, @Nullable Integer count, boolean forward) {
4356
super(initPosition(pos, count, forward), count, (pos instanceof OffsetScrollPosition || forward));
4457
}
@@ -56,4 +69,54 @@ else if (pos instanceof KeysetScrollPosition keysetPosition) {
5669
return pos;
5770
}
5871

72+
73+
/**
74+
* Create a {@link ScrollSubrange} instance.
75+
* @param position the position relative to which to scroll, or {@code null}
76+
* for scrolling from the beginning
77+
* @param count the number of elements requested
78+
* @param forward whether to return elements after (true) or before (false)
79+
* the element at the given position
80+
* @return the created subrange
81+
* @since 1.2.4
82+
*/
83+
public static ScrollSubrange create(@Nullable ScrollPosition position, @Nullable Integer count, boolean forward) {
84+
if (position instanceof OffsetScrollPosition offsetScrollPosition) {
85+
return initFromOffsetPosition(offsetScrollPosition, count, forward);
86+
}
87+
else if (position instanceof KeysetScrollPosition keysetScrollPosition) {
88+
return initFromKeysetPosition(keysetScrollPosition, count, forward);
89+
}
90+
else {
91+
return new ScrollSubrange(position, count, forward, null);
92+
}
93+
}
94+
95+
private static ScrollSubrange initFromOffsetPosition(
96+
OffsetScrollPosition position, @Nullable Integer count, boolean forward) {
97+
98+
if (!forward) {
99+
if (count != null) {
100+
if (position.getOffset() == 0) {
101+
count = 0;
102+
}
103+
else if (count >= position.getOffset()) {
104+
count = (int) (position.getOffset() - 1);
105+
}
106+
position = position.advanceBy(-count - 1);
107+
}
108+
forward = true;
109+
}
110+
return new ScrollSubrange(position, count, forward, null);
111+
}
112+
113+
private static ScrollSubrange initFromKeysetPosition(
114+
KeysetScrollPosition position, @Nullable Integer count, boolean forward) {
115+
116+
if (!forward) {
117+
position = position.backward();
118+
}
119+
return new ScrollSubrange(position, count, forward, null);
120+
}
121+
59122
}

spring-graphql/src/test/java/org/springframework/graphql/data/query/ScrollSubrangeTests.java

+28-8
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,13 @@ void offset() {
4141
ScrollPosition position = ScrollPosition.offset(30);
4242
int count = 10;
4343

44-
ScrollSubrange subrange = new ScrollSubrange(position, count, true);
44+
ScrollSubrange subrange = ScrollSubrange.create(position, count, true);
4545
assertThat(((OffsetScrollPosition) subrange.position().get())).isEqualTo(position);
4646
assertThat(subrange.count().orElse(0)).isEqualTo(count);
4747
assertThat(subrange.forward()).isTrue();
4848

49-
subrange = new ScrollSubrange(position, count, false);
50-
assertThat(((OffsetScrollPosition) subrange.position().get()).getOffset()).isEqualTo(20);
49+
subrange = ScrollSubrange.create(position, count, false);
50+
assertThat(((OffsetScrollPosition) subrange.position().get()).getOffset()).isEqualTo(19);
5151
assertThat(subrange.count().orElse(0)).isEqualTo(count);
5252
assertThat(subrange.forward()).isTrue();
5353
}
@@ -62,14 +62,14 @@ void keyset() {
6262
ScrollPosition position = ScrollPosition.forward(keys);
6363
int count = 10;
6464

65-
ScrollSubrange subrange = new ScrollSubrange(position, count, true);
65+
ScrollSubrange subrange = ScrollSubrange.create(position, count, true);
6666
KeysetScrollPosition actualPosition = (KeysetScrollPosition) subrange.position().get();
6767
assertThat(actualPosition.getKeys()).isEqualTo(keys);
6868
assertThat(actualPosition.getDirection()).isEqualTo(Direction.FORWARD);
6969
assertThat(subrange.count().orElse(0)).isEqualTo(count);
7070
assertThat(subrange.forward()).isTrue();
7171

72-
subrange = new ScrollSubrange(position, count, false);
72+
subrange = ScrollSubrange.create(position, count, false);
7373
actualPosition = (KeysetScrollPosition) subrange.position().get();
7474
assertThat(actualPosition.getKeys()).isEqualTo(keys);
7575
assertThat(actualPosition.getDirection()).isEqualTo(Direction.BACKWARD);
@@ -79,17 +79,37 @@ void keyset() {
7979

8080
@Test
8181
void nullInput() {
82-
ScrollSubrange subrange = new ScrollSubrange(null, null, true);
82+
ScrollSubrange subrange = ScrollSubrange.create(null, null, true);
8383

8484
assertThat(subrange.position()).isNotPresent();
8585
assertThat(subrange.count()).isNotPresent();
8686
assertThat(subrange.forward()).isTrue();
8787
}
8888

8989
@Test
90-
void offsetBackwardPaginationNullSize() {
90+
void offsetBackwardPaginationWithInsufficientCount() {
91+
ScrollPosition position = ScrollPosition.offset(5);
92+
ScrollSubrange subrange = ScrollSubrange.create(position, 10, false);
93+
94+
assertThat(((OffsetScrollPosition) subrange.position().get()).getOffset()).isEqualTo(0);
95+
assertThat(subrange.count().getAsInt()).isEqualTo(4);
96+
assertThat(subrange.forward()).isTrue();
97+
}
98+
99+
@Test
100+
void offsetBackwardPaginationWithOffsetZero() {
101+
ScrollPosition position = ScrollPosition.offset(0);
102+
ScrollSubrange subrange = ScrollSubrange.create(position, 10, false);
103+
104+
assertThat(((OffsetScrollPosition) subrange.position().get()).getOffset()).isEqualTo(0);
105+
assertThat(subrange.count().getAsInt()).isEqualTo(0);
106+
assertThat(subrange.forward()).isTrue();
107+
}
108+
109+
@Test
110+
void offsetBackwardPaginationWithNullCount() {
91111
ScrollPosition position = ScrollPosition.offset(30);
92-
ScrollSubrange subrange = new ScrollSubrange(position, null, false);
112+
ScrollSubrange subrange = ScrollSubrange.create(position, null, false);
93113

94114
assertThat(((OffsetScrollPosition) subrange.position().get())).isEqualTo(position);
95115
assertThat(subrange.count()).isNotPresent();

spring-graphql/src/test/java/org/springframework/graphql/data/query/jpa/QueryByExampleDataFetcherJpaTests.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ private static RuntimeWiringConfigurer createRuntimeWiringConfigurer(QueryByExam
256256
executor != null ? Collections.singletonList(executor) : Collections.emptyList(),
257257
Collections.emptyList(),
258258
new ScrollPositionCursorStrategy(),
259-
new ScrollSubrange(ScrollPosition.offset(), 10, true));
259+
ScrollSubrange.create(ScrollPosition.offset(), 10, true));
260260
}
261261

262262
private WebGraphQlRequest request(String query) {

spring-graphql/src/test/java/org/springframework/graphql/data/query/mongo/QueryByExampleDataFetcherMongoDbTests.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ private static RuntimeWiringConfigurer createRuntimeWiringConfigurer(QueryByExam
236236
(executor != null ? Collections.singletonList(executor) : Collections.emptyList()),
237237
Collections.emptyList(),
238238
new ScrollPositionCursorStrategy(),
239-
new ScrollSubrange(ScrollPosition.offset(), 10, true));
239+
ScrollSubrange.create(ScrollPosition.offset(), 10, true));
240240
}
241241

242242
private WebGraphQlRequest request(String query) {

spring-graphql/src/test/java/org/springframework/graphql/data/query/mongo/QueryByExampleDataFetcherReactiveMongoDbTests.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ private static RuntimeWiringConfigurer createRuntimeWiringConfigurer(ReactiveQue
207207
Collections.emptyList(),
208208
(executor != null ? Collections.singletonList(executor) : Collections.emptyList()),
209209
new ScrollPositionCursorStrategy(),
210-
new ScrollSubrange(ScrollPosition.offset(), 10, true));
210+
ScrollSubrange.create(ScrollPosition.offset(), 10, true));
211211
}
212212

213213
private WebGraphQlRequest request(String query) {

spring-graphql/src/test/java/org/springframework/graphql/data/query/neo4j/QueryByExampleDataFetcherNeo4jTests.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ private static RuntimeWiringConfigurer createRuntimeWiringConfigurer(QueryByExam
235235
(executor != null ? Collections.singletonList(executor) : Collections.emptyList()),
236236
Collections.emptyList(),
237237
new ScrollPositionCursorStrategy(),
238-
new ScrollSubrange(ScrollPosition.offset(), 10, true));
238+
ScrollSubrange.create(ScrollPosition.offset(), 10, true));
239239
}
240240

241241
private WebGraphQlRequest request(String query) {

spring-graphql/src/test/java/org/springframework/graphql/data/query/neo4j/QueryByExampleDataFetcherReactiveNeo4jDbTests.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ private static RuntimeWiringConfigurer createRuntimeWiringConfigurer(ReactiveQue
219219
Collections.emptyList(),
220220
(executor != null ? Collections.singletonList(executor) : Collections.emptyList()),
221221
new ScrollPositionCursorStrategy(),
222-
new ScrollSubrange(ScrollPosition.offset(), 10, true));
222+
ScrollSubrange.create(ScrollPosition.offset(), 10, true));
223223
}
224224

225225
private WebGraphQlRequest request(String query) {

0 commit comments

Comments
 (0)