Skip to content

Commit 124920e

Browse files
committed
fix: remove skip from subsequent pages
1 parent ae06c5f commit 124920e

File tree

4 files changed

+160
-10
lines changed

4 files changed

+160
-10
lines changed

modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BasePageIterator.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,11 @@ List<I> nextRequest() {
6363
if (items.size() < this.pageSize) {
6464
this.hasNext = false;
6565
} else {
66+
O options = this.nextPageOptionsRef.get();
6667
B optionsBuilder = optsHandler.builderFromOptions(this.nextPageOptionsRef.get());
6768
setNextPageOptions(optionsBuilder, result);
69+
// Remove any options valid on the user request, but invalid during paging
70+
optionsBuilder = this.optsHandler.removeOptsForSubsequentPage(options, optionsBuilder);
6871
buildAndSetOptions(optionsBuilder);
6972
}
7073
return items;

modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/OptionsHandler.java

Lines changed: 114 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,11 @@ B applyLimit(B builder, Long newLimit) {
8888
return this.limitSetter.apply(builder, newLimit);
8989
}
9090

91+
B removeOptsForSubsequentPage(O options, B builder) {
92+
// Default is a no-op
93+
return builder;
94+
}
95+
9196
Long getPageSizeFromOptionsLimit(O opts) {
9297
return Optional.ofNullable(this.limitGetter.apply(opts)).orElse(MAX_LIMIT);
9398
}
@@ -172,15 +177,17 @@ private abstract static class KeyOptionsHandler<B, O, K> extends OptionsHandler<
172177

173178
protected final Function<O, K> keyGetter;
174179
private final Function<O, List<K>> keysGetter;
180+
protected final Function<O, Long> skipGetter;
175181
private final String keyErrorMsg =
176182
keyErrorMessage(new StringBuilder("when using pagination. "));
177183

178184
protected KeyOptionsHandler(Function<B, O> builderToOptions, Function<O, B> optionsToBuilder,
179185
Function<O, Long> limitGetter, BiFunction<B, Long, B> limitSetter, Function<O, K> keyGetter,
180-
Function<O, List<K>> keysGetter) {
186+
Function<O, List<K>> keysGetter, Function<O, Long> skipGetter) {
181187
super(builderToOptions, optionsToBuilder, limitGetter, limitSetter);
182188
this.keyGetter = keyGetter;
183189
this.keysGetter = keysGetter;
190+
this.skipGetter = skipGetter;
184191
}
185192

186193
protected String keyErrorMessage(StringBuilder baseMessage) {
@@ -201,14 +208,27 @@ protected void validate(O options) {
201208
this.keyErrorMsg);
202209
super.validate(options);
203210
}
211+
212+
@Override
213+
B removeOptsForSubsequentPage(O options, B builder) {
214+
// Unset the skip option if necessary
215+
if (optionIsPresent(options, this.skipGetter)) {
216+
builder = this.builderFromOptions(replaceOpts(builder));
217+
}
218+
return super.removeOptsForSubsequentPage(options, builder);
219+
}
220+
221+
protected abstract O replaceOpts(B builder);
204222
}
205223

206224
private abstract static class ViewsOptionsHandler<B, O> extends KeyOptionsHandler<B, O, Object> {
207225

208226
protected ViewsOptionsHandler(Function<B, O> builderToOptions, Function<O, B> optionsToBuilder,
209227
Function<O, Long> limitGetter, BiFunction<B, Long, B> limitSetter,
210-
Function<O, Object> keyGetter, Function<O, List<Object>> keysGetter) {
211-
super(builderToOptions, optionsToBuilder, limitGetter, limitSetter, keyGetter, keysGetter);
228+
Function<O, Object> keyGetter, Function<O, List<Object>> keysGetter,
229+
Function<O, Long> skipGetter) {
230+
super(builderToOptions, optionsToBuilder, limitGetter, limitSetter, keyGetter, keysGetter,
231+
skipGetter);
212232
}
213233

214234
@Override
@@ -233,7 +253,17 @@ private static final class AllDocsOptionsHandler
233253
private AllDocsOptionsHandler() {
234254
super(PostAllDocsOptions.Builder::build, PostAllDocsOptions::newBuilder,
235255
PostAllDocsOptions::limit, PostAllDocsOptions.Builder::limit, PostAllDocsOptions::key,
236-
PostAllDocsOptions::keys);
256+
PostAllDocsOptions::keys, PostAllDocsOptions::skip);
257+
}
258+
259+
@Override
260+
protected PostAllDocsOptions replaceOpts(PostAllDocsOptions.Builder builder) {
261+
return new PostAllDocsOptions(builder) {
262+
PostAllDocsOptions unsetOpts() {
263+
this.skip = null;
264+
return this;
265+
}
266+
}.unsetOpts();
237267
}
238268

239269
}
@@ -244,19 +274,45 @@ private static final class DesignDocsOptionsHandler
244274
private DesignDocsOptionsHandler() {
245275
super(PostDesignDocsOptions.Builder::build, PostDesignDocsOptions::newBuilder,
246276
PostDesignDocsOptions::limit, PostDesignDocsOptions.Builder::limit,
247-
PostDesignDocsOptions::key, PostDesignDocsOptions::keys);
277+
PostDesignDocsOptions::key, PostDesignDocsOptions::keys, PostDesignDocsOptions::skip);
278+
}
279+
280+
@Override
281+
protected PostDesignDocsOptions replaceOpts(PostDesignDocsOptions.Builder builder) {
282+
return new PostDesignDocsOptions(builder) {
283+
PostDesignDocsOptions unsetOpts() {
284+
this.skip = null;
285+
return this;
286+
}
287+
}.unsetOpts();
248288
}
249289

250290
}
251291

252292
private static final class FindOptionsHandler
253293
extends BookmarkOptionsHandler<PostFindOptions.Builder, PostFindOptions> {
254294

295+
private final Function<PostFindOptions, Long> skipGetter = PostFindOptions::skip;
296+
255297
private FindOptionsHandler() {
256298
super(PostFindOptions.Builder::build, PostFindOptions::newBuilder, PostFindOptions::limit,
257299
PostFindOptions.Builder::limit);
258300
}
259301

302+
@Override
303+
PostFindOptions.Builder removeOptsForSubsequentPage(PostFindOptions options,
304+
PostFindOptions.Builder builder) {
305+
if (optionIsPresent(options, this.skipGetter)) {
306+
return new PostFindOptions(builder) {
307+
PostFindOptions unsetOpts() {
308+
this.skip = null;
309+
return this;
310+
}
311+
}.unsetOpts().newBuilder();
312+
}
313+
return builder;
314+
}
315+
260316
}
261317

262318
private static final class PartitionAllDocsOptionsHandler extends
@@ -265,19 +321,47 @@ private static final class PartitionAllDocsOptionsHandler extends
265321
private PartitionAllDocsOptionsHandler() {
266322
super(PostPartitionAllDocsOptions.Builder::build, PostPartitionAllDocsOptions::newBuilder,
267323
PostPartitionAllDocsOptions::limit, PostPartitionAllDocsOptions.Builder::limit,
268-
PostPartitionAllDocsOptions::key, PostPartitionAllDocsOptions::keys);
324+
PostPartitionAllDocsOptions::key, PostPartitionAllDocsOptions::keys,
325+
PostPartitionAllDocsOptions::skip);
326+
}
327+
328+
@Override
329+
protected PostPartitionAllDocsOptions replaceOpts(PostPartitionAllDocsOptions.Builder builder) {
330+
return new PostPartitionAllDocsOptions(builder) {
331+
PostPartitionAllDocsOptions unsetOpts() {
332+
this.skip = null;
333+
return this;
334+
}
335+
}.unsetOpts();
269336
}
270337

271338
}
272339

273340
private static final class PartitionFindOptionsHandler
274341
extends BookmarkOptionsHandler<PostPartitionFindOptions.Builder, PostPartitionFindOptions> {
275342

343+
private final Function<PostPartitionFindOptions, Long> skipGetter =
344+
PostPartitionFindOptions::skip;
345+
276346
private PartitionFindOptionsHandler() {
277347
super(PostPartitionFindOptions.Builder::build, PostPartitionFindOptions::newBuilder,
278348
PostPartitionFindOptions::limit, PostPartitionFindOptions.Builder::limit);
279349
}
280350

351+
@Override
352+
PostPartitionFindOptions.Builder removeOptsForSubsequentPage(PostPartitionFindOptions options,
353+
PostPartitionFindOptions.Builder builder) {
354+
if (optionIsPresent(options, this.skipGetter)) {
355+
return new PostPartitionFindOptions(builder) {
356+
PostPartitionFindOptions unsetOpts() {
357+
this.skip = null;
358+
return this;
359+
}
360+
}.unsetOpts().newBuilder();
361+
}
362+
return builder;
363+
}
364+
281365
}
282366

283367
private static final class PartitionSearchOptionsHandler extends
@@ -296,7 +380,18 @@ private static final class PartitionViewOptionsHandler
296380
private PartitionViewOptionsHandler() {
297381
super(PostPartitionViewOptions.Builder::build, PostPartitionViewOptions::newBuilder,
298382
PostPartitionViewOptions::limit, PostPartitionViewOptions.Builder::limit,
299-
PostPartitionViewOptions::key, PostPartitionViewOptions::keys);
383+
PostPartitionViewOptions::key, PostPartitionViewOptions::keys,
384+
PostPartitionViewOptions::skip);
385+
}
386+
387+
@Override
388+
protected PostPartitionViewOptions replaceOpts(PostPartitionViewOptions.Builder builder) {
389+
return new PostPartitionViewOptions(builder) {
390+
PostPartitionViewOptions unsetOpts() {
391+
this.skip = null;
392+
return this;
393+
}
394+
}.unsetOpts();
300395
}
301396

302397
}
@@ -328,7 +423,18 @@ private static final class ViewOptionsHandler
328423

329424
private ViewOptionsHandler() {
330425
super(PostViewOptions.Builder::build, PostViewOptions::newBuilder, PostViewOptions::limit,
331-
PostViewOptions.Builder::limit, PostViewOptions::key, PostViewOptions::keys);
426+
PostViewOptions.Builder::limit, PostViewOptions::key, PostViewOptions::keys,
427+
PostViewOptions::skip);
428+
}
429+
430+
@Override
431+
protected PostViewOptions replaceOpts(PostViewOptions.Builder builder) {
432+
return new PostViewOptions(builder) {
433+
PostViewOptions unsetOpts() {
434+
this.skip = null;
435+
return this;
436+
}
437+
}.unsetOpts();
332438
}
333439

334440
}

modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BookmarkPageIteratorTest.java

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ public class BookmarkPageIteratorTest {
3838
* This test sub-class of BookmarkPager implicitly tests that various abstract methods are
3939
* correctly called.
4040
*/
41-
class TestBookmarkPager extends BookmarkPageIterator<Builder, PostFindOptions, TestResult, Integer> {
41+
class TestBookmarkPager
42+
extends BookmarkPageIterator<Builder, PostFindOptions, TestResult, Integer> {
4243

4344
protected TestBookmarkPager(Cloudant client, PostFindOptions options) {
4445
super(client, options, OptionsHandler.POST_FIND);
@@ -187,4 +188,24 @@ void testGetAll() {
187188
Assert.assertEquals(actualItems, pageSupplier.allItems,
188189
"The results should match all the pages.");
189190
}
191+
192+
@Test
193+
void testSkipRemovedForSubsequentPages() {
194+
int pageSize = 3;
195+
long expectedSkip = 17;
196+
PageSupplier<TestResult, Integer> pageSupplier = newBasePageSupplier(pageSize * 3, pageSize);
197+
MockPagerClient c = new MockPagerClient(pageSupplier);
198+
PostFindOptions opts =
199+
getRequiredTestFindOptionsBuilder().limit(pageSize).skip(expectedSkip).build();
200+
TestBookmarkPager pager = new TestBookmarkPager(c, opts);
201+
// Assert skip set for first request
202+
Assert.assertEquals(pager.nextPageOptionsRef.get().skip(), expectedSkip,
203+
"The skip should equal the user provided skip.");
204+
pager.next();
205+
// Assert skip not set for next page
206+
Assert.assertNull(pager.nextPageOptionsRef.get().skip(),
207+
"Skip should not be set for the next page.");
208+
pager.next();
209+
}
210+
190211
}

modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/KeyPageIteratorTest.java

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ public class KeyPageIteratorTest {
4141
* This test sub-class of KeyPager implicitly tests that various abstract methods are correctly
4242
* called.
4343
*/
44-
class TestKeyPager extends KeyPageIterator<Integer, Builder, PostViewOptions, TestResult, Integer> {
44+
class TestKeyPager
45+
extends KeyPageIterator<Integer, Builder, PostViewOptions, TestResult, Integer> {
4546

4647
protected TestKeyPager(Cloudant client, PostViewOptions options) {
4748
super(client, options, OptionsHandler.POST_VIEW);
@@ -289,4 +290,23 @@ Optional<String> checkBoundary(Integer penultimateItem, Integer lastItem) {
289290
Assert.assertFalse(pager.hasNext(), "hasNext() should return false.");
290291
}
291292

293+
@Test
294+
void testSkipRemovedForSubsequentPages() {
295+
int pageSize = 3;
296+
long expectedSkip = 17;
297+
PageSupplier<TestResult, Integer> pageSupplier = newKeyPageSupplier(pageSize * 3, pageSize);
298+
MockPagerClient c = new MockPagerClient(pageSupplier);
299+
PostViewOptions opts =
300+
getRequiredTestOptionsBuilder().limit(pageSize).skip(expectedSkip).build();
301+
TestKeyPager pager = new TestKeyPager(c, opts);
302+
// Assert skip set for first request
303+
Assert.assertEquals(pager.nextPageOptionsRef.get().skip(), expectedSkip,
304+
"The skip should equal the user provided skip.");
305+
pager.next();
306+
// Assert skip not set for next page
307+
Assert.assertNull(pager.nextPageOptionsRef.get().skip(),
308+
"Skip should not be set for the next page.");
309+
pager.next();
310+
}
311+
292312
}

0 commit comments

Comments
 (0)