Skip to content

Commit 05db78e

Browse files
committed
fix: remove skip from subsequent pages
1 parent 621bf03 commit 05db78e

4 files changed

Lines changed: 157 additions & 10 deletions

File tree

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: 111 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,11 @@ B applyLimit(B builder, Long newLimit) {
9494
return this.limitSetter.apply(builder, newLimit);
9595
}
9696

97+
B removeOptsForSubsequentPage(O options, B builder) {
98+
// Default is a no-op
99+
return builder;
100+
}
101+
97102
Long getPageSizeFromOptionsLimit(O opts) {
98103
return Optional.ofNullable(this.limitGetter.apply(opts)).orElse(MAX_LIMIT);
99104
}
@@ -179,13 +184,15 @@ private abstract static class KeyOptionsHandler<B, O, K> extends OptionsHandler<
179184

180185
protected final Function<O, K> keyGetter;
181186
private final Function<O, List<K>> keysGetter;
187+
protected final Function<O, Long> skipGetter;
182188

183189
protected KeyOptionsHandler(Function<B, O> builderToOptions, Function<O, B> optionsToBuilder,
184190
Function<O, Long> limitGetter, BiFunction<B, Long, B> limitSetter, Function<O, K> keyGetter,
185-
Function<O, List<K>> keysGetter) {
191+
Function<O, List<K>> keysGetter, Function<O, Long> skipGetter) {
186192
super(builderToOptions, optionsToBuilder, limitGetter, limitSetter);
187193
this.keyGetter = keyGetter;
188194
this.keysGetter = keysGetter;
195+
this.skipGetter = skipGetter;
189196
}
190197

191198
@Override
@@ -198,6 +205,17 @@ protected void validate(O options) {
198205
validateOptionsAbsent(options, Collections.singletonMap("keys", this.keysGetter));
199206
super.validate(options);
200207
}
208+
209+
@Override
210+
B removeOptsForSubsequentPage(O options, B builder) {
211+
// Unset the skip option if necessary
212+
if (optionIsPresent(options, this.skipGetter)) {
213+
builder = this.builderFromOptions(replaceOpts(builder));
214+
}
215+
return super.removeOptsForSubsequentPage(options, builder);
216+
}
217+
218+
protected abstract O replaceOpts(B builder);
201219
}
202220

203221
private abstract static class ViewsOptionsHandler<B, O> extends KeyOptionsHandler<B, O, Object> {
@@ -214,8 +232,9 @@ protected ViewsOptionsHandler(Function<B, O> builderToOptions, Function<O, B> op
214232
Function<O, Object> keyGetter, Function<O, Object> startKeyGetter,
215233
Function<O, Object> endKeyGetter, BiFunction<B, Object, B> keySetter,
216234
BiFunction<B, Object, B> startKeySetter, BiFunction<B, Object, B> endKeySetter,
217-
Function<O, List<Object>> keysGetter) {
218-
super(builderToOptions, optionsToBuilder, limitGetter, limitSetter, keyGetter, keysGetter);
235+
Function<O, List<Object>> keysGetter, Function<O, Long> skipGetter) {
236+
super(builderToOptions, optionsToBuilder, limitGetter, limitSetter, keyGetter, keysGetter,
237+
skipGetter);
219238
this.startKeyGetter = startKeyGetter;
220239
this.endKeyGetter = endKeyGetter;
221240
this.keySetter = keySetter;
@@ -264,7 +283,7 @@ private static final class AllDocsOptionsHandler
264283
private AllDocsOptionsHandler() {
265284
super(PostAllDocsOptions.Builder::build, PostAllDocsOptions::newBuilder,
266285
PostAllDocsOptions::limit, PostAllDocsOptions.Builder::limit, PostAllDocsOptions::key,
267-
PostAllDocsOptions::keys);
286+
PostAllDocsOptions::keys, PostAllDocsOptions::skip);
268287
}
269288

270289
@Override
@@ -275,6 +294,16 @@ protected void validate(PostAllDocsOptions options) {
275294
super.validate(options);
276295
}
277296

297+
@Override
298+
protected PostAllDocsOptions replaceOpts(PostAllDocsOptions.Builder builder) {
299+
return new PostAllDocsOptions(builder) {
300+
PostAllDocsOptions unsetOpts() {
301+
this.skip = null;
302+
return this;
303+
}
304+
}.unsetOpts();
305+
}
306+
278307
}
279308

280309
private static final class DesignDocsOptionsHandler
@@ -283,7 +312,7 @@ private static final class DesignDocsOptionsHandler
283312
private DesignDocsOptionsHandler() {
284313
super(PostDesignDocsOptions.Builder::build, PostDesignDocsOptions::newBuilder,
285314
PostDesignDocsOptions::limit, PostDesignDocsOptions.Builder::limit,
286-
PostDesignDocsOptions::key, PostDesignDocsOptions::keys);
315+
PostDesignDocsOptions::key, PostDesignDocsOptions::keys, PostDesignDocsOptions::skip);
287316
}
288317

289318
@Override
@@ -294,16 +323,42 @@ protected void validate(PostDesignDocsOptions options) {
294323
super.validate(options);
295324
}
296325

326+
@Override
327+
protected PostDesignDocsOptions replaceOpts(PostDesignDocsOptions.Builder builder) {
328+
return new PostDesignDocsOptions(builder) {
329+
PostDesignDocsOptions unsetOpts() {
330+
this.skip = null;
331+
return this;
332+
}
333+
}.unsetOpts();
334+
}
335+
297336
}
298337

299338
private static final class FindOptionsHandler
300339
extends BookmarkOptionsHandler<PostFindOptions.Builder, PostFindOptions> {
301340

341+
private final Function<PostFindOptions, Long> skipGetter = PostFindOptions::skip;
342+
302343
private FindOptionsHandler() {
303344
super(PostFindOptions.Builder::build, PostFindOptions::newBuilder, PostFindOptions::limit,
304345
PostFindOptions.Builder::limit);
305346
}
306347

348+
@Override
349+
PostFindOptions.Builder removeOptsForSubsequentPage(PostFindOptions options,
350+
PostFindOptions.Builder builder) {
351+
if (optionIsPresent(options, this.skipGetter)) {
352+
return new PostFindOptions(builder) {
353+
PostFindOptions unsetOpts() {
354+
this.skip = null;
355+
return this;
356+
}
357+
}.unsetOpts().newBuilder();
358+
}
359+
return builder;
360+
}
361+
307362
}
308363

309364
private static final class PartitionAllDocsOptionsHandler extends
@@ -312,7 +367,8 @@ private static final class PartitionAllDocsOptionsHandler extends
312367
private PartitionAllDocsOptionsHandler() {
313368
super(PostPartitionAllDocsOptions.Builder::build, PostPartitionAllDocsOptions::newBuilder,
314369
PostPartitionAllDocsOptions::limit, PostPartitionAllDocsOptions.Builder::limit,
315-
PostPartitionAllDocsOptions::key, PostPartitionAllDocsOptions::keys);
370+
PostPartitionAllDocsOptions::key, PostPartitionAllDocsOptions::keys,
371+
PostPartitionAllDocsOptions::skip);
316372
}
317373

318374
@Override
@@ -323,16 +379,43 @@ protected void validate(PostPartitionAllDocsOptions options) {
323379
super.validate(options);
324380
}
325381

382+
@Override
383+
protected PostPartitionAllDocsOptions replaceOpts(PostPartitionAllDocsOptions.Builder builder) {
384+
return new PostPartitionAllDocsOptions(builder) {
385+
PostPartitionAllDocsOptions unsetOpts() {
386+
this.skip = null;
387+
return this;
388+
}
389+
}.unsetOpts();
390+
}
391+
326392
}
327393

328394
private static final class PartitionFindOptionsHandler
329395
extends BookmarkOptionsHandler<PostPartitionFindOptions.Builder, PostPartitionFindOptions> {
330396

397+
private final Function<PostPartitionFindOptions, Long> skipGetter =
398+
PostPartitionFindOptions::skip;
399+
331400
private PartitionFindOptionsHandler() {
332401
super(PostPartitionFindOptions.Builder::build, PostPartitionFindOptions::newBuilder,
333402
PostPartitionFindOptions::limit, PostPartitionFindOptions.Builder::limit);
334403
}
335404

405+
@Override
406+
PostPartitionFindOptions.Builder removeOptsForSubsequentPage(PostPartitionFindOptions options,
407+
PostPartitionFindOptions.Builder builder) {
408+
if (optionIsPresent(options, this.skipGetter)) {
409+
return new PostPartitionFindOptions(builder) {
410+
PostPartitionFindOptions unsetOpts() {
411+
this.skip = null;
412+
return this;
413+
}
414+
}.unsetOpts().newBuilder();
415+
}
416+
return builder;
417+
}
418+
336419
}
337420

338421
private static final class PartitionSearchOptionsHandler extends
@@ -354,7 +437,17 @@ private PartitionViewOptionsHandler() {
354437
PostPartitionViewOptions::key, PostPartitionViewOptions::startKey,
355438
PostPartitionViewOptions::endKey, PostPartitionViewOptions.Builder::key,
356439
PostPartitionViewOptions.Builder::startKey, PostPartitionViewOptions.Builder::endKey,
357-
PostPartitionViewOptions::keys);
440+
PostPartitionViewOptions::keys, PostPartitionViewOptions::skip);
441+
}
442+
443+
@Override
444+
protected PostPartitionViewOptions replaceOpts(PostPartitionViewOptions.Builder builder) {
445+
return new PostPartitionViewOptions(builder) {
446+
PostPartitionViewOptions unsetOpts() {
447+
this.skip = null;
448+
return this;
449+
}
450+
}.unsetOpts();
358451
}
359452

360453
}
@@ -388,7 +481,17 @@ private ViewOptionsHandler() {
388481
super(PostViewOptions.Builder::build, PostViewOptions::newBuilder, PostViewOptions::limit,
389482
PostViewOptions.Builder::limit, PostViewOptions::key, PostViewOptions::startKey,
390483
PostViewOptions::endKey, PostViewOptions.Builder::key, PostViewOptions.Builder::startKey,
391-
PostViewOptions.Builder::endKey, PostViewOptions::keys);
484+
PostViewOptions.Builder::endKey, PostViewOptions::keys, PostViewOptions::skip);
485+
}
486+
487+
@Override
488+
protected PostViewOptions replaceOpts(PostViewOptions.Builder builder) {
489+
return new PostViewOptions(builder) {
490+
PostViewOptions unsetOpts() {
491+
this.skip = null;
492+
return this;
493+
}
494+
}.unsetOpts();
392495
}
393496

394497
}

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)