From c1e942f5f2ff7047cd518a0aabd64f048db0b436 Mon Sep 17 00:00:00 2001 From: Rich Ellis Date: Tue, 28 Jan 2025 15:47:05 +0000 Subject: [PATCH 01/43] feat: add pagination interfaces --- .../features/pagination/AllDocsBasePager.java | 53 ++++++ .../features/pagination/AllDocsPager.java | 52 ++++++ .../pagination/AllDocsPartitionPager.java | 53 ++++++ .../features/pagination/BasePager.java | 64 +++++++ .../features/pagination/BookmarkPager.java | 36 ++++ .../features/pagination/FindBasePager.java | 38 +++++ .../features/pagination/FindPager.java | 52 ++++++ .../pagination/FindPartitionPager.java | 52 ++++++ .../features/pagination/KeyPager.java | 46 +++++ .../cloudant/features/pagination/Pager.java | 157 ++++++++++++++++++ .../features/pagination/SearchBasePager.java | 38 +++++ .../features/pagination/SearchPager.java | 52 ++++++ .../pagination/SearchPartitionPager.java | 52 ++++++ .../features/pagination/ViewBasePager.java | 43 +++++ .../features/pagination/ViewPager.java | 58 +++++++ .../pagination/ViewPartitionPager.java | 58 +++++++ .../features/pagination/package-info.java | 20 +++ 17 files changed, 924 insertions(+) create mode 100644 modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsBasePager.java create mode 100644 modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsPager.java create mode 100644 modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsPartitionPager.java create mode 100644 modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BasePager.java create mode 100644 modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BookmarkPager.java create mode 100644 modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindBasePager.java create mode 100644 modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindPager.java create mode 100644 modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindPartitionPager.java create mode 100644 modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/KeyPager.java create mode 100644 modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/Pager.java create mode 100644 modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchBasePager.java create mode 100644 modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchPager.java create mode 100644 modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchPartitionPager.java create mode 100644 modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewBasePager.java create mode 100644 modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewPager.java create mode 100644 modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewPartitionPager.java create mode 100644 modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/package-info.java diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsBasePager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsBasePager.java new file mode 100644 index 000000000..131261a87 --- /dev/null +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsBasePager.java @@ -0,0 +1,53 @@ +/** + * © Copyright IBM Corporation 2025. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.ibm.cloud.cloudant.features.pagination; + +import java.util.List; +import java.util.Optional; +import java.util.function.BiFunction; +import java.util.function.Function; +import com.ibm.cloud.cloudant.v1.model.AllDocsResult; +import com.ibm.cloud.cloudant.v1.model.DocsResultRow; + +abstract class AllDocsBasePager extends KeyPager { + + protected AllDocsBasePager(O options) { + super(options); + //TODO Auto-generated constructor stub + } + + @Override + protected final Function> itemsGetter() { + return AllDocsResult::getRows; + } + + @Override + protected final Function nextKeyGetter() { + return DocsResultRow::getKey; + } + + @Override + protected final Function nextKeyIdGetter() { + return DocsResultRow::getId; + } + + /** + * Setting start key doc ID is a no-op for all_docs based paging where + * key is the same as id. + */ + @Override + protected final Optional> nextKeyIdSetter() { + return Optional.empty(); + } +} diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsPager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsPager.java new file mode 100644 index 000000000..c17b79353 --- /dev/null +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsPager.java @@ -0,0 +1,52 @@ +/** + * © Copyright IBM Corporation 2025. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.ibm.cloud.cloudant.features.pagination; + +import java.util.function.BiFunction; +import java.util.function.Function; +import com.ibm.cloud.cloudant.v1.Cloudant; +import com.ibm.cloud.cloudant.v1.model.AllDocsResult; +import com.ibm.cloud.cloudant.v1.model.PostAllDocsOptions; +import com.ibm.cloud.cloudant.v1.model.PostAllDocsOptions.Builder; +import com.ibm.cloud.sdk.core.http.ServiceCall; + +final class AllDocsPager extends AllDocsBasePager { + + protected AllDocsPager(PostAllDocsOptions options) { + super(options); + //TODO Auto-generated constructor stub + } + + @Override + protected Function optionsToBuilderFunction() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getOptionsToBuilderFunction'"); + } + + @Override + protected Function builderToOptionsFunction() { + return Builder::build; + } + + @Override + protected BiFunction> nextRequestFunction() { + return Cloudant::postAllDocs; + } + + @Override + protected BiFunction nextKeySetter() { + return Builder::startKey; + } + +} diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsPartitionPager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsPartitionPager.java new file mode 100644 index 000000000..41a0bb21a --- /dev/null +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsPartitionPager.java @@ -0,0 +1,53 @@ +/** + * © Copyright IBM Corporation 2025. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.ibm.cloud.cloudant.features.pagination; + +import java.util.function.BiFunction; +import java.util.function.Function; +import com.ibm.cloud.cloudant.v1.Cloudant; +import com.ibm.cloud.cloudant.v1.model.AllDocsResult; +import com.ibm.cloud.cloudant.v1.model.PostPartitionAllDocsOptions; +import com.ibm.cloud.cloudant.v1.model.PostPartitionAllDocsOptions.Builder; +import com.ibm.cloud.sdk.core.http.ServiceCall; + +final class AllDocsPartitionPager extends AllDocsBasePager { + + protected AllDocsPartitionPager(PostPartitionAllDocsOptions options) { + super(options); + //TODO Auto-generated constructor stub + } + + @Override + protected Function optionsToBuilderFunction() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getOptionsToBuilderFunction'"); + } + + @Override + protected Function builderToOptionsFunction() { + return Builder::build; + } + + @Override + protected BiFunction> nextRequestFunction() { + return Cloudant::postPartitionAllDocs; + } + + + @Override + protected BiFunction nextKeySetter() { + return Builder::startKey; + } + +} diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BasePager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BasePager.java new file mode 100644 index 000000000..8defb0aff --- /dev/null +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BasePager.java @@ -0,0 +1,64 @@ +/** + * © Copyright IBM Corporation 2025. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.ibm.cloud.cloudant.features.pagination; + +import java.util.List; +import java.util.function.BiFunction; +import java.util.function.Function; +import com.ibm.cloud.cloudant.v1.Cloudant; +import com.ibm.cloud.sdk.core.http.ServiceCall; + +abstract class BasePager implements Pager { + + protected O nextPageOptions; + + protected BasePager(O options) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented constructor 'BasePager'"); + } + + @Override + public final boolean hasNext() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'hasNext'"); + } + + @Override + public final List getNext() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getNext'"); + } + + @Override + public final List getAll() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getAll'"); + } + + protected final List nextRequest() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'nextRequest'"); + } + + protected abstract Function optionsToBuilderFunction(); + + protected abstract Function builderToOptionsFunction(); + + protected abstract Function> itemsGetter(); + + protected abstract void setNextPageOptions(B builder, R result); + + protected abstract BiFunction> nextRequestFunction(); + +} diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BookmarkPager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BookmarkPager.java new file mode 100644 index 000000000..fca54dc40 --- /dev/null +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BookmarkPager.java @@ -0,0 +1,36 @@ +/** + * © Copyright IBM Corporation 2025. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.ibm.cloud.cloudant.features.pagination; + +import java.util.function.BiFunction; +import java.util.function.Function; + +abstract class BookmarkPager extends BasePager { + + protected BookmarkPager(O options) { + super(options); + //TODO Auto-generated constructor stub + } + + protected abstract Function bookmarkGetter(); + + protected abstract BiFunction bookmarkSetter(); + + @Override + protected final void setNextPageOptions(B builder, R result) { + String bookmark = bookmarkGetter().apply(result); + bookmarkSetter().apply(builder, bookmark); + } + +} diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindBasePager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindBasePager.java new file mode 100644 index 000000000..be9b79b83 --- /dev/null +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindBasePager.java @@ -0,0 +1,38 @@ +/** + * © Copyright IBM Corporation 2025. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.ibm.cloud.cloudant.features.pagination; + +import java.util.List; +import java.util.function.Function; +import com.ibm.cloud.cloudant.v1.model.Document; +import com.ibm.cloud.cloudant.v1.model.FindResult; + +abstract class FindBasePager extends BookmarkPager { + + protected FindBasePager(O options) { + super(options); + //TODO Auto-generated constructor stub + } + + @Override + protected final Function> itemsGetter() { + return FindResult::getDocs; + } + + @Override + protected final Function bookmarkGetter() { + return FindResult::getBookmark; + } + +} diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindPager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindPager.java new file mode 100644 index 000000000..99fc05e80 --- /dev/null +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindPager.java @@ -0,0 +1,52 @@ +/** + * © Copyright IBM Corporation 2025. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.ibm.cloud.cloudant.features.pagination; + +import java.util.function.BiFunction; +import java.util.function.Function; +import com.ibm.cloud.cloudant.v1.Cloudant; +import com.ibm.cloud.cloudant.v1.model.FindResult; +import com.ibm.cloud.cloudant.v1.model.PostFindOptions; +import com.ibm.cloud.cloudant.v1.model.PostFindOptions.Builder; +import com.ibm.cloud.sdk.core.http.ServiceCall; + +final class FindPager extends FindBasePager { + + protected FindPager(PostFindOptions options) { + super(options); + //TODO Auto-generated constructor stub + } + + @Override + protected Function optionsToBuilderFunction() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getOptionsToBuilderFunction'"); + } + + @Override + protected Function builderToOptionsFunction() { + return Builder::build; + } + + @Override + protected BiFunction bookmarkSetter() { + return Builder::bookmark; + } + + @Override + protected BiFunction> nextRequestFunction() { + return Cloudant::postFind; + } + +} diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindPartitionPager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindPartitionPager.java new file mode 100644 index 000000000..ff01d637a --- /dev/null +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindPartitionPager.java @@ -0,0 +1,52 @@ +/** + * © Copyright IBM Corporation 2025. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.ibm.cloud.cloudant.features.pagination; + +import java.util.function.BiFunction; +import java.util.function.Function; +import com.ibm.cloud.cloudant.v1.Cloudant; +import com.ibm.cloud.cloudant.v1.model.FindResult; +import com.ibm.cloud.cloudant.v1.model.PostPartitionFindOptions; +import com.ibm.cloud.cloudant.v1.model.PostPartitionFindOptions.Builder; +import com.ibm.cloud.sdk.core.http.ServiceCall; + +final class FindPartitionPager extends FindBasePager { + + protected FindPartitionPager(PostPartitionFindOptions options) { + super(options); + //TODO Auto-generated constructor stub + } + + @Override + protected Function optionsToBuilderFunction() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getOptionsToBuilderFunction'"); + } + + @Override + protected Function builderToOptionsFunction() { + return Builder::build; + } + + @Override + protected BiFunction bookmarkSetter() { + return Builder::bookmark; + } + + @Override + protected BiFunction> nextRequestFunction() { + return Cloudant::postPartitionFind; + } + +} diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/KeyPager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/KeyPager.java new file mode 100644 index 000000000..92dd8026e --- /dev/null +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/KeyPager.java @@ -0,0 +1,46 @@ +/** + * © Copyright IBM Corporation 2025. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.ibm.cloud.cloudant.features.pagination; + +import java.util.List; +import java.util.Optional; +import java.util.function.BiFunction; +import java.util.function.Function; + +abstract class KeyPager extends BasePager { + + protected KeyPager(O options) { + super(options); + //TODO Auto-generated constructor stub + } + + protected abstract BiFunction nextKeySetter(); + + protected abstract Optional> nextKeyIdSetter(); + + protected abstract Function nextKeyGetter(); + + protected abstract Function nextKeyIdGetter(); + + @Override + protected final void setNextPageOptions(B builder, R result) { + List items = itemsGetter().apply(result); + I lastItem = items.get(items.size() - 1); + K nextKey = nextKeyGetter().apply(lastItem); + String nextId = nextKeyIdGetter().apply(lastItem); + nextKeySetter().apply(builder, nextKey); + nextKeyIdSetter().ifPresent(f -> f.apply(builder, nextId)); + } + +} diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/Pager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/Pager.java new file mode 100644 index 000000000..73e573626 --- /dev/null +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/Pager.java @@ -0,0 +1,157 @@ +/** + * © Copyright IBM Corporation 2025. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.ibm.cloud.cloudant.features.pagination; + +import java.util.List; + +import com.ibm.cloud.cloudant.v1.Cloudant; +import com.ibm.cloud.cloudant.v1.model.DocsResultRow; +import com.ibm.cloud.cloudant.v1.model.Document; +import com.ibm.cloud.cloudant.v1.model.PostAllDocsOptions; +import com.ibm.cloud.cloudant.v1.model.PostFindOptions; +import com.ibm.cloud.cloudant.v1.model.PostPartitionAllDocsOptions; +import com.ibm.cloud.cloudant.v1.model.PostPartitionFindOptions; +import com.ibm.cloud.cloudant.v1.model.PostPartitionSearchOptions; +import com.ibm.cloud.cloudant.v1.model.PostPartitionViewOptions; +import com.ibm.cloud.cloudant.v1.model.PostSearchOptions; +import com.ibm.cloud.cloudant.v1.model.PostViewOptions; +import com.ibm.cloud.cloudant.v1.model.SearchResultRow; +import com.ibm.cloud.cloudant.v1.model.ViewResultRow; + +/** + * Interface for pagination of database operations. + * + * Use the static methods to instantiate a Pager instance for + * the required operation. + */ +public interface Pager { + + /** + * Returns {@code true} if there may be another page. + * + * @return {@code false} if there are no more pages + */ + boolean hasNext(); + + /** + * Get the next page in the sequence. + * + * @return java.util.List of the items from the next page + */ + List getNext(); + + /** + * Get all the avaialble pages and collect them into a + * single java.util.List. + * + * @return java.util.List of the items from all the pages + */ + List getAll(); + + /** + * Get a Pager for the postAllDocs operation. + * The page size is configured with the limit paramater of the options. + * + * @param client com.ibm.cloud.cloudant.v1.Cloudant instance to make page requests + * @param options com.ibm.cloud.cloudant.v1.model.PostAllDocsOptions for the query + * @return a pager for all the documents in the database + */ + static Pager newPager(Cloudant client, PostAllDocsOptions options) { + throw new UnsupportedOperationException("Not yet implemented."); + } + + /** + * Get a Pager for the postPartitionAllDocs operation. + * The page size is configured with the limit paramater of the options. + * + * @param client com.ibm.cloud.cloudant.v1.Cloudant instance to make page requests + * @param options com.ibm.cloud.cloudant.v1.model.PostPartitionAllDocsOptions for the query + * @return a pager for all the documents in a database partition + */ + static Pager newPager(Cloudant client, PostPartitionAllDocsOptions options) { + throw new UnsupportedOperationException("Not yet implemented."); + } + + /** + * Get a Pager for the postFind operation. + * The page size is configured with the limit paramater of the options. + * + * @param client com.ibm.cloud.cloudant.v1.Cloudant instance to make page requests + * @param options com.ibm.cloud.cloudant.v1.model.PostFindOptions for the query + * @return a pager for the result of a find query + */ + static Pager newPager(Cloudant client, PostFindOptions options) { + throw new UnsupportedOperationException("Not yet implemented."); + } + + /** + * Get a Pager for the postPartitionFind operation. + * + * @param client com.ibm.cloud.cloudant.v1.Cloudant instance to make page requests + * @param options com.ibm.cloud.cloudant.v1.model.PostPartitionFindOptions for the query + * @return a pager for the result of a partition find query + */ + static Pager newPager(Cloudant client, PostPartitionFindOptions options) { + throw new UnsupportedOperationException("Not yet implemented."); + } + + /** + * Get a Pager for the postSearch operation. + * The page size is configured with the limit paramater of the options. + * + * @param client com.ibm.cloud.cloudant.v1.Cloudant instance to make page requests + * @param options com.ibm.cloud.cloudant.v1.model.PostSearchOptions for the query + * @return a pager for the result of a search query + */ + static Pager newPager(Cloudant client, PostSearchOptions options) { + throw new UnsupportedOperationException("Not yet implemented."); + } + + /** + * Get a Pager for the postPartitionSearch operation. + * The page size is configured with the limit paramater of the options. + * + * @param client com.ibm.cloud.cloudant.v1.Cloudant instance to make page requests + * @param options com.ibm.cloud.cloudant.v1.model.PostPartitionSearchOptions for the query + * @return a pager for the result of a partition search query + */ + static Pager newPager(Cloudant client, PostPartitionSearchOptions options) { + throw new UnsupportedOperationException("Not yet implemented."); + } + + /** + * Get a Pager for the postView operation. + * The page size is configured with the limit paramater of the options. + * + * @param client com.ibm.cloud.cloudant.v1.Cloudant instance to make page requests + * @param options com.ibm.cloud.cloudant.v1.model.PostViewOptions for the query + * @return a pager for the result of a view query + */ + static Pager newPager(Cloudant client, PostViewOptions options) { + throw new UnsupportedOperationException("Not yet implemented."); + } + + /** + * Get a Pager for the postPartitionView operation. + * The page size is configured with the limit paramater of the options. + * + * @param client com.ibm.cloud.cloudant.v1.Cloudant instance to make page requests + * @param options com.ibm.cloud.cloudant.v1.model.PostPartitionViewOptions for the query + * @return a pager for the result of a partition view query + */ + static Pager newPager(Cloudant client, PostPartitionViewOptions options) { + throw new UnsupportedOperationException("Not yet implemented."); + } + +} diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchBasePager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchBasePager.java new file mode 100644 index 000000000..57d4c6d6d --- /dev/null +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchBasePager.java @@ -0,0 +1,38 @@ +/** + * © Copyright IBM Corporation 2025. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.ibm.cloud.cloudant.features.pagination; + +import java.util.List; +import java.util.function.Function; +import com.ibm.cloud.cloudant.v1.model.SearchResult; +import com.ibm.cloud.cloudant.v1.model.SearchResultRow; + +abstract class SearchBasePager extends BookmarkPager { + + protected SearchBasePager(O options) { + super(options); + //TODO Auto-generated constructor stub + } + + @Override + protected final Function> itemsGetter() { + return SearchResult::getRows; + } + + @Override + protected final Function bookmarkGetter() { + return SearchResult::getBookmark; + } + +} diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchPager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchPager.java new file mode 100644 index 000000000..447df3e20 --- /dev/null +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchPager.java @@ -0,0 +1,52 @@ +/** + * © Copyright IBM Corporation 2025. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.ibm.cloud.cloudant.features.pagination; + +import java.util.function.BiFunction; +import java.util.function.Function; +import com.ibm.cloud.cloudant.v1.Cloudant; +import com.ibm.cloud.cloudant.v1.model.PostSearchOptions; +import com.ibm.cloud.cloudant.v1.model.PostSearchOptions.Builder; +import com.ibm.cloud.cloudant.v1.model.SearchResult; +import com.ibm.cloud.sdk.core.http.ServiceCall; + +final class SearchPager extends SearchBasePager { + + protected SearchPager(PostSearchOptions options) { + super(options); + //TODO Auto-generated constructor stub + } + + @Override + protected Function optionsToBuilderFunction() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getOptionsToBuilderFunction'"); + } + + @Override + protected Function builderToOptionsFunction() { + return Builder::build; + } + + @Override + protected BiFunction bookmarkSetter() { + return Builder::bookmark; + } + + @Override + protected BiFunction> nextRequestFunction() { + return Cloudant::postSearch; + } + +} diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchPartitionPager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchPartitionPager.java new file mode 100644 index 000000000..861d28994 --- /dev/null +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchPartitionPager.java @@ -0,0 +1,52 @@ +/** + * © Copyright IBM Corporation 2025. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.ibm.cloud.cloudant.features.pagination; + +import java.util.function.BiFunction; +import java.util.function.Function; +import com.ibm.cloud.cloudant.v1.Cloudant; +import com.ibm.cloud.cloudant.v1.model.PostPartitionSearchOptions; +import com.ibm.cloud.cloudant.v1.model.PostPartitionSearchOptions.Builder; +import com.ibm.cloud.cloudant.v1.model.SearchResult; +import com.ibm.cloud.sdk.core.http.ServiceCall; + +final class SearchPartitionPager extends SearchBasePager { + + protected SearchPartitionPager(PostPartitionSearchOptions options) { + super(options); + //TODO Auto-generated constructor stub + } + + @Override + protected Function optionsToBuilderFunction() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getOptionsToBuilderFunction'"); + } + + @Override + protected Function builderToOptionsFunction() { + return Builder::build; + } + + @Override + protected BiFunction bookmarkSetter() { + return Builder::bookmark; + } + + @Override + protected BiFunction> nextRequestFunction() { + return Cloudant::postPartitionSearch; + } + +} diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewBasePager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewBasePager.java new file mode 100644 index 000000000..a620cba3d --- /dev/null +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewBasePager.java @@ -0,0 +1,43 @@ +/** + * © Copyright IBM Corporation 2025. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.ibm.cloud.cloudant.features.pagination; + +import java.util.List; +import java.util.function.Function; +import com.ibm.cloud.cloudant.v1.model.ViewResult; +import com.ibm.cloud.cloudant.v1.model.ViewResultRow; + +abstract class ViewBasePager extends KeyPager { + + protected ViewBasePager(O options) { + super(options); + //TODO Auto-generated constructor stub + } + + @Override + protected final Function> itemsGetter() { + return ViewResult::getRows; + } + + @Override + protected final Function nextKeyGetter() { + return ViewResultRow::getKey; + } + + @Override + protected final Function nextKeyIdGetter() { + return ViewResultRow::getId; + } + +} diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewPager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewPager.java new file mode 100644 index 000000000..b6bffa063 --- /dev/null +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewPager.java @@ -0,0 +1,58 @@ +/** + * © Copyright IBM Corporation 2025. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.ibm.cloud.cloudant.features.pagination; + +import java.util.Optional; +import java.util.function.BiFunction; +import java.util.function.Function; +import com.ibm.cloud.cloudant.v1.Cloudant; +import com.ibm.cloud.cloudant.v1.model.PostViewOptions; +import com.ibm.cloud.cloudant.v1.model.PostViewOptions.Builder; +import com.ibm.cloud.cloudant.v1.model.ViewResult; +import com.ibm.cloud.sdk.core.http.ServiceCall; + +final class ViewPager extends ViewBasePager { + + protected ViewPager(PostViewOptions options) { + super(options); + //TODO Auto-generated constructor stub + } + + @Override + protected Function optionsToBuilderFunction() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getOptionsToBuilderFunction'"); + } + + @Override + protected Function builderToOptionsFunction() { + return Builder::build; + } + + @Override + protected BiFunction> nextRequestFunction() { + return Cloudant::postView; + } + + @Override + protected BiFunction nextKeySetter() { + return Builder::startKey; + } + + @Override + protected Optional> nextKeyIdSetter() { + return Optional.of(Builder::startKeyDocId); + } + +} diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewPartitionPager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewPartitionPager.java new file mode 100644 index 000000000..37cc1da29 --- /dev/null +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewPartitionPager.java @@ -0,0 +1,58 @@ +/** + * © Copyright IBM Corporation 2025. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.ibm.cloud.cloudant.features.pagination; + +import java.util.Optional; +import java.util.function.BiFunction; +import java.util.function.Function; +import com.ibm.cloud.cloudant.v1.Cloudant; +import com.ibm.cloud.cloudant.v1.model.PostPartitionViewOptions; +import com.ibm.cloud.cloudant.v1.model.PostPartitionViewOptions.Builder; +import com.ibm.cloud.cloudant.v1.model.ViewResult; +import com.ibm.cloud.sdk.core.http.ServiceCall; + +final class ViewPartitionPager extends ViewBasePager { + + protected ViewPartitionPager(PostPartitionViewOptions options) { + super(options); + //TODO Auto-generated constructor stub + } + + @Override + protected Function optionsToBuilderFunction() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getOptionsToBuilderFunction'"); + } + + @Override + protected Function builderToOptionsFunction() { + return Builder::build; + } + + @Override + protected BiFunction> nextRequestFunction() { + return Cloudant::postPartitionView; + } + + @Override + protected BiFunction nextKeySetter() { + return Builder::startKey; + } + + @Override + protected Optional> nextKeyIdSetter() { + return Optional.of(Builder::startKeyDocId); + } + +} diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/package-info.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/package-info.java new file mode 100644 index 000000000..1450bdcd5 --- /dev/null +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/package-info.java @@ -0,0 +1,20 @@ +/** + * © Copyright IBM Corporation 2025. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +/** + * Feature for paginating requests. + * + * Accessed via the com.ibm.cloud.cloudant.features.pagination.Pager interface. + * + */ +package com.ibm.cloud.cloudant.features.pagination; From 710384f7dcc862c8b2f0104e9a9c9f3fb2e12dc5 Mon Sep 17 00:00:00 2001 From: Rich Ellis Date: Mon, 10 Feb 2025 17:24:45 +0000 Subject: [PATCH 02/43] fix: client in ctors --- .../cloudant/features/pagination/AllDocsBasePager.java | 6 +++--- .../cloud/cloudant/features/pagination/AllDocsPager.java | 5 ++--- .../cloudant/features/pagination/AllDocsPartitionPager.java | 4 ++-- .../ibm/cloud/cloudant/features/pagination/BasePager.java | 2 +- .../cloud/cloudant/features/pagination/BookmarkPager.java | 6 +++--- .../cloud/cloudant/features/pagination/FindBasePager.java | 6 +++--- .../ibm/cloud/cloudant/features/pagination/FindPager.java | 5 ++--- .../cloudant/features/pagination/FindPartitionPager.java | 5 ++--- .../ibm/cloud/cloudant/features/pagination/KeyPager.java | 6 +++--- .../cloud/cloudant/features/pagination/SearchBasePager.java | 6 +++--- .../ibm/cloud/cloudant/features/pagination/SearchPager.java | 5 ++--- .../cloudant/features/pagination/SearchPartitionPager.java | 5 ++--- .../cloud/cloudant/features/pagination/ViewBasePager.java | 6 +++--- .../ibm/cloud/cloudant/features/pagination/ViewPager.java | 5 ++--- .../cloudant/features/pagination/ViewPartitionPager.java | 5 ++--- 15 files changed, 35 insertions(+), 42 deletions(-) diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsBasePager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsBasePager.java index 131261a87..ad7cc2bed 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsBasePager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsBasePager.java @@ -17,14 +17,14 @@ import java.util.Optional; import java.util.function.BiFunction; import java.util.function.Function; +import com.ibm.cloud.cloudant.v1.Cloudant; import com.ibm.cloud.cloudant.v1.model.AllDocsResult; import com.ibm.cloud.cloudant.v1.model.DocsResultRow; abstract class AllDocsBasePager extends KeyPager { - protected AllDocsBasePager(O options) { - super(options); - //TODO Auto-generated constructor stub + protected AllDocsBasePager(Cloudant client, O options) { + super(client, options); } @Override diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsPager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsPager.java index c17b79353..e475b6b82 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsPager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsPager.java @@ -23,9 +23,8 @@ final class AllDocsPager extends AllDocsBasePager { - protected AllDocsPager(PostAllDocsOptions options) { - super(options); - //TODO Auto-generated constructor stub + protected AllDocsPager(Cloudant client, PostAllDocsOptions options) { + super(client, options); } @Override diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsPartitionPager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsPartitionPager.java index 41a0bb21a..a1aaeeda7 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsPartitionPager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsPartitionPager.java @@ -23,8 +23,8 @@ final class AllDocsPartitionPager extends AllDocsBasePager { - protected AllDocsPartitionPager(PostPartitionAllDocsOptions options) { - super(options); + protected AllDocsPartitionPager(Cloudant client, PostPartitionAllDocsOptions options) { + super(client, options); //TODO Auto-generated constructor stub } diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BasePager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BasePager.java index 8defb0aff..95251046b 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BasePager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BasePager.java @@ -23,7 +23,7 @@ abstract class BasePager implements Pager { protected O nextPageOptions; - protected BasePager(O options) { + protected BasePager(Cloudant client, O options) { // TODO Auto-generated method stub throw new UnsupportedOperationException("Unimplemented constructor 'BasePager'"); } diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BookmarkPager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BookmarkPager.java index fca54dc40..1eeff5dac 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BookmarkPager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BookmarkPager.java @@ -15,12 +15,12 @@ import java.util.function.BiFunction; import java.util.function.Function; +import com.ibm.cloud.cloudant.v1.Cloudant; abstract class BookmarkPager extends BasePager { - protected BookmarkPager(O options) { - super(options); - //TODO Auto-generated constructor stub + protected BookmarkPager(Cloudant client, O options) { + super(client, options); } protected abstract Function bookmarkGetter(); diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindBasePager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindBasePager.java index be9b79b83..9a8ae50e6 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindBasePager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindBasePager.java @@ -15,14 +15,14 @@ import java.util.List; import java.util.function.Function; +import com.ibm.cloud.cloudant.v1.Cloudant; import com.ibm.cloud.cloudant.v1.model.Document; import com.ibm.cloud.cloudant.v1.model.FindResult; abstract class FindBasePager extends BookmarkPager { - protected FindBasePager(O options) { - super(options); - //TODO Auto-generated constructor stub + protected FindBasePager(Cloudant client, O options) { + super(client, options); } @Override diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindPager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindPager.java index 99fc05e80..bdef0104b 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindPager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindPager.java @@ -23,9 +23,8 @@ final class FindPager extends FindBasePager { - protected FindPager(PostFindOptions options) { - super(options); - //TODO Auto-generated constructor stub + protected FindPager(Cloudant client, PostFindOptions options) { + super(client, options); } @Override diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindPartitionPager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindPartitionPager.java index ff01d637a..b6c3c1e86 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindPartitionPager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindPartitionPager.java @@ -23,9 +23,8 @@ final class FindPartitionPager extends FindBasePager { - protected FindPartitionPager(PostPartitionFindOptions options) { - super(options); - //TODO Auto-generated constructor stub + protected FindPartitionPager(Cloudant client, PostPartitionFindOptions options) { + super(client, options); } @Override diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/KeyPager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/KeyPager.java index 92dd8026e..510960abf 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/KeyPager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/KeyPager.java @@ -17,12 +17,12 @@ import java.util.Optional; import java.util.function.BiFunction; import java.util.function.Function; +import com.ibm.cloud.cloudant.v1.Cloudant; abstract class KeyPager extends BasePager { - protected KeyPager(O options) { - super(options); - //TODO Auto-generated constructor stub + protected KeyPager(Cloudant client, O options) { + super(client, options); } protected abstract BiFunction nextKeySetter(); diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchBasePager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchBasePager.java index 57d4c6d6d..dfa705d68 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchBasePager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchBasePager.java @@ -15,14 +15,14 @@ import java.util.List; import java.util.function.Function; +import com.ibm.cloud.cloudant.v1.Cloudant; import com.ibm.cloud.cloudant.v1.model.SearchResult; import com.ibm.cloud.cloudant.v1.model.SearchResultRow; abstract class SearchBasePager extends BookmarkPager { - protected SearchBasePager(O options) { - super(options); - //TODO Auto-generated constructor stub + protected SearchBasePager(Cloudant client, O options) { + super(client, options); } @Override diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchPager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchPager.java index 447df3e20..b44e70553 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchPager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchPager.java @@ -23,9 +23,8 @@ final class SearchPager extends SearchBasePager { - protected SearchPager(PostSearchOptions options) { - super(options); - //TODO Auto-generated constructor stub + protected SearchPager(Cloudant client, PostSearchOptions options) { + super(client, options); } @Override diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchPartitionPager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchPartitionPager.java index 861d28994..8da205c9e 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchPartitionPager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchPartitionPager.java @@ -23,9 +23,8 @@ final class SearchPartitionPager extends SearchBasePager { - protected SearchPartitionPager(PostPartitionSearchOptions options) { - super(options); - //TODO Auto-generated constructor stub + protected SearchPartitionPager(Cloudant client, PostPartitionSearchOptions options) { + super(client, options); } @Override diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewBasePager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewBasePager.java index a620cba3d..5c555231a 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewBasePager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewBasePager.java @@ -15,14 +15,14 @@ import java.util.List; import java.util.function.Function; +import com.ibm.cloud.cloudant.v1.Cloudant; import com.ibm.cloud.cloudant.v1.model.ViewResult; import com.ibm.cloud.cloudant.v1.model.ViewResultRow; abstract class ViewBasePager extends KeyPager { - protected ViewBasePager(O options) { - super(options); - //TODO Auto-generated constructor stub + protected ViewBasePager(Cloudant client, O options) { + super(client, options); } @Override diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewPager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewPager.java index b6bffa063..633d07bb3 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewPager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewPager.java @@ -24,9 +24,8 @@ final class ViewPager extends ViewBasePager { - protected ViewPager(PostViewOptions options) { - super(options); - //TODO Auto-generated constructor stub + protected ViewPager(Cloudant client, PostViewOptions options) { + super(client, options); } @Override diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewPartitionPager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewPartitionPager.java index 37cc1da29..844ca2658 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewPartitionPager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewPartitionPager.java @@ -24,9 +24,8 @@ final class ViewPartitionPager extends ViewBasePager { - protected ViewPartitionPager(PostPartitionViewOptions options) { - super(options); - //TODO Auto-generated constructor stub + protected ViewPartitionPager(Cloudant client, PostPartitionViewOptions options) { + super(client, options); } @Override From 4a1ba8d323d68009920ea96da9ba2441309640ea Mon Sep 17 00:00:00 2001 From: Rich Ellis Date: Mon, 10 Feb 2025 17:33:23 +0000 Subject: [PATCH 03/43] fix: add limit getters --- .../ibm/cloud/cloudant/features/pagination/AllDocsPager.java | 5 +++++ .../cloudant/features/pagination/AllDocsPartitionPager.java | 5 +++++ .../ibm/cloud/cloudant/features/pagination/BasePager.java | 2 ++ .../ibm/cloud/cloudant/features/pagination/FindPager.java | 5 +++++ .../cloudant/features/pagination/FindPartitionPager.java | 5 +++++ .../ibm/cloud/cloudant/features/pagination/SearchPager.java | 5 +++++ .../cloudant/features/pagination/SearchPartitionPager.java | 5 +++++ .../ibm/cloud/cloudant/features/pagination/ViewPager.java | 5 +++++ .../cloudant/features/pagination/ViewPartitionPager.java | 5 +++++ 9 files changed, 42 insertions(+) diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsPager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsPager.java index e475b6b82..dce4fae88 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsPager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsPager.java @@ -48,4 +48,9 @@ protected BiFunction nextKeySetter() { return Builder::startKey; } + @Override + protected Function limitGetter() { + return PostAllDocsOptions::limit; + } + } diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsPartitionPager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsPartitionPager.java index a1aaeeda7..e5265d466 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsPartitionPager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsPartitionPager.java @@ -50,4 +50,9 @@ protected BiFunction nextKeySetter() { return Builder::startKey; } + @Override + protected Function limitGetter() { + return PostPartitionAllDocsOptions::limit; + } + } diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BasePager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BasePager.java index 95251046b..d9fe0288e 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BasePager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BasePager.java @@ -61,4 +61,6 @@ protected final List nextRequest() { protected abstract BiFunction> nextRequestFunction(); + protected abstract Function limitGetter(); + } diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindPager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindPager.java index bdef0104b..379387edb 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindPager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindPager.java @@ -48,4 +48,9 @@ protected BiFunction> nextReq return Cloudant::postFind; } + @Override + protected Function limitGetter() { + return PostFindOptions::limit; + } + } diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindPartitionPager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindPartitionPager.java index b6c3c1e86..f8118c2e4 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindPartitionPager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindPartitionPager.java @@ -48,4 +48,9 @@ protected BiFunction return Cloudant::postPartitionFind; } + @Override + protected Function limitGetter() { + return PostPartitionFindOptions::limit; + } + } diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchPager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchPager.java index b44e70553..e7b8d9942 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchPager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchPager.java @@ -48,4 +48,9 @@ protected BiFunction> nex return Cloudant::postSearch; } + @Override + protected Function limitGetter() { + return PostSearchOptions::limit; + } + } diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchPartitionPager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchPartitionPager.java index 8da205c9e..bb19a35b4 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchPartitionPager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchPartitionPager.java @@ -48,4 +48,9 @@ protected BiFunction limitGetter() { + return PostPartitionSearchOptions::limit; + } + } diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewPager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewPager.java index 633d07bb3..5293b999c 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewPager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewPager.java @@ -54,4 +54,9 @@ protected Optional> nextKeyIdSetter() { return Optional.of(Builder::startKeyDocId); } + @Override + protected Function limitGetter() { + return PostViewOptions::limit; + } + } diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewPartitionPager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewPartitionPager.java index 844ca2658..c052b87a4 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewPartitionPager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewPartitionPager.java @@ -54,4 +54,9 @@ protected Optional> nextKeyIdSetter() { return Optional.of(Builder::startKeyDocId); } + @Override + protected Function limitGetter() { + return PostPartitionViewOptions::limit; + } + } From cb4f1ed3585417b9ae212a6e9a69159eaa469ddd Mon Sep 17 00:00:00 2001 From: Rich Ellis Date: Mon, 10 Feb 2025 17:36:02 +0000 Subject: [PATCH 04/43] feat: base pager implementation --- .../features/pagination/BasePager.java | 72 +++++++++++++++---- .../cloudant/features/pagination/Pager.java | 2 +- 2 files changed, 61 insertions(+), 13 deletions(-) diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BasePager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BasePager.java index d9fe0288e..0a22a1871 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BasePager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BasePager.java @@ -13,42 +13,77 @@ package com.ibm.cloud.cloudant.features.pagination; +import java.util.Collections; +import java.util.Iterator; import java.util.List; +import java.util.NoSuchElementException; +import java.util.Optional; +import java.util.Spliterator; +import java.util.Spliterators; import java.util.function.BiFunction; import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; import com.ibm.cloud.cloudant.v1.Cloudant; import com.ibm.cloud.sdk.core.http.ServiceCall; -abstract class BasePager implements Pager { +abstract class BasePager implements Pager, Iterator> { - protected O nextPageOptions; + protected final Cloudant client; + protected final long pageSize; + protected volatile boolean hasNext = true; + protected volatile O nextPageOptions; protected BasePager(Cloudant client, O options) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented constructor 'BasePager'"); + this.client = client; + // TODO validate initial options + // Set the page size from the supplied options limit + this.pageSize = getPageSizeFromOptionsLimit(options); + // Clone the supplied options into the nextPageOptions + this.buildAndSetOptions(this.optionsToBuilderFunction().apply(options)); } @Override public final boolean hasNext() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'hasNext'"); + return this.hasNext; + } + + @Override + public List next() { + return getNext(); } @Override public final List getNext() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getNext'"); + if (this.hasNext()) { + return Collections.unmodifiableList(this.nextRequest()); + } else { + throw new NoSuchElementException(); + } } @Override public final List getAll() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getAll'"); + return StreamSupport.stream(this.spliterator(), false) + .flatMap(List::stream).collect(Collectors.toList()); } protected final List nextRequest() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'nextRequest'"); + ServiceCall request = nextRequestFunction().apply(this.client, nextPageOptions); + R result = request.execute().getResult(); + List items = itemsGetter().apply(result); + if (items.size() < this.pageSize) { + this.hasNext = false; + } else { + B optionsBuilder = optionsToBuilderFunction().apply(nextPageOptions); + setNextPageOptions(optionsBuilder, result); + this.buildAndSetOptions(optionsBuilder); + } + return items; + } + + private void buildAndSetOptions(B optionsBuilder) { + this.nextPageOptions = this.builderToOptionsFunction().apply(optionsBuilder); } protected abstract Function optionsToBuilderFunction(); @@ -63,4 +98,17 @@ protected final List nextRequest() { protected abstract Function limitGetter(); + protected Long getPageSizeFromOptionsLimit(O opts) { + return Optional.ofNullable(limitGetter().apply(opts)).orElse(200L); + } + + @Override + public Iterator> iterator() { + return this; + } + + @Override + public Spliterator> spliterator() { + return Spliterators.spliteratorUnknownSize(this, Spliterator.ORDERED | Spliterator.NONNULL | Spliterator.IMMUTABLE); + } } diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/Pager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/Pager.java index 73e573626..93a033909 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/Pager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/Pager.java @@ -35,7 +35,7 @@ * Use the static methods to instantiate a Pager instance for * the required operation. */ -public interface Pager { +public interface Pager extends Iterable> { /** * Returns {@code true} if there may be another page. From 896e1b9083ffc5b567e320dd5695f2884cbf4dbd Mon Sep 17 00:00:00 2001 From: Rich Ellis Date: Mon, 10 Feb 2025 17:33:43 +0000 Subject: [PATCH 05/43] feat: KeyPager nextRequest override --- .../cloudant/features/pagination/BasePager.java | 2 +- .../cloudant/features/pagination/KeyPager.java | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BasePager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BasePager.java index 0a22a1871..ee052261d 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BasePager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BasePager.java @@ -68,7 +68,7 @@ public final List getAll() { .flatMap(List::stream).collect(Collectors.toList()); } - protected final List nextRequest() { + protected List nextRequest() { ServiceCall request = nextRequestFunction().apply(this.client, nextPageOptions); R result = request.execute().getResult(); List items = itemsGetter().apply(result); diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/KeyPager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/KeyPager.java index 510960abf..76dc2267f 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/KeyPager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/KeyPager.java @@ -33,6 +33,14 @@ protected KeyPager(Cloudant client, O options) { protected abstract Function nextKeyIdGetter(); + protected List nextRequest() { + List items = super.nextRequest(); + if (hasNext()) { + items.remove(items.size() - 1); + } + return items; + } + @Override protected final void setNextPageOptions(B builder, R result) { List items = itemsGetter().apply(result); @@ -43,4 +51,9 @@ protected final void setNextPageOptions(B builder, R result) { nextKeyIdSetter().ifPresent(f -> f.apply(builder, nextId)); } + @Override + protected Long getPageSizeFromOptionsLimit(O opts) { + return super.getPageSizeFromOptionsLimit(opts) + 1; + } + } From 13ce6da19593cd0684cf5984e19b7dd180b58fb2 Mon Sep 17 00:00:00 2001 From: Rich Ellis Date: Wed, 12 Feb 2025 10:46:44 +0000 Subject: [PATCH 06/43] test: use Java 11 source version for test compile --- pom.xml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/pom.xml b/pom.xml index 9ada9ab54..3e3c6d531 100644 --- a/pom.xml +++ b/pom.xml @@ -298,6 +298,19 @@ 1.8 1.8 + + + test-compile + process-test-sources + + testCompile + + + 11 + 11 + + + org.apache.maven.plugins From f027b452d654fd5aa0a5b42b7372539681d676ab Mon Sep 17 00:00:00 2001 From: Rich Ellis Date: Tue, 11 Feb 2025 17:02:23 +0000 Subject: [PATCH 07/43] test: extract MockCloudant --- .../features/ChangesFollowerTest.java | 45 +-- .../features/ChangesRequestMockClient.java | 268 ++--------------- .../ChangesResultSpliteratorTest.java | 42 +-- .../cloud/cloudant/features/MockCloudant.java | 273 ++++++++++++++++++ 4 files changed, 337 insertions(+), 291 deletions(-) create mode 100644 modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/MockCloudant.java diff --git a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/ChangesFollowerTest.java b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/ChangesFollowerTest.java index 3a58f71a8..985f3ecc0 100644 --- a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/ChangesFollowerTest.java +++ b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/ChangesFollowerTest.java @@ -23,11 +23,12 @@ import java.util.function.Supplier; import java.util.stream.Stream; import com.ibm.cloud.cloudant.features.ChangesRequestMockClient.LimitExposingException; -import com.ibm.cloud.cloudant.features.ChangesRequestMockClient.MockError; -import com.ibm.cloud.cloudant.features.ChangesRequestMockClient.MockInstruction; import com.ibm.cloud.cloudant.features.ChangesRequestMockClient.PerpetualSupplier; -import com.ibm.cloud.cloudant.features.ChangesRequestMockClient.QueuedSupplier; +import com.ibm.cloud.cloudant.features.MockCloudant.MockError; +import com.ibm.cloud.cloudant.features.MockCloudant.MockInstruction; +import com.ibm.cloud.cloudant.features.MockCloudant.QueuedSupplier; import com.ibm.cloud.cloudant.v1.Cloudant; +import com.ibm.cloud.cloudant.v1.model.ChangesResult; import com.ibm.cloud.cloudant.v1.model.ChangesResultItem; import org.testng.Assert; import org.testng.annotations.DataProvider; @@ -53,21 +54,21 @@ Object[][] errorsAsTestObjectArray(Collection errors) { * @param batches * @return */ - Collection getAlternatingBatchesAndErrors(int batches) { - Collection instructions = new ArrayList<>(batches*2); + Collection> getAlternatingBatchesAndErrors(int batches) { + Collection> instructions = new ArrayList<>(batches*2); Object[] errors = MockError.getTransientErrors().toArray(); - Supplier qs = QueuedSupplier.makeBatchSupplier(batches); + Supplier> qs = ChangesRequestMockClient.makeBatchSupplier(batches); for (int i = 0; i < batches; i++) { // Add a successful batch instructions.add(qs.get()); // Add a transient error - instructions.add(new MockInstruction((MockError) errors[i % errors.length])); + instructions.add(new MockInstruction((MockError) errors[i % errors.length])); } return instructions; } - Supplier getAlternatingBatchErrorSupplier(int batches) { - return new QueuedSupplier(getAlternatingBatchesAndErrors(batches)); + Supplier> getAlternatingBatchErrorSupplier(int batches) { + return new QueuedSupplier(getAlternatingBatchesAndErrors(batches)); } Cloudant getClientWithTimeouts(Duration callTimeout, Duration readTimeout) { @@ -90,7 +91,7 @@ Cloudant getClientWithTimeouts(Duration callTimeout, Duration readTimeout) { * @param batches * @return */ - Supplier getAlternatingBatchErrorThenPerpetualSupplier(int batches) { + Supplier> getAlternatingBatchErrorThenPerpetualSupplier(int batches) { return new PerpetualSupplier(getAlternatingBatchesAndErrors(batches), true); } @@ -265,7 +266,7 @@ long testFollowerOnThread(ChangesFollower testFollower, ChangesFollower.Mode mod @Test void testStartOneOff() { int batches = 6; - Cloudant mockClient = new ChangesRequestMockClient(QueuedSupplier.makeBatchSupplier(batches)); + Cloudant mockClient = new ChangesRequestMockClient(ChangesRequestMockClient.makeBatchSupplier(batches)); ChangesFollower testFollower = new ChangesFollower(mockClient, TestOptions.MINIMUM.getOptions()); Assert.assertEquals(testFollower.startOneOff().count(), batches*ChangesFollower.BATCH_SIZE, "There should be the expected number of changes."); } @@ -275,7 +276,7 @@ void testStartOneOff() { */ @Test(dataProvider = "terminalErrors") void testStartOneOffTerminalErrors(MockError error) { - Cloudant mockClient = new ChangesRequestMockClient(() -> new MockInstruction(error)); + Cloudant mockClient = new ChangesRequestMockClient(() -> new MockInstruction(error)); ChangesFollower testFollower = new ChangesFollower(mockClient, TestOptions.MINIMUM.getOptions()); RuntimeException e = Assert.expectThrows(error.getExceptionClass(), () -> { testFollower.startOneOff().count(); @@ -288,7 +289,7 @@ void testStartOneOffTerminalErrors(MockError error) { */ @Test(dataProvider = "transientErrors") void testStartOneOffTransientErrorsNoSuppression(MockError error) { - Cloudant mockClient = new ChangesRequestMockClient(() -> new MockInstruction(error)); + Cloudant mockClient = new ChangesRequestMockClient(() -> new MockInstruction(error)); ChangesFollower testFollower = new ChangesFollower(mockClient, TestOptions.MINIMUM.getOptions(), Duration.ZERO); RuntimeException e = Assert.expectThrows(error.getExceptionClass(), () -> { testFollower.startOneOff().count(); @@ -303,7 +304,7 @@ void testStartOneOffTransientErrorsNoSuppression(MockError error) { */ @Test(dataProvider = "transientErrors") void testStartOneOffTransientErrorsWithSuppressionDuration(MockError error) { - Cloudant mockClient = new ChangesRequestMockClient(() -> new MockInstruction(error)); + Cloudant mockClient = new ChangesRequestMockClient(() -> new MockInstruction(error)); ChangesFollower testFollower = new ChangesFollower(mockClient, TestOptions.MINIMUM.getOptions(), Duration.ofMillis(100L)); RuntimeException e = Assert.expectThrows(error.getExceptionClass(), () -> { testFollower.startOneOff().count(); @@ -330,7 +331,7 @@ void testStartOneOffTransientErrorsWithSuppressionDurationCompletes() { */ @Test(dataProvider = "transientErrors") void testStartOneOffTransientErrorsMaxSuppressionDoesNotComplete(MockError error) { - Cloudant mockClient = new ChangesRequestMockClient(() -> new MockInstruction(error)); + Cloudant mockClient = new ChangesRequestMockClient(() -> new MockInstruction(error)); ChangesFollower testFollower = new ChangesFollower(mockClient, TestOptions.MINIMUM.getOptions()); try { long count = testFollowerOnThread(testFollower, ChangesFollower.Mode.FINITE, false, Duration.ofMillis(500L)); @@ -380,7 +381,7 @@ void testStart() { */ @Test(dataProvider = "terminalErrors") void testStartTerminalErrors(MockError error) { - Cloudant mockClient = new ChangesRequestMockClient(() -> new MockInstruction(error)); + Cloudant mockClient = new ChangesRequestMockClient(() -> new MockInstruction(error)); ChangesFollower testFollower = new ChangesFollower(mockClient, TestOptions.MINIMUM.getOptions()); RuntimeException e = Assert.expectThrows(error.getExceptionClass(), () -> { testFollowerOnThread(testFollower, ChangesFollower.Mode.LISTEN, true, Duration.ofSeconds(1L)); @@ -393,7 +394,7 @@ void testStartTerminalErrors(MockError error) { */ @Test(dataProvider = "transientErrors") void testStartTransientErrorsNoSuppression(MockError error) { - Cloudant mockClient = new ChangesRequestMockClient(() -> new MockInstruction(error)); + Cloudant mockClient = new ChangesRequestMockClient(() -> new MockInstruction(error)); ChangesFollower testFollower = new ChangesFollower(mockClient, TestOptions.MINIMUM.getOptions(), Duration.ZERO); RuntimeException e = Assert.expectThrows(error.getExceptionClass(), () -> { testFollowerOnThread(testFollower, ChangesFollower.Mode.LISTEN, true, Duration.ofSeconds(1L)); @@ -406,7 +407,7 @@ void testStartTransientErrorsNoSuppression(MockError error) { */ @Test(dataProvider = "transientErrors") void testStartTransientErrorsWithSuppressionDurationErrorTermination(MockError error) { - Cloudant mockClient = new ChangesRequestMockClient(() -> new MockInstruction(error)); + Cloudant mockClient = new ChangesRequestMockClient(() -> new MockInstruction(error)); ChangesFollower testFollower = new ChangesFollower(mockClient, TestOptions.MINIMUM.getOptions(), Duration.ofMillis(100L)); RuntimeException e = Assert.expectThrows(error.getExceptionClass(), () -> { testFollowerOnThread(testFollower, ChangesFollower.Mode.LISTEN, true, Duration.ofSeconds(1L)); @@ -436,7 +437,7 @@ void testStartTransientErrorsWithSuppressionDurationAllChanges() { */ @Test(dataProvider = "transientErrors") void testStartTransientErrorsWithMaxSuppression(MockError error) { - Cloudant mockClient = new ChangesRequestMockClient(() -> new MockInstruction(error)); + Cloudant mockClient = new ChangesRequestMockClient(() -> new MockInstruction(error)); ChangesFollower testFollower = new ChangesFollower(mockClient, TestOptions.MINIMUM.getOptions()); try { long count = testFollowerOnThread(testFollower, ChangesFollower.Mode.LISTEN, false, Duration.ofSeconds(1L)); @@ -539,7 +540,7 @@ void testLimit(ChangesFollower.Mode mode, long limit) { */ @Test void testBatchSize() { - Cloudant mockClient = new ChangesRequestMockClient(QueuedSupplier.makeBatchSupplier(1)); + Cloudant mockClient = new ChangesRequestMockClient(ChangesRequestMockClient.makeBatchSupplier(1)); // Use no error tolerance to ensure we get our special test exception ChangesFollower testFollower = new ChangesFollower(mockClient, TestOptions.MINIMUM.getBuilder().includeDocs(true).build(), Duration.ZERO); LimitExposingException lee = Assert.expectThrows(LimitExposingException.class, () -> { @@ -558,7 +559,7 @@ void testBatchSize() { */ @Test void testBatchSizeMinimum() { - ChangesRequestMockClient mockClient = new ChangesRequestMockClient(QueuedSupplier.makeBatchSupplier(1)); + ChangesRequestMockClient mockClient = new ChangesRequestMockClient(ChangesRequestMockClient.makeBatchSupplier(1)); mockClient.setDatabaseInfoDocCount(1L); mockClient.setDatabaseInfoDocSize(5L * 1024L * 1024L - 1L); // Use no error tolerance to ensure we get our special test exception @@ -580,7 +581,7 @@ void testBatchSizeMinimum() { */ @Test void testBatchSizeLimit() { - Cloudant mockClient = new ChangesRequestMockClient(QueuedSupplier.makeBatchSupplier(1)); + Cloudant mockClient = new ChangesRequestMockClient(ChangesRequestMockClient.makeBatchSupplier(1)); // Use no error tolerance to ensure we get our special test exception ChangesFollower testFollower = new ChangesFollower(mockClient, TestOptions.MINIMUM.getBuilder().limit(1000L).includeDocs(true).build(), Duration.ZERO); diff --git a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/ChangesRequestMockClient.java b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/ChangesRequestMockClient.java index 587793b28..a57de1ce4 100644 --- a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/ChangesRequestMockClient.java +++ b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/ChangesRequestMockClient.java @@ -13,22 +13,16 @@ package com.ibm.cloud.cloudant.features; -import java.io.IOException; -import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.EnumSet; import java.util.List; import java.util.NoSuchElementException; -import java.util.Queue; import java.util.Random; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.LongStream; -import com.google.gson.stream.MalformedJsonException; -import com.ibm.cloud.cloudant.v1.Cloudant; import com.ibm.cloud.cloudant.v1.model.Change; import com.ibm.cloud.cloudant.v1.model.ChangesResult; import com.ibm.cloud.cloudant.v1.model.ChangesResultItem; @@ -39,149 +33,19 @@ import com.ibm.cloud.sdk.core.http.Response; import com.ibm.cloud.sdk.core.http.ServiceCall; import com.ibm.cloud.sdk.core.http.ServiceCallback; -import com.ibm.cloud.sdk.core.security.NoAuthAuthenticator; -import com.ibm.cloud.sdk.core.service.exception.BadRequestException; -import com.ibm.cloud.sdk.core.service.exception.ForbiddenException; -import com.ibm.cloud.sdk.core.service.exception.InternalServerErrorException; -import com.ibm.cloud.sdk.core.service.exception.InvalidServiceResponseException; -import com.ibm.cloud.sdk.core.service.exception.NotFoundException; -import com.ibm.cloud.sdk.core.service.exception.ServiceResponseException; -import com.ibm.cloud.sdk.core.service.exception.ServiceUnavailableException; -import com.ibm.cloud.sdk.core.service.exception.TooManyRequestsException; -import com.ibm.cloud.sdk.core.service.exception.UnauthorizedException; import org.testng.Assert; import io.reactivex.Single; -import okhttp3.MediaType; -import okhttp3.Protocol; -import okhttp3.RequestBody; -import okhttp3.ResponseBody; /** * Mock client that simulates changes responses */ -public class ChangesRequestMockClient extends Cloudant { +public class ChangesRequestMockClient extends MockCloudant { static final Random r = new Random(); - private static final okhttp3.Request MOCK_REQUEST = new okhttp3.Request.Builder() - .url("https://test.example/foo/_changes") - .method("POST", - RequestBody.create("{}", - MediaType.get("application/json"))) - .build(); - private static final okhttp3.Response SUCCESS_RESPONSE = new okhttp3.Response.Builder() - .code(200) - .message("OK") - .protocol(Protocol.HTTP_2) - .request(MOCK_REQUEST) - .build(); - - private final Supplier mocks; - final List> cancelledRequests = Collections.synchronizedList(new ArrayList<>()); + private boolean batchSizeCalculation = false; private Long databaseInfoDocCount = 500_000L; private Long databaseInfoDocSize = 523L; - - /** - * Mock errors - */ - enum MockError { - TERMINAL_400, - TERMINAL_401, - TERMINAL_403, - TERMINAL_404, - TRANSIENT_429, - TRANSIENT_500, - TRANSIENT_502, - TRANSIENT_503, - TRANSIENT_504, - TRANSIENT_BAD_JSON, - TRANSIENT_IO; - - static EnumSet getTerminalErrors() { - return EnumSet.of( - TERMINAL_400, - TERMINAL_401, - TERMINAL_403, - TERMINAL_404); - } - - static EnumSet getTransientErrors() { - return EnumSet.of( - TRANSIENT_429, - TRANSIENT_500, - TRANSIENT_502, - TRANSIENT_503, - TRANSIENT_504, - TRANSIENT_BAD_JSON, - TRANSIENT_IO); - } - - private okhttp3.Response makeResponse(okhttp3.Response.Builder rb, int code, String error) { - rb.code(code) - .protocol(Protocol.HTTP_2) - .request(MOCK_REQUEST); - switch (code) { - case 200: - rb.message("OK"); - // make some invalid JSON - rb.body(ResponseBody.create("{", MediaType.get("application/json"))); - break; - case 502: - rb.message("BAD_GATEWAY"); - rb.body(ResponseBody.create("", MediaType.get("text/plain"))); - break; - case 504: - rb.message("GATEWAY_TIMEOUT"); - rb.body(ResponseBody.create("", MediaType.get("text/plain"))); - break; - default: - rb.message(error); - rb.body(ResponseBody.create( - String.format("{\"error\":\"%s\", \"reason\":\"test error\"}", error), - MediaType.get("application/json"))); - break; - } - return rb.build(); - } - - Class getExceptionClass() { - return getException().getClass(); - } - - RuntimeException getException() { - okhttp3.Response.Builder rb = new okhttp3.Response.Builder(); - switch(this) { - case TERMINAL_400: - return new BadRequestException(makeResponse(rb, 400, "bad_request")); - case TERMINAL_401: - return new UnauthorizedException(makeResponse(rb, 401, "unauthorized")); - case TERMINAL_403: - return new ForbiddenException(makeResponse(rb, 403, "forbidden")); - case TERMINAL_404: - return new NotFoundException(makeResponse(rb, 404, "not_found")); - case TRANSIENT_429: - return new TooManyRequestsException(makeResponse(rb, 429, "too_many_requests")); - case TRANSIENT_500: - return new InternalServerErrorException(makeResponse(rb, 500, "internal_server_error")); - case TRANSIENT_503: - return new ServiceUnavailableException(makeResponse(rb, 503, "service_unavailable")); - case TRANSIENT_502: - return new ServiceResponseException(502, makeResponse(rb, 502, null)); - case TRANSIENT_504: - return new ServiceResponseException(504, makeResponse(rb, 504, null)); - case TRANSIENT_BAD_JSON: - return new InvalidServiceResponseException(makeResponse(rb, 200, null), "Bad request body", new MalformedJsonException("test bad json")); - case TRANSIENT_IO: - return new RuntimeException("Bad IO", new IOException("test bad IO")); - default: - return new RuntimeException("Unimplemented test exception"); - } - } - - void throwException() throws RuntimeException { - throw getException(); - } - } static String generateAlphanumString(final int size) { return r.ints(48 /* i.e. 0 */, 122 + 1 /* i.e. z +1 code point */) @@ -201,9 +65,8 @@ static String generateSeqLikeString(final long gen, final int size) { * Make a mock client that supplies results or exceptions as indicated by the supplier. * @param instructionSupplier */ - ChangesRequestMockClient(Supplier instructionSupplier) { - super(null, new NoAuthAuthenticator()); - this.mocks = instructionSupplier; + ChangesRequestMockClient(Supplier> instructionSupplier) { + super(instructionSupplier); } void setDatabaseInfoDocCount(Long count) { @@ -290,34 +153,6 @@ public void cancel() { }; } - /** - * An instruction to return a result or an error - */ - static class MockInstruction { - - private final MockError e; - private final ChangesResult result; - - MockInstruction(MockError e) { - this.e = e; - this.result = null; - } - - MockInstruction(ChangesResult result) { - this.result = result; - this.e = null; - } - - ChangesResult getResultOrThrow() { - if (result != null) { - return result; - } else { - e.throwException(); - return null; - } - } - } - static final class MockChangesResult extends ChangesResult { static ChangesResult EMPTY_RESULT = new MockChangesResult(Collections.emptyList(), 0L); @@ -358,86 +193,11 @@ static final class MockChange extends Change { } } - final class MockServiceCall implements ServiceCall { - - private final MockInstruction mockI; - private volatile boolean isCancelled = false; - - MockServiceCall(MockInstruction mockI) { - this.mockI = mockI; - } - - @Override - public ServiceCall addHeader(String name, String value) { - throw new UnsupportedOperationException("NOT MOCKED"); - } - - @Override - public Response execute() throws RuntimeException { - if (isCancelled) { - throw new RuntimeException("Execution of MockServiceCall after cancellation."); - } - return new Response<>(mockI.getResultOrThrow(), SUCCESS_RESPONSE); - } - - @Override - public void enqueue(ServiceCallback callback) { - throw new UnsupportedOperationException("NOT MOCKED"); - } - - @Override - public Single> reactiveRequest() { - throw new UnsupportedOperationException("NOT MOCKED"); - } - - @Override - public void cancel() { - isCancelled = true; - cancelledRequests.add(this); - } - } - - /** - * A MockInstruction supplier that uses a queue. - */ - static class QueuedSupplier implements Supplier { - - protected final Queue q; - - QueuedSupplier(Collection instructions) { - q = new ArrayDeque<>(instructions); - } - - @Override - public MockInstruction get() { - try { - return q.remove(); - } catch(NoSuchElementException e) { - Assert.fail("Test error: no mock instruction available for request."); - // The assert throw should take care of the exit, but the compiler - // doesn't seem to notice. - return null; - } - } - - static Supplier makeBatchSupplier(int numberOfBatches) { - long pending = numberOfBatches * ChangesFollower.BATCH_SIZE; - List mocks = new ArrayList<>(); - for (int i=1; i <= numberOfBatches; i++) { - pending -= ChangesFollower.BATCH_SIZE; - mocks.add(new MockInstruction(new MockChangesResult(((i-1) * ChangesFollower.BATCH_SIZE) + 1, pending))); - } - // Add a final empty result (the only result for 0 batch case) - mocks.add(new MockInstruction(MockChangesResult.EMPTY_RESULT)); - return new QueuedSupplier(mocks); - } - } - /** * A MockInstruction supplier that adds a new result instruction for * each one taken, thereby providing results perpetually. */ - static class PerpetualSupplier extends QueuedSupplier { + static class PerpetualSupplier extends QueuedSupplier { final boolean emptyFeed; final AtomicInteger counter = new AtomicInteger(0); @@ -450,7 +210,7 @@ static class PerpetualSupplier extends QueuedSupplier { this(Collections.emptyList(), emptyFeed); } - PerpetualSupplier(Collection initialMocks, boolean emptyFeed) { + PerpetualSupplier(Collection> initialMocks, boolean emptyFeed) { super(initialMocks); this.emptyFeed = emptyFeed; // Add an initial batch if there wasn't one @@ -463,11 +223,11 @@ boolean add() { ChangesResult mockResult = emptyFeed ? MockChangesResult.EMPTY_RESULT : new MockChangesResult((counter.getAndIncrement() * ChangesFollower.BATCH_SIZE) + 1, Long.MAX_VALUE); - return q.add(new MockInstruction(mockResult)); + return q.add(new MockInstruction(mockResult)); } @Override - public MockInstruction get() { + public MockInstruction get() { add(); return super.get(); } @@ -483,4 +243,16 @@ long getRequestLimit() { return this.limit; } } + + static Supplier> makeBatchSupplier(int numberOfBatches) { + long pending = numberOfBatches * ChangesFollower.BATCH_SIZE; + List> mocks = new ArrayList<>(); + for (int i=1; i <= numberOfBatches; i++) { + pending -= ChangesFollower.BATCH_SIZE; + mocks.add(new MockInstruction(new MockChangesResult(((i-1) * ChangesFollower.BATCH_SIZE) + 1, pending))); + } + // Add a final empty result (the only result for 0 batch case) + mocks.add(new MockInstruction(MockChangesResult.EMPTY_RESULT)); + return new QueuedSupplier(mocks); + } } diff --git a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/ChangesResultSpliteratorTest.java b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/ChangesResultSpliteratorTest.java index 2d1f7a24a..b907e6715 100644 --- a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/ChangesResultSpliteratorTest.java +++ b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/ChangesResultSpliteratorTest.java @@ -26,10 +26,10 @@ import java.util.function.Consumer; import java.util.function.Supplier; import com.ibm.cloud.cloudant.features.ChangesRequestMockClient.MockChangesResult; -import com.ibm.cloud.cloudant.features.ChangesRequestMockClient.MockError; -import com.ibm.cloud.cloudant.features.ChangesRequestMockClient.MockInstruction; import com.ibm.cloud.cloudant.features.ChangesRequestMockClient.PerpetualSupplier; -import com.ibm.cloud.cloudant.features.ChangesRequestMockClient.QueuedSupplier; +import com.ibm.cloud.cloudant.features.MockCloudant.MockError; +import com.ibm.cloud.cloudant.features.MockCloudant.MockInstruction; +import com.ibm.cloud.cloudant.features.MockCloudant.QueuedSupplier; import com.ibm.cloud.cloudant.v1.Cloudant; import com.ibm.cloud.cloudant.v1.model.ChangesResult; import com.ibm.cloud.cloudant.v1.model.PostChangesOptions; @@ -66,7 +66,7 @@ enum Action { * @return */ WrappedTestSpliterator makeFixedSizeSpliterator(int batches, ChangesFollower.Mode mode, Duration tolerance) { - return makeSpliterator(QueuedSupplier.makeBatchSupplier(batches), mode, tolerance); + return makeSpliterator(ChangesRequestMockClient.makeBatchSupplier(batches), mode, tolerance); } /** @@ -91,7 +91,7 @@ WrappedTestSpliterator makePerpetualSpliterator(boolean empty, ChangesFollower.M * @param tolerance * @return */ - WrappedTestSpliterator makeSpliterator(Supplier mockSupplier, ChangesFollower.Mode mode, Duration tolerance) { + WrappedTestSpliterator makeSpliterator(Supplier> mockSupplier, ChangesFollower.Mode mode, Duration tolerance) { return new WrappedTestSpliterator( new ChangesRequestMockClient(mockSupplier), DEFAULT_OPTIONS, @@ -134,7 +134,7 @@ Object[][] getModesWithErrors() { int index = 0; for (ChangesFollower.Mode mode : modes) { for (ChangesRequestMockClient.MockError error : errors) { - tests[index] = new Object[]{mode, error, new ChangesRequestMockClient(() -> new MockInstruction(error))}; + tests[index] = new Object[]{mode, error, new ChangesRequestMockClient(() -> new MockInstruction(error))}; index++; } } @@ -169,19 +169,19 @@ Object[][] getSuppressionSequences() { for (ChangesFollower.Mode mode : modes) { for (ChangesRequestMockClient.MockError error : errors) { for(Action[] sequence : sequences) { - List instructions = new ArrayList<>(3); + List> instructions = new ArrayList<>(3); for (Action a: sequence) { switch(a) { case SUCCESS: - instructions.add(new MockInstruction(new MockChangesResult(1,1))); + instructions.add(new MockInstruction(new MockChangesResult(1,1))); break; case SUPPRESS: case THROW: - instructions.add(new MockInstruction(error)); + instructions.add(new MockInstruction(error)); break; } } - tests[index] = new Object[]{sequence, mode, error, new ChangesRequestMockClient(new QueuedSupplier(instructions))}; + tests[index] = new Object[]{sequence, mode, error, new ChangesRequestMockClient(new QueuedSupplier(instructions))}; index++; } } @@ -270,12 +270,12 @@ void testEstimateSize(ChangesFollower.Mode mode) { */ @Test void testEstimateSizePartialBatch() { - List instructions = new ArrayList<>(); + List> instructions = new ArrayList<>(); long partialBatchSize = 1844; - instructions.add(new MockInstruction(new MockChangesResult(1, ChangesFollower.BATCH_SIZE, ChangesFollower.BATCH_SIZE + partialBatchSize))); - instructions.add(new MockInstruction(new MockChangesResult(ChangesFollower.BATCH_SIZE + 1, ChangesFollower.BATCH_SIZE, partialBatchSize))); - instructions.add(new MockInstruction(new MockChangesResult(2*ChangesFollower.BATCH_SIZE + 1, partialBatchSize, 0L))); - ChangesResultSpliterator testSpliterator = makeSpliterator(new QueuedSupplier(instructions), ChangesFollower.Mode.FINITE, Duration.ZERO); + instructions.add(new MockInstruction(new MockChangesResult(1, ChangesFollower.BATCH_SIZE, ChangesFollower.BATCH_SIZE + partialBatchSize))); + instructions.add(new MockInstruction(new MockChangesResult(ChangesFollower.BATCH_SIZE + 1, ChangesFollower.BATCH_SIZE, partialBatchSize))); + instructions.add(new MockInstruction(new MockChangesResult(2*ChangesFollower.BATCH_SIZE + 1, partialBatchSize, 0L))); + ChangesResultSpliterator testSpliterator = makeSpliterator(new QueuedSupplier(instructions), ChangesFollower.Mode.FINITE, Duration.ZERO); // Initial estimate is always Long.MAX_VALUE Assert.assertEquals(testSpliterator.estimateSize(), Long.MAX_VALUE, "The initial estimated size should be Long.MAX_VALUE."); // Advance the spliterator @@ -292,7 +292,7 @@ void testEstimateSizePartialBatch() { void testNext(ChangesFollower.Mode mode) { ChangesResult expectedResult = new MockChangesResult(1, 0); ChangesResultSpliterator testSpliterator = makeSpliterator( - new QueuedSupplier(Collections.singletonList(new MockInstruction(expectedResult))), + new QueuedSupplier(Collections.singletonList(new MockInstruction(expectedResult))), mode, Duration.ZERO); ChangesResult actualResult = testSpliterator.next(); @@ -409,7 +409,7 @@ void testStop(ChangesFollower.Mode mode) throws Exception { @Test(dataProvider = "modes") void testStopDuringSuppression(ChangesFollower.Mode mode) throws Exception { executeTestStop(makeSpliterator( - () -> new MockInstruction(MockError.TRANSIENT_429), + () -> new MockInstruction(MockError.TRANSIENT_429), mode, ChronoUnit.FOREVER.getDuration())); } @@ -423,11 +423,11 @@ void testStopDuringSuppression(ChangesFollower.Mode mode) throws Exception { */ @Test(dataProvider = "modes") void testStopWithCancelledCall(ChangesFollower.Mode mode) throws Exception { - Collection instructions = Collections.singletonList( - new MockInstruction(MockError.TRANSIENT_IO) + Collection> instructions = Collections.singletonList( + new MockInstruction(MockError.TRANSIENT_IO) ); ChangesResultSpliterator testSpliterator = makeSpliterator( - new QueuedSupplier(instructions), + new QueuedSupplier(instructions), mode, Duration.ZERO); testSpliterator.stop(); @@ -573,7 +573,7 @@ void testTrySplit(ChangesFollower.Mode mode) { @Test(dataProvider = "modes") void testRetryDelay(ChangesFollower.Mode mode) { // Make a spliterator with 429 responses - ChangesResultSpliterator testSpliterator = makeSpliterator(() -> new MockInstruction(MockError.TRANSIENT_429), mode, ChronoUnit.FOREVER.getDuration()); + ChangesResultSpliterator testSpliterator = makeSpliterator(() -> new MockInstruction(MockError.TRANSIENT_429), mode, ChronoUnit.FOREVER.getDuration()); // Iterate for at least 300 ms (i.e. minimum 2 delays i.e. 100, 200 but could be more because of jitter) int requestCounter = 0; long startTime = System.currentTimeMillis(); diff --git a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/MockCloudant.java b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/MockCloudant.java new file mode 100644 index 000000000..c559f118e --- /dev/null +++ b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/MockCloudant.java @@ -0,0 +1,273 @@ +/** + * © Copyright IBM Corporation 2022, 2023. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.ibm.cloud.cloudant.features; + +import java.io.IOException; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.EnumSet; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Queue; +import java.util.function.Supplier; +import com.google.gson.stream.MalformedJsonException; +import com.ibm.cloud.cloudant.v1.Cloudant; +import com.ibm.cloud.sdk.core.http.Response; +import com.ibm.cloud.sdk.core.http.ServiceCall; +import com.ibm.cloud.sdk.core.http.ServiceCallback; +import com.ibm.cloud.sdk.core.security.NoAuthAuthenticator; +import com.ibm.cloud.sdk.core.service.exception.BadRequestException; +import com.ibm.cloud.sdk.core.service.exception.ForbiddenException; +import com.ibm.cloud.sdk.core.service.exception.InternalServerErrorException; +import com.ibm.cloud.sdk.core.service.exception.InvalidServiceResponseException; +import com.ibm.cloud.sdk.core.service.exception.NotFoundException; +import com.ibm.cloud.sdk.core.service.exception.ServiceResponseException; +import com.ibm.cloud.sdk.core.service.exception.ServiceUnavailableException; +import com.ibm.cloud.sdk.core.service.exception.TooManyRequestsException; +import com.ibm.cloud.sdk.core.service.exception.UnauthorizedException; +import org.testng.Assert; +import io.reactivex.Single; +import okhttp3.MediaType; +import okhttp3.Protocol; +import okhttp3.RequestBody; +import okhttp3.ResponseBody; + +/** + * Mock client that simulates changes responses + */ +public class MockCloudant extends Cloudant { + + /** + * Make a mock client that supplies results or exceptions as indicated by the supplier. + * @param instructionSupplier + */ + public MockCloudant(Supplier> instructionSupplier) { + super(null, new NoAuthAuthenticator()); + this.mocks = instructionSupplier; + } + + protected static final okhttp3.Request MOCK_REQUEST = new okhttp3.Request.Builder() + .url("https://test.example/foo/bar/baz") + .method("POST", + RequestBody.create("{}", + MediaType.get("application/json"))) + .build(); + protected static final okhttp3.Response SUCCESS_RESPONSE = new okhttp3.Response.Builder() + .code(200) + .message("OK") + .protocol(Protocol.HTTP_2) + .request(MOCK_REQUEST) + .build(); + + protected final Supplier> mocks; + protected final List> cancelledRequests = Collections.synchronizedList(new ArrayList<>()); + + /** + * Mock errors + */ + enum MockError { + TERMINAL_400, + TERMINAL_401, + TERMINAL_403, + TERMINAL_404, + TRANSIENT_429, + TRANSIENT_500, + TRANSIENT_502, + TRANSIENT_503, + TRANSIENT_504, + TRANSIENT_BAD_JSON, + TRANSIENT_IO; + + static EnumSet getTerminalErrors() { + return EnumSet.of( + TERMINAL_400, + TERMINAL_401, + TERMINAL_403, + TERMINAL_404); + } + + static EnumSet getTransientErrors() { + return EnumSet.of( + TRANSIENT_429, + TRANSIENT_500, + TRANSIENT_502, + TRANSIENT_503, + TRANSIENT_504, + TRANSIENT_BAD_JSON, + TRANSIENT_IO); + } + + private okhttp3.Response makeResponse(okhttp3.Response.Builder rb, int code, String error) { + rb.code(code) + .protocol(Protocol.HTTP_2) + .request(MOCK_REQUEST); + switch (code) { + case 200: + rb.message("OK"); + // make some invalid JSON + rb.body(ResponseBody.create("{", MediaType.get("application/json"))); + break; + case 502: + rb.message("BAD_GATEWAY"); + rb.body(ResponseBody.create("", MediaType.get("text/plain"))); + break; + case 504: + rb.message("GATEWAY_TIMEOUT"); + rb.body(ResponseBody.create("", MediaType.get("text/plain"))); + break; + default: + rb.message(error); + rb.body(ResponseBody.create( + String.format("{\"error\":\"%s\", \"reason\":\"test error\"}", error), + MediaType.get("application/json"))); + break; + } + return rb.build(); + } + + Class getExceptionClass() { + return getException().getClass(); + } + + RuntimeException getException() { + okhttp3.Response.Builder rb = new okhttp3.Response.Builder(); + switch(this) { + case TERMINAL_400: + return new BadRequestException(makeResponse(rb, 400, "bad_request")); + case TERMINAL_401: + return new UnauthorizedException(makeResponse(rb, 401, "unauthorized")); + case TERMINAL_403: + return new ForbiddenException(makeResponse(rb, 403, "forbidden")); + case TERMINAL_404: + return new NotFoundException(makeResponse(rb, 404, "not_found")); + case TRANSIENT_429: + return new TooManyRequestsException(makeResponse(rb, 429, "too_many_requests")); + case TRANSIENT_500: + return new InternalServerErrorException(makeResponse(rb, 500, "internal_server_error")); + case TRANSIENT_503: + return new ServiceUnavailableException(makeResponse(rb, 503, "service_unavailable")); + case TRANSIENT_502: + return new ServiceResponseException(502, makeResponse(rb, 502, null)); + case TRANSIENT_504: + return new ServiceResponseException(504, makeResponse(rb, 504, null)); + case TRANSIENT_BAD_JSON: + return new InvalidServiceResponseException(makeResponse(rb, 200, null), "Bad request body", new MalformedJsonException("test bad json")); + case TRANSIENT_IO: + return new RuntimeException("Bad IO", new IOException("test bad IO")); + default: + return new RuntimeException("Unimplemented test exception"); + } + } + + void throwException() throws RuntimeException { + throw getException(); + } + } + + /** + * An instruction to return a result or an error + */ + public static class MockInstruction { + + private final MockError e; + private final R result; + + public MockInstruction(MockError e) { + this.e = e; + this.result = null; + } + + public MockInstruction(R result) { + this.result = result; + this.e = null; + } + + R getResultOrThrow() { + if (result != null) { + return result; + } else { + e.throwException(); + return null; + } + } + } + + protected final class MockServiceCall implements ServiceCall { + + private final MockInstruction mockI; + private volatile boolean isCancelled = false; + + public MockServiceCall(MockInstruction mockI) { + this.mockI = mockI; + } + + @Override + public ServiceCall addHeader(String name, String value) { + throw new UnsupportedOperationException("NOT MOCKED"); + } + + @Override + public Response execute() throws RuntimeException { + if (isCancelled) { + throw new RuntimeException("Execution of MockServiceCall after cancellation."); + } + return new Response(mockI.getResultOrThrow(), SUCCESS_RESPONSE); + } + + @Override + public void enqueue(ServiceCallback callback) { + throw new UnsupportedOperationException("NOT MOCKED"); + } + + @Override + public Single> reactiveRequest() { + throw new UnsupportedOperationException("NOT MOCKED"); + } + + @Override + public void cancel() { + isCancelled = true; + cancelledRequests.add(this); + } + } + + /** + * A MockInstruction supplier that uses a queue. + */ + public static class QueuedSupplier implements Supplier> { + + protected final Collection> instructions; + protected final Queue> q; + + protected QueuedSupplier(Collection> instructions) { + this.instructions = instructions; + this.q = new ArrayDeque<>(instructions); + } + + @Override + public MockInstruction get() { + try { + return q.remove(); + } catch(NoSuchElementException e) { + Assert.fail("Test error: no mock instruction available for request."); + // The assert throw should take care of the exit, but the compiler + // doesn't seem to notice. + return null; + } + } + } + +} From 103273a0197c70b2e23eb4a11628663dcf9ecaf6 Mon Sep 17 00:00:00 2001 From: Rich Ellis Date: Wed, 12 Feb 2025 09:37:29 +0000 Subject: [PATCH 08/43] test: BasePager tests --- .../features/pagination/BasePagerTest.java | 367 ++++++++++++++++++ 1 file changed, 367 insertions(+) create mode 100644 modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BasePagerTest.java diff --git a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BasePagerTest.java b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BasePagerTest.java new file mode 100644 index 000000000..eb9dbd26e --- /dev/null +++ b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BasePagerTest.java @@ -0,0 +1,367 @@ +package com.ibm.cloud.cloudant.features.pagination; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Spliterator; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import org.testng.Assert; +import org.testng.annotations.Test; +import com.ibm.cloud.cloudant.features.MockCloudant; +import com.ibm.cloud.cloudant.features.MockCloudant.MockInstruction; +import com.ibm.cloud.cloudant.features.MockCloudant.QueuedSupplier; +import com.ibm.cloud.cloudant.v1.Cloudant; +import com.ibm.cloud.cloudant.v1.model.PostViewOptions; +import com.ibm.cloud.cloudant.v1.model.PostViewOptions.Builder; +import com.ibm.cloud.sdk.core.http.ServiceCall; + +public class BasePagerTest { + + private Cloudant mockClient = new MockPagerClient(null); + + static class PageSupplier extends QueuedSupplier { + + final List allItems; + final List> pages; + + private PageSupplier(List> pages) { + super(pages.stream().map(TestResult::new).map(MockInstruction::new).collect(Collectors.toList())); + this.pages = pages; + this.allItems = this.pages.stream().flatMap(List::stream).collect(Collectors.toList()); + } + + static PageSupplier makePageSupplier(int total, int pageSize) { + final List> pages = new ArrayList<>(); + List page = new ArrayList<>(); + for (int i = 0; i < total; i++) { + page.add(i); + if (i % pageSize == pageSize - 1) { + pages.add(page); + page = new ArrayList<>(); + } + } + // Add the final page, empty or otherwise + pages.add(page); + return new PageSupplier(pages); + } + } + + class MockPagerClient extends MockCloudant { + + MockPagerClient(Supplier> instructionSupplier) { + super(instructionSupplier); + } + + ServiceCall testCall() { + return new MockServiceCall(mocks.get()); + } + + } + + static class TestResult { + private List rows; + TestResult(List rows) { + this.rows = rows; + } + + List getRows() { + return this.rows; + } + } + + /** + * This test sub-class of BasePager implicitly tests that various abstract methods are correctly + * called by non-abstract methods in the BasePager. + */ + class TestPager extends BasePager { + + protected TestPager(Cloudant client, PostViewOptions options) { + super(client, options); + } + + Cloudant getClient() { + return this.client; + } + + /** + * Implicitly tests that limit gets applied per page, otherwise the default would + * be used and page counts would be wrong. + */ + @Override + protected Function optionsToBuilderFunction() { + return o -> { + return BasePagerTest.getRequiredTestOptionsBuilder(); + }; + } + + @Override + protected Function builderToOptionsFunction() { + return Builder::build; + } + + @Override + protected Function> itemsGetter() { + return TestResult::getRows; + } + + /** + * These tests don't actually use the options, but we set a key + * so we can validate calls to setNextPageOptions. + */ + @Override + protected void setNextPageOptions(Builder builder, TestResult result) { + List rows = result.getRows(); + if (rows.isEmpty()) { + throw new IllegalStateException("Test failure: tried to setNextPageOptions on empty page."); + } else { + Integer i = rows.get(rows.size() - 1); + builder.key(i); + } + } + + /** + * Delegates to our next mock. + * If the BasePager didn't correctly call this the mocks wouldn't work. + */ + @Override + protected BiFunction> nextRequestFunction() { + return (c, o) -> { + return ((MockPagerClient) c).testCall(); + }; + } + + @Override + protected Function limitGetter() { + return PostViewOptions::limit; + } + + } + + private PostViewOptions getDefaultTestOptions(int limit) { + return getRequiredTestOptionsBuilder() + .limit(limit) + .build(); + } + + private static Builder getRequiredTestOptionsBuilder() { + return new Builder() + .db("example-database") + .ddoc("test-ddoc") + .view("test-view"); + } + + // test constructor + @Test + void testConstructor() { + TestPager pager = new TestPager(mockClient, getDefaultTestOptions(42)); + // Assert the client + Assert.assertEquals(pager.getClient(), mockClient, "The client should be the supplied one."); + } + + // test page size limit + @Test + void testDefaultPageSize() { + TestPager pager = new TestPager(mockClient, getRequiredTestOptionsBuilder().build()); + // Assert the default page size + Assert.assertEquals(pager.pageSize, 200, "The page size should be the default."); + } + + // test page size default + @Test + void testLimitPageSize() { + TestPager pager = new TestPager(mockClient, getDefaultTestOptions(42)); + // Assert the limit provided as page size + Assert.assertEquals(pager.pageSize, 42, "The page size should match the limit."); + } + + // test hasNext + @Test + void testHasNextIsTrueInitially() { + TestPager pager = new TestPager(mockClient, getDefaultTestOptions(42)); + Assert.assertEquals(pager.hasNext(), true, "hasNext() should initially return true."); + } + + @Test + void testHasNextIsTrueForResultEqualToLimit() { + // Mock a one element page + MockPagerClient c = new MockPagerClient(() -> { + return new MockInstruction(new TestResult(Collections.singletonList(1))); + }); + TestPager pager = new TestPager(c, getDefaultTestOptions(1)); + // Get the first page (size 1) + pager.getNext(); + // hasNext should return true because results size is the same as limit + Assert.assertEquals(pager.hasNext(), true, "hasNext() should return true."); + } + + @Test + void testHasNextIsFalseForResultLessThanLimit() { + // Mock a zero size page + MockPagerClient c = new MockPagerClient(() -> { + return new MockInstruction(new TestResult(Collections.emptyList())); + }); + TestPager pager = new TestPager(c, getDefaultTestOptions(1)); + // Get the first page (size 0) + pager.getNext(); + // hasNext should return false because results size smaller than limit + Assert.assertEquals(pager.hasNext(), false, "hasNext() should return false."); + } + + // test getNext + @Test + void testGetNextFirstPage() { + int pageSize = 25; + PageSupplier pageSupplier = PageSupplier.makePageSupplier(pageSize, pageSize); + MockPagerClient c = new MockPagerClient(pageSupplier); + TestPager pager = new TestPager(c, getDefaultTestOptions(pageSize)); + List actualPage = pager.getNext(); + // Assert first page + Assert.assertEquals(actualPage, pageSupplier.pages.get(0), "The actual page should match the expected page."); + } + + @Test + void testGetNextTwoPages() { + int pageSize = 3; + PageSupplier pageSupplier = PageSupplier.makePageSupplier(2*pageSize, pageSize); + MockPagerClient c = new MockPagerClient(pageSupplier); + TestPager pager = new TestPager(c, getDefaultTestOptions(pageSize)); + Assert.assertEquals(pager.hasNext(), true, "hasNext() should return true."); + List actualPage1 = pager.getNext(); + // Assert pages + Assert.assertEquals(actualPage1, pageSupplier.pages.get(0), "The actual page should match the expected page."); + Assert.assertEquals(pager.hasNext(), true, "hasNext() should return true."); + List actualPage2 = pager.getNext(); + Assert.assertEquals(actualPage2, pageSupplier.pages.get(1), "The actual page should match the expected page."); + Assert.assertEquals(pager.hasNext(), true, "hasNext() should return true."); + } + + @Test + void testGetNextUntilEmpty() { + int pageSize = 3; + PageSupplier pageSupplier = PageSupplier.makePageSupplier(10, pageSize); + MockPagerClient c = new MockPagerClient(pageSupplier); + TestPager pager = new TestPager(c, getDefaultTestOptions(pageSize)); + List actualItems = new ArrayList<>(); + int pageCount = 0; + while(pager.hasNext()) { + pageCount++; + List page = pager.getNext(); + actualItems.addAll(page); + // Assert each page is the same or smaller than the limit + // to make sure we aren't getting all the results in a single page + Assert.assertTrue(page.size() <= pageSize, "The page should be smaller or equal to the page size."); + } + Assert.assertEquals(actualItems, pageSupplier.allItems, "The results should match all the pages."); + Assert.assertEquals(pageCount, pageSupplier.pages.size(), "There should have been the correct number of pages."); + } + + @Test + void testGetNextException() { + int pageSize = 2; + PageSupplier pageSupplier = PageSupplier.makePageSupplier(pageSize - 1, pageSize); + MockPagerClient c = new MockPagerClient(pageSupplier); + TestPager pager = new TestPager(c, getDefaultTestOptions(pageSize)); + List actualPage = pager.getNext(); + // Assert first page + Assert.assertEquals(actualPage, pageSupplier.pages.get(0), "The actual page should match the expected page."); + // hasNext false + Assert.assertEquals(pager.hasNext(), false, "hasNext() should return false."); + // getNext throws + Assert.assertThrows(NoSuchElementException.class, () -> { + pager.getNext(); + }); + } + + // test getAll + @Test + void testGetAll() { + int pageSize = 11; + PageSupplier pageSupplier = PageSupplier.makePageSupplier(71, pageSize); + MockPagerClient c = new MockPagerClient(pageSupplier); + TestPager pager = new TestPager(c, getDefaultTestOptions(pageSize)); + List actualItems = pager.getAll(); + Assert.assertEquals(actualItems, pageSupplier.allItems, "The results should match all the pages."); + } + + // test next + @Test + void testNextFirstPage() { + int pageSize = 7; + PageSupplier pageSupplier = PageSupplier.makePageSupplier(2*pageSize, pageSize); + MockPagerClient c = new MockPagerClient(pageSupplier); + TestPager pager = new TestPager(c, getDefaultTestOptions(pageSize)); + List actualPage = pager.next(); + // Assert first page + Assert.assertEquals(actualPage, pageSupplier.pages.get(0), "The actual page should match the expected page."); + } + + // test iterator + @Test + void testIterator() { + int pageSize = 23; + PageSupplier pageSupplier = PageSupplier.makePageSupplier(3*pageSize - 1, pageSize); + MockPagerClient c = new MockPagerClient(pageSupplier); + TestPager pager = new TestPager(c, getDefaultTestOptions(pageSize)); + Assert.assertNotNull(pager.iterator(), "The iterator should not be null."); + // Check pager is Iterable + Iterator> expectedPages = pageSupplier.pages.iterator(); + for (List page : pager) { + Assert.assertEquals(page, expectedPages.next()); + } + } + + // test spliterator + @Test + void testSpliterator() { + int pageSize = 23; + PageSupplier pageSupplier = PageSupplier.makePageSupplier(3*pageSize - 1, pageSize); + MockPagerClient c = new MockPagerClient(pageSupplier); + TestPager pager = new TestPager(c, getDefaultTestOptions(pageSize)); + Spliterator> s = pager.spliterator(); + Assert.assertNotNull(s, "The spliterator should not be null."); + Assert.assertTrue(s.hasCharacteristics(Spliterator.ORDERED), "The spliterator should be ordered."); + Assert.assertTrue(s.hasCharacteristics(Spliterator.NONNULL), "The spliterator should be nonnull."); + Assert.assertTrue(s.hasCharacteristics(Spliterator.IMMUTABLE), "The spliterator should be immutable."); + } + + @Test + void testPagesAreImmutable() { + int pageSize = 1; + PageSupplier pageSupplier = PageSupplier.makePageSupplier(pageSize, pageSize); + MockPagerClient c = new MockPagerClient(pageSupplier); + TestPager pager = new TestPager(c, getDefaultTestOptions(pageSize)); + List actualPage = pager.getNext(); + // Assert immutable + Assert.assertThrows( UnsupportedOperationException.class, () -> { + actualPage.add(17); + }); + } + + //test setNextPageOptions + @Test + void testSetNextPageOptions() { + int pageSize = 1; + PageSupplier pageSupplier = PageSupplier.makePageSupplier(5*pageSize, pageSize); + MockPagerClient c = new MockPagerClient(pageSupplier); + TestPager pager = new TestPager(c, getDefaultTestOptions(pageSize)); + Assert.assertNull(pager.nextPageOptions.key(), "key should initially be null."); + // Since we use a page size of 1, each next page options key, is the same as the element from the page + int page = 0; + while(pager.hasNext()) { + pager.getNext(); + if (pager.hasNext()) { + Assert.assertEquals(pager.nextPageOptions.key(), page, "The key should increment per page."); + } else { + // Options don't change for last page + Assert.assertEquals(pager.nextPageOptions.key(), page - 1, "The options should not be set for the final page."); + } + page++; + } + } + +} From 543f5640b1b3c87d0ffc18beed4bde6ff1b8966f Mon Sep 17 00:00:00 2001 From: Rich Ellis Date: Wed, 26 Feb 2025 15:10:06 +0000 Subject: [PATCH 09/43] fix: update default page size to 20 --- .../com/ibm/cloud/cloudant/features/pagination/BasePager.java | 2 +- .../ibm/cloud/cloudant/features/pagination/BasePagerTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BasePager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BasePager.java index ee052261d..7e045238f 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BasePager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BasePager.java @@ -99,7 +99,7 @@ private void buildAndSetOptions(B optionsBuilder) { protected abstract Function limitGetter(); protected Long getPageSizeFromOptionsLimit(O opts) { - return Optional.ofNullable(limitGetter().apply(opts)).orElse(200L); + return Optional.ofNullable(limitGetter().apply(opts)).orElse(20L); } @Override diff --git a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BasePagerTest.java b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BasePagerTest.java index eb9dbd26e..15918b8a4 100644 --- a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BasePagerTest.java +++ b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BasePagerTest.java @@ -168,7 +168,7 @@ void testConstructor() { void testDefaultPageSize() { TestPager pager = new TestPager(mockClient, getRequiredTestOptionsBuilder().build()); // Assert the default page size - Assert.assertEquals(pager.pageSize, 200, "The page size should be the default."); + Assert.assertEquals(pager.pageSize, 20, "The page size should be the default."); } // test page size default From ad3323f3e2fd2ca918de294d855c4c277aad676a Mon Sep 17 00:00:00 2001 From: Rich Ellis Date: Wed, 26 Feb 2025 15:13:18 +0000 Subject: [PATCH 10/43] test: add testGetNextUntilSmaller case Also fix testGetNextUntilEmpty to use an empty page and improve assertion messages. --- .../features/pagination/BasePagerTest.java | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BasePagerTest.java b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BasePagerTest.java index 15918b8a4..3db9c12a6 100644 --- a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BasePagerTest.java +++ b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BasePagerTest.java @@ -242,6 +242,26 @@ void testGetNextTwoPages() { @Test void testGetNextUntilEmpty() { + int pageSize = 3; + PageSupplier pageSupplier = PageSupplier.makePageSupplier(3*pageSize, pageSize); + MockPagerClient c = new MockPagerClient(pageSupplier); + TestPager pager = new TestPager(c, getDefaultTestOptions(pageSize)); + List actualItems = new ArrayList<>(); + int pageCount = 0; + while(pager.hasNext()) { + pageCount++; + List page = pager.getNext(); + actualItems.addAll(page); + // Assert each page is the same or smaller than the limit + // to make sure we aren't getting all the results in a single page + Assert.assertTrue(page.size() <= pageSize, "The actual page size should be smaller or equal to the expected page size."); + } + Assert.assertEquals(actualItems, pageSupplier.allItems, "The results should match all the pages."); + Assert.assertEquals(pageCount, pageSupplier.pages.size(), "There should have been the correct number of pages."); + } + + @Test + void testGetNextUntilSmaller() { int pageSize = 3; PageSupplier pageSupplier = PageSupplier.makePageSupplier(10, pageSize); MockPagerClient c = new MockPagerClient(pageSupplier); @@ -254,7 +274,7 @@ void testGetNextUntilEmpty() { actualItems.addAll(page); // Assert each page is the same or smaller than the limit // to make sure we aren't getting all the results in a single page - Assert.assertTrue(page.size() <= pageSize, "The page should be smaller or equal to the page size."); + Assert.assertTrue(page.size() <= pageSize, "The actual page size should be smaller or equal to the expected page size."); } Assert.assertEquals(actualItems, pageSupplier.allItems, "The results should match all the pages."); Assert.assertEquals(pageCount, pageSupplier.pages.size(), "There should have been the correct number of pages."); From 1141b46b8888cdefc026d447b0f12b0b846dd8a6 Mon Sep 17 00:00:00 2001 From: Rich Ellis Date: Wed, 5 Mar 2025 16:04:50 +0000 Subject: [PATCH 11/43] test: move test helpers for re-use --- .../features/pagination/BasePagerTest.java | 108 ++++-------- .../pagination/PaginationTestHelpers.java | 164 ++++++++++++++++++ 2 files changed, 194 insertions(+), 78 deletions(-) create mode 100644 modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/PaginationTestHelpers.java diff --git a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BasePagerTest.java b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BasePagerTest.java index 3db9c12a6..b8a6ef606 100644 --- a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BasePagerTest.java +++ b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BasePagerTest.java @@ -1,3 +1,16 @@ +/** + * © Copyright IBM Corporation 2025. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + package com.ibm.cloud.cloudant.features.pagination; import java.util.ArrayList; @@ -8,72 +21,24 @@ import java.util.Spliterator; import java.util.function.BiFunction; import java.util.function.Function; -import java.util.function.Supplier; -import java.util.stream.Collectors; import org.testng.Assert; import org.testng.annotations.Test; -import com.ibm.cloud.cloudant.features.MockCloudant; import com.ibm.cloud.cloudant.features.MockCloudant.MockInstruction; -import com.ibm.cloud.cloudant.features.MockCloudant.QueuedSupplier; +import com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.MockPagerClient; +import com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.PageSupplier; +import com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.TestResult; import com.ibm.cloud.cloudant.v1.Cloudant; import com.ibm.cloud.cloudant.v1.model.PostViewOptions; import com.ibm.cloud.cloudant.v1.model.PostViewOptions.Builder; import com.ibm.cloud.sdk.core.http.ServiceCall; +import static com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.getDefaultTestOptions; +import static com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.getRequiredTestOptionsBuilder; + public class BasePagerTest { private Cloudant mockClient = new MockPagerClient(null); - static class PageSupplier extends QueuedSupplier { - - final List allItems; - final List> pages; - - private PageSupplier(List> pages) { - super(pages.stream().map(TestResult::new).map(MockInstruction::new).collect(Collectors.toList())); - this.pages = pages; - this.allItems = this.pages.stream().flatMap(List::stream).collect(Collectors.toList()); - } - - static PageSupplier makePageSupplier(int total, int pageSize) { - final List> pages = new ArrayList<>(); - List page = new ArrayList<>(); - for (int i = 0; i < total; i++) { - page.add(i); - if (i % pageSize == pageSize - 1) { - pages.add(page); - page = new ArrayList<>(); - } - } - // Add the final page, empty or otherwise - pages.add(page); - return new PageSupplier(pages); - } - } - - class MockPagerClient extends MockCloudant { - - MockPagerClient(Supplier> instructionSupplier) { - super(instructionSupplier); - } - - ServiceCall testCall() { - return new MockServiceCall(mocks.get()); - } - - } - - static class TestResult { - private List rows; - TestResult(List rows) { - this.rows = rows; - } - - List getRows() { - return this.rows; - } - } - /** * This test sub-class of BasePager implicitly tests that various abstract methods are correctly * called by non-abstract methods in the BasePager. @@ -142,19 +107,6 @@ protected Function limitGetter() { } - private PostViewOptions getDefaultTestOptions(int limit) { - return getRequiredTestOptionsBuilder() - .limit(limit) - .build(); - } - - private static Builder getRequiredTestOptionsBuilder() { - return new Builder() - .db("example-database") - .ddoc("test-ddoc") - .view("test-view"); - } - // test constructor @Test void testConstructor() { @@ -216,7 +168,7 @@ void testHasNextIsFalseForResultLessThanLimit() { @Test void testGetNextFirstPage() { int pageSize = 25; - PageSupplier pageSupplier = PageSupplier.makePageSupplier(pageSize, pageSize); + PageSupplier pageSupplier = PaginationTestHelpers.newBasePageSupplier(pageSize, pageSize); MockPagerClient c = new MockPagerClient(pageSupplier); TestPager pager = new TestPager(c, getDefaultTestOptions(pageSize)); List actualPage = pager.getNext(); @@ -227,7 +179,7 @@ void testGetNextFirstPage() { @Test void testGetNextTwoPages() { int pageSize = 3; - PageSupplier pageSupplier = PageSupplier.makePageSupplier(2*pageSize, pageSize); + PageSupplier pageSupplier = PaginationTestHelpers.newBasePageSupplier(2*pageSize, pageSize); MockPagerClient c = new MockPagerClient(pageSupplier); TestPager pager = new TestPager(c, getDefaultTestOptions(pageSize)); Assert.assertEquals(pager.hasNext(), true, "hasNext() should return true."); @@ -243,7 +195,7 @@ void testGetNextTwoPages() { @Test void testGetNextUntilEmpty() { int pageSize = 3; - PageSupplier pageSupplier = PageSupplier.makePageSupplier(3*pageSize, pageSize); + PageSupplier pageSupplier = PaginationTestHelpers.newBasePageSupplier(3*pageSize, pageSize); MockPagerClient c = new MockPagerClient(pageSupplier); TestPager pager = new TestPager(c, getDefaultTestOptions(pageSize)); List actualItems = new ArrayList<>(); @@ -263,7 +215,7 @@ void testGetNextUntilEmpty() { @Test void testGetNextUntilSmaller() { int pageSize = 3; - PageSupplier pageSupplier = PageSupplier.makePageSupplier(10, pageSize); + PageSupplier pageSupplier = PaginationTestHelpers.newBasePageSupplier(10, pageSize); MockPagerClient c = new MockPagerClient(pageSupplier); TestPager pager = new TestPager(c, getDefaultTestOptions(pageSize)); List actualItems = new ArrayList<>(); @@ -283,7 +235,7 @@ void testGetNextUntilSmaller() { @Test void testGetNextException() { int pageSize = 2; - PageSupplier pageSupplier = PageSupplier.makePageSupplier(pageSize - 1, pageSize); + PageSupplier pageSupplier = PaginationTestHelpers.newBasePageSupplier(pageSize - 1, pageSize); MockPagerClient c = new MockPagerClient(pageSupplier); TestPager pager = new TestPager(c, getDefaultTestOptions(pageSize)); List actualPage = pager.getNext(); @@ -301,7 +253,7 @@ void testGetNextException() { @Test void testGetAll() { int pageSize = 11; - PageSupplier pageSupplier = PageSupplier.makePageSupplier(71, pageSize); + PageSupplier pageSupplier = PaginationTestHelpers.newBasePageSupplier(71, pageSize); MockPagerClient c = new MockPagerClient(pageSupplier); TestPager pager = new TestPager(c, getDefaultTestOptions(pageSize)); List actualItems = pager.getAll(); @@ -312,7 +264,7 @@ void testGetAll() { @Test void testNextFirstPage() { int pageSize = 7; - PageSupplier pageSupplier = PageSupplier.makePageSupplier(2*pageSize, pageSize); + PageSupplier pageSupplier = PaginationTestHelpers.newBasePageSupplier(2*pageSize, pageSize); MockPagerClient c = new MockPagerClient(pageSupplier); TestPager pager = new TestPager(c, getDefaultTestOptions(pageSize)); List actualPage = pager.next(); @@ -324,7 +276,7 @@ void testNextFirstPage() { @Test void testIterator() { int pageSize = 23; - PageSupplier pageSupplier = PageSupplier.makePageSupplier(3*pageSize - 1, pageSize); + PageSupplier pageSupplier = PaginationTestHelpers.newBasePageSupplier(3*pageSize - 1, pageSize); MockPagerClient c = new MockPagerClient(pageSupplier); TestPager pager = new TestPager(c, getDefaultTestOptions(pageSize)); Assert.assertNotNull(pager.iterator(), "The iterator should not be null."); @@ -339,7 +291,7 @@ void testIterator() { @Test void testSpliterator() { int pageSize = 23; - PageSupplier pageSupplier = PageSupplier.makePageSupplier(3*pageSize - 1, pageSize); + PageSupplier pageSupplier = PaginationTestHelpers.newBasePageSupplier(3*pageSize - 1, pageSize); MockPagerClient c = new MockPagerClient(pageSupplier); TestPager pager = new TestPager(c, getDefaultTestOptions(pageSize)); Spliterator> s = pager.spliterator(); @@ -352,7 +304,7 @@ void testSpliterator() { @Test void testPagesAreImmutable() { int pageSize = 1; - PageSupplier pageSupplier = PageSupplier.makePageSupplier(pageSize, pageSize); + PageSupplier pageSupplier = PaginationTestHelpers.newBasePageSupplier(pageSize, pageSize); MockPagerClient c = new MockPagerClient(pageSupplier); TestPager pager = new TestPager(c, getDefaultTestOptions(pageSize)); List actualPage = pager.getNext(); @@ -366,7 +318,7 @@ void testPagesAreImmutable() { @Test void testSetNextPageOptions() { int pageSize = 1; - PageSupplier pageSupplier = PageSupplier.makePageSupplier(5*pageSize, pageSize); + PageSupplier pageSupplier = PaginationTestHelpers.newBasePageSupplier(5*pageSize, pageSize); MockPagerClient c = new MockPagerClient(pageSupplier); TestPager pager = new TestPager(c, getDefaultTestOptions(pageSize)); Assert.assertNull(pager.nextPageOptions.key(), "key should initially be null."); diff --git a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/PaginationTestHelpers.java b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/PaginationTestHelpers.java new file mode 100644 index 000000000..54676f51b --- /dev/null +++ b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/PaginationTestHelpers.java @@ -0,0 +1,164 @@ +/** + * © Copyright IBM Corporation 2025. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.ibm.cloud.cloudant.features.pagination; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import com.ibm.cloud.cloudant.features.MockCloudant; +import com.ibm.cloud.cloudant.features.MockCloudant.MockInstruction; +import com.ibm.cloud.cloudant.features.MockCloudant.QueuedSupplier; +import com.ibm.cloud.cloudant.v1.model.PostViewOptions; +import com.ibm.cloud.cloudant.v1.model.PostViewOptions.Builder; +import com.ibm.cloud.cloudant.v1.model.ViewResult; +import com.ibm.cloud.cloudant.v1.model.ViewResultRow; +import com.ibm.cloud.sdk.core.http.ServiceCall; + +public class PaginationTestHelpers { + + static class MockPagerClient extends MockCloudant { + + MockPagerClient(Supplier> instructionSupplier) { + super(instructionSupplier); + } + + ServiceCall testCall() { + return new MockServiceCall(mocks.get()); + } + + } + + static class TestResult { + private List rows; + TestResult(List rows) { + this.rows = rows; + } + + List getRows() { + return this.rows; + } + } + + private static class PageSupplierFactory { + + Function, R> itemsToPageResultFn; + Function integerRowWrapFn; + + PageSupplierFactory(Function, R> itemsToPageResultFn, Function integerRowWrapFn) { + this.itemsToPageResultFn = itemsToPageResultFn; + this.integerRowWrapFn = integerRowWrapFn; + } + + private List> getPages(int total, int pageSize) { + final List> pages = new ArrayList<>(); + List page = new ArrayList<>(); + for (int i = 0; i < total; i++) { + page.add(this.integerRowWrapFn.apply(i)); + if (i % pageSize == pageSize - 1) { + pages.add(page); + page = new ArrayList<>(); + } + } + // Add the final page, empty or otherwise + pages.add(page); + return pages; + } + + PageSupplier newPageSupplier(int total, int pageSize) { + return new PageSupplier(this.itemsToPageResultFn, getPages(total, pageSize)); + } + + PageSupplier newExtraRowPageSupplier(int total, int pageSize) { + List> pages = getPages(total, pageSize); + List> responsePages = repageForKeyBased(pages); + return new PageSupplier(this.itemsToPageResultFn, pages, responsePages); + } + + private List> repageForKeyBased(List> pages) { + List> responsePages = new ArrayList<>(pages.size()); + int index = 0; + for (List page : pages) { + List responsePage = new ArrayList<>(page); + // Add the first element from the next page as n + 1 + try { + List nextPage = pages.get(index + 1); + responsePage.add(nextPage.get(0)); + } catch(IndexOutOfBoundsException e) { + // Suppress exception if pages/elements exhuasted + } + responsePages.add(responsePage); + index++; + } + return responsePages; + } + + } + + static class PageSupplier extends QueuedSupplier { + + final List allItems; + final List> pages; + + private PageSupplier(Function, R> itemsToPageFn, List> pages) { + this(itemsToPageFn, pages, pages); + } + + private PageSupplier(Function, R> itemsToPageFn, List> pages, List> responsePages) { + super(responsePages.stream().map(itemsToPageFn).map(MockInstruction::new).collect(Collectors.toList())); + this.pages = pages; + this.allItems = this.pages.stream().flatMap(List::stream).collect(Collectors.toList()); + } + } + + private static class TestViewResultRow extends ViewResultRow { + private TestViewResultRow(Integer i) { + this.id = "testdoc" + String.valueOf(i); + this.key = i; + } + } + + private static class TestViewResult extends ViewResult { + private TestViewResult(List rows) { + this.rows = rows; + } + } + + static PageSupplier newBasePageSupplier(int total, int pageSize) { + return new PageSupplierFactory<>(TestResult::new, Function.identity()).newPageSupplier(total, pageSize); + } + + static PageSupplier newKeyPageSupplier(int total, int pageSize) { + return new PageSupplierFactory(TestResult::new, Function.identity()).newExtraRowPageSupplier(total, pageSize); + } + + static PageSupplier newViewPageSupplier(int total, int pageSize) { + return new PageSupplierFactory(TestViewResult::new, TestViewResultRow::new).newExtraRowPageSupplier(total, pageSize); + } + + static PostViewOptions getDefaultTestOptions(int limit) { + return getRequiredTestOptionsBuilder() + .limit(limit) + .build(); + } + + static Builder getRequiredTestOptionsBuilder() { + return new Builder() + .db("example-database") + .ddoc("test-ddoc") + .view("test-view"); + } + +} From 5d667845389a22815ae6b75f03b2d7a3a9395ed2 Mon Sep 17 00:00:00 2001 From: Rich Ellis Date: Tue, 11 Mar 2025 09:32:29 +0000 Subject: [PATCH 12/43] test: fix incorrect comments --- .../ibm/cloud/cloudant/features/pagination/BasePagerTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BasePagerTest.java b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BasePagerTest.java index b8a6ef606..d49a206f2 100644 --- a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BasePagerTest.java +++ b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BasePagerTest.java @@ -115,7 +115,7 @@ void testConstructor() { Assert.assertEquals(pager.getClient(), mockClient, "The client should be the supplied one."); } - // test page size limit + // test page size default @Test void testDefaultPageSize() { TestPager pager = new TestPager(mockClient, getRequiredTestOptionsBuilder().build()); @@ -123,7 +123,7 @@ void testDefaultPageSize() { Assert.assertEquals(pager.pageSize, 20, "The page size should be the default."); } - // test page size default + // test page size limit @Test void testLimitPageSize() { TestPager pager = new TestPager(mockClient, getDefaultTestOptions(42)); From 8371d36730e0bf049e0412688d8ad01be8d5b2bb Mon Sep 17 00:00:00 2001 From: Rich Ellis Date: Fri, 7 Mar 2025 17:21:26 +0000 Subject: [PATCH 13/43] fix: make new instances for Iterable --- .../features/pagination/AllDocsBasePager.java | 10 ++--- .../features/pagination/AllDocsPager.java | 25 ++++++----- .../pagination/AllDocsPartitionPager.java | 21 +++++---- .../features/pagination/BasePager.java | 45 ++++++++++++------- .../features/pagination/BookmarkPager.java | 8 ++-- .../features/pagination/FindBasePager.java | 6 +-- .../features/pagination/FindPager.java | 21 +++++---- .../pagination/FindPartitionPager.java | 21 +++++---- .../features/pagination/KeyPager.java | 18 ++++---- .../features/pagination/SearchBasePager.java | 6 +-- .../features/pagination/SearchPager.java | 21 +++++---- .../pagination/SearchPartitionPager.java | 21 +++++---- .../features/pagination/ViewBasePager.java | 8 ++-- .../features/pagination/ViewPager.java | 23 ++++++---- .../pagination/ViewPartitionPager.java | 23 ++++++---- .../features/pagination/BasePagerTest.java | 9 ++-- 16 files changed, 172 insertions(+), 114 deletions(-) diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsBasePager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsBasePager.java index ad7cc2bed..5c54d41dd 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsBasePager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsBasePager.java @@ -23,22 +23,22 @@ abstract class AllDocsBasePager extends KeyPager { - protected AllDocsBasePager(Cloudant client, O options) { + AllDocsBasePager(Cloudant client, O options) { super(client, options); } @Override - protected final Function> itemsGetter() { + final Function> itemsGetter() { return AllDocsResult::getRows; } @Override - protected final Function nextKeyGetter() { + final Function nextKeyGetter() { return DocsResultRow::getKey; } @Override - protected final Function nextKeyIdGetter() { + final Function nextKeyIdGetter() { return DocsResultRow::getId; } @@ -47,7 +47,7 @@ protected final Function nextKeyIdGetter() { * key is the same as id. */ @Override - protected final Optional> nextKeyIdSetter() { + final Optional> nextKeyIdSetter() { return Optional.empty(); } } diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsPager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsPager.java index dce4fae88..63dc30720 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsPager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsPager.java @@ -17,40 +17,45 @@ import java.util.function.Function; import com.ibm.cloud.cloudant.v1.Cloudant; import com.ibm.cloud.cloudant.v1.model.AllDocsResult; +import com.ibm.cloud.cloudant.v1.model.DocsResultRow; import com.ibm.cloud.cloudant.v1.model.PostAllDocsOptions; import com.ibm.cloud.cloudant.v1.model.PostAllDocsOptions.Builder; import com.ibm.cloud.sdk.core.http.ServiceCall; -final class AllDocsPager extends AllDocsBasePager { +final class AllDocsPager extends AllDocsBasePager { - protected AllDocsPager(Cloudant client, PostAllDocsOptions options) { - super(client, options); + AllDocsPager(Cloudant client, PostAllDocsOptions options) { + super(client, options); } @Override - protected Function optionsToBuilderFunction() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getOptionsToBuilderFunction'"); + Function optionsToBuilderFunction() { + return PostAllDocsOptions::newBuilder; } @Override - protected Function builderToOptionsFunction() { + Function builderToOptionsFunction() { return Builder::build; } @Override - protected BiFunction> nextRequestFunction() { + BiFunction> nextRequestFunction() { return Cloudant::postAllDocs; } @Override - protected BiFunction nextKeySetter() { + BiFunction nextKeySetter() { return Builder::startKey; } @Override - protected Function limitGetter() { + Function limitGetter() { return PostAllDocsOptions::limit; } + @Override + BiFunction> getConstructor() { + return AllDocsPager::new; + } + } diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsPartitionPager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsPartitionPager.java index e5265d466..370e529f6 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsPartitionPager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsPartitionPager.java @@ -17,42 +17,47 @@ import java.util.function.Function; import com.ibm.cloud.cloudant.v1.Cloudant; import com.ibm.cloud.cloudant.v1.model.AllDocsResult; +import com.ibm.cloud.cloudant.v1.model.DocsResultRow; import com.ibm.cloud.cloudant.v1.model.PostPartitionAllDocsOptions; import com.ibm.cloud.cloudant.v1.model.PostPartitionAllDocsOptions.Builder; import com.ibm.cloud.sdk.core.http.ServiceCall; final class AllDocsPartitionPager extends AllDocsBasePager { - protected AllDocsPartitionPager(Cloudant client, PostPartitionAllDocsOptions options) { + AllDocsPartitionPager(Cloudant client, PostPartitionAllDocsOptions options) { super(client, options); //TODO Auto-generated constructor stub } @Override - protected Function optionsToBuilderFunction() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getOptionsToBuilderFunction'"); + Function optionsToBuilderFunction() { + return PostPartitionAllDocsOptions::newBuilder; } @Override - protected Function builderToOptionsFunction() { + Function builderToOptionsFunction() { return Builder::build; } @Override - protected BiFunction> nextRequestFunction() { + BiFunction> nextRequestFunction() { return Cloudant::postPartitionAllDocs; } @Override - protected BiFunction nextKeySetter() { + BiFunction nextKeySetter() { return Builder::startKey; } @Override - protected Function limitGetter() { + Function limitGetter() { return PostPartitionAllDocsOptions::limit; } + @Override + BiFunction> getConstructor() { + return AllDocsPartitionPager::new; + } + } diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BasePager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BasePager.java index 7e045238f..4eb557aca 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BasePager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BasePager.java @@ -30,6 +30,7 @@ abstract class BasePager implements Pager, Iterator> { protected final Cloudant client; + protected final O initialOptions; protected final long pageSize; protected volatile boolean hasNext = true; protected volatile O nextPageOptions; @@ -37,9 +38,12 @@ abstract class BasePager implements Pager, Iterator> { protected BasePager(Cloudant client, O options) { this.client = client; // TODO validate initial options + // Clone user provided options into initial options + this.initialOptions = this.builderToOptionsFunction() + .apply(this.optionsToBuilderFunction().apply(options)); // Set the page size from the supplied options limit this.pageSize = getPageSizeFromOptionsLimit(options); - // Clone the supplied options into the nextPageOptions + // Set the first page options this.buildAndSetOptions(this.optionsToBuilderFunction().apply(options)); } @@ -49,12 +53,12 @@ public final boolean hasNext() { } @Override - public List next() { - return getNext(); + public List getNext() { + return this.next(); } @Override - public final List getNext() { + public List next() { if (this.hasNext()) { return Collections.unmodifiableList(this.nextRequest()); } else { @@ -64,11 +68,11 @@ public final List getNext() { @Override public final List getAll() { - return StreamSupport.stream(this.spliterator(), false) + return StreamSupport.stream(this.wrapIteratorAsSpliterator(this), false) .flatMap(List::stream).collect(Collectors.toList()); } - protected List nextRequest() { + List nextRequest() { ServiceCall request = nextRequestFunction().apply(this.client, nextPageOptions); R result = request.execute().getResult(); List items = itemsGetter().apply(result); @@ -86,29 +90,40 @@ private void buildAndSetOptions(B optionsBuilder) { this.nextPageOptions = this.builderToOptionsFunction().apply(optionsBuilder); } - protected abstract Function optionsToBuilderFunction(); + abstract Function optionsToBuilderFunction(); - protected abstract Function builderToOptionsFunction(); + abstract Function builderToOptionsFunction(); - protected abstract Function> itemsGetter(); + abstract Function> itemsGetter(); - protected abstract void setNextPageOptions(B builder, R result); + abstract void setNextPageOptions(B builder, R result); - protected abstract BiFunction> nextRequestFunction(); + abstract BiFunction> nextRequestFunction(); - protected abstract Function limitGetter(); + abstract Function limitGetter(); - protected Long getPageSizeFromOptionsLimit(O opts) { + Long getPageSizeFromOptionsLimit(O opts) { return Optional.ofNullable(limitGetter().apply(opts)).orElse(20L); } + private BasePager newInstance() { + return this.getConstructor().apply(this.client, this.initialOptions); + } + + abstract BiFunction> getConstructor(); + @Override public Iterator> iterator() { - return this; + return this.newInstance(); } @Override public Spliterator> spliterator() { - return Spliterators.spliteratorUnknownSize(this, Spliterator.ORDERED | Spliterator.NONNULL | Spliterator.IMMUTABLE); + return wrapIteratorAsSpliterator(this.iterator()); + } + + private Spliterator> wrapIteratorAsSpliterator(Iterator> iterator) { + return Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED | Spliterator.NONNULL | Spliterator.IMMUTABLE); } + } diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BookmarkPager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BookmarkPager.java index 1eeff5dac..c85b2331e 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BookmarkPager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BookmarkPager.java @@ -19,16 +19,16 @@ abstract class BookmarkPager extends BasePager { - protected BookmarkPager(Cloudant client, O options) { + BookmarkPager(Cloudant client, O options) { super(client, options); } - protected abstract Function bookmarkGetter(); + abstract Function bookmarkGetter(); - protected abstract BiFunction bookmarkSetter(); + abstract BiFunction bookmarkSetter(); @Override - protected final void setNextPageOptions(B builder, R result) { + final void setNextPageOptions(B builder, R result) { String bookmark = bookmarkGetter().apply(result); bookmarkSetter().apply(builder, bookmark); } diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindBasePager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindBasePager.java index 9a8ae50e6..04928ee11 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindBasePager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindBasePager.java @@ -21,17 +21,17 @@ abstract class FindBasePager extends BookmarkPager { - protected FindBasePager(Cloudant client, O options) { + FindBasePager(Cloudant client, O options) { super(client, options); } @Override - protected final Function> itemsGetter() { + final Function> itemsGetter() { return FindResult::getDocs; } @Override - protected final Function bookmarkGetter() { + final Function bookmarkGetter() { return FindResult::getBookmark; } diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindPager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindPager.java index 379387edb..7bacf08fe 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindPager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindPager.java @@ -16,6 +16,7 @@ import java.util.function.BiFunction; import java.util.function.Function; import com.ibm.cloud.cloudant.v1.Cloudant; +import com.ibm.cloud.cloudant.v1.model.Document; import com.ibm.cloud.cloudant.v1.model.FindResult; import com.ibm.cloud.cloudant.v1.model.PostFindOptions; import com.ibm.cloud.cloudant.v1.model.PostFindOptions.Builder; @@ -23,34 +24,38 @@ final class FindPager extends FindBasePager { - protected FindPager(Cloudant client, PostFindOptions options) { + FindPager(Cloudant client, PostFindOptions options) { super(client, options); } @Override - protected Function optionsToBuilderFunction() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getOptionsToBuilderFunction'"); + Function optionsToBuilderFunction() { + return PostFindOptions::newBuilder; } @Override - protected Function builderToOptionsFunction() { + Function builderToOptionsFunction() { return Builder::build; } @Override - protected BiFunction bookmarkSetter() { + BiFunction bookmarkSetter() { return Builder::bookmark; } @Override - protected BiFunction> nextRequestFunction() { + BiFunction> nextRequestFunction() { return Cloudant::postFind; } @Override - protected Function limitGetter() { + Function limitGetter() { return PostFindOptions::limit; } + @Override + BiFunction> getConstructor() { + return FindPager::new; + } + } diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindPartitionPager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindPartitionPager.java index f8118c2e4..13f969b71 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindPartitionPager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindPartitionPager.java @@ -16,6 +16,7 @@ import java.util.function.BiFunction; import java.util.function.Function; import com.ibm.cloud.cloudant.v1.Cloudant; +import com.ibm.cloud.cloudant.v1.model.Document; import com.ibm.cloud.cloudant.v1.model.FindResult; import com.ibm.cloud.cloudant.v1.model.PostPartitionFindOptions; import com.ibm.cloud.cloudant.v1.model.PostPartitionFindOptions.Builder; @@ -23,34 +24,38 @@ final class FindPartitionPager extends FindBasePager { - protected FindPartitionPager(Cloudant client, PostPartitionFindOptions options) { + FindPartitionPager(Cloudant client, PostPartitionFindOptions options) { super(client, options); } @Override - protected Function optionsToBuilderFunction() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getOptionsToBuilderFunction'"); + Function optionsToBuilderFunction() { + return PostPartitionFindOptions::newBuilder; } @Override - protected Function builderToOptionsFunction() { + Function builderToOptionsFunction() { return Builder::build; } @Override - protected BiFunction bookmarkSetter() { + BiFunction bookmarkSetter() { return Builder::bookmark; } @Override - protected BiFunction> nextRequestFunction() { + BiFunction> nextRequestFunction() { return Cloudant::postPartitionFind; } @Override - protected Function limitGetter() { + Function limitGetter() { return PostPartitionFindOptions::limit; } + @Override + BiFunction> getConstructor() { + return FindPartitionPager::new; + } + } diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/KeyPager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/KeyPager.java index 76dc2267f..2d972abdf 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/KeyPager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/KeyPager.java @@ -21,28 +21,28 @@ abstract class KeyPager extends BasePager { - protected KeyPager(Cloudant client, O options) { + KeyPager(Cloudant client, O options) { super(client, options); } - protected abstract BiFunction nextKeySetter(); + abstract BiFunction nextKeySetter(); - protected abstract Optional> nextKeyIdSetter(); + abstract Optional> nextKeyIdSetter(); - protected abstract Function nextKeyGetter(); + abstract Function nextKeyGetter(); - protected abstract Function nextKeyIdGetter(); + abstract Function nextKeyIdGetter(); - protected List nextRequest() { + List nextRequest() { List items = super.nextRequest(); - if (hasNext()) { + if (this.hasNext()) { items.remove(items.size() - 1); } return items; } @Override - protected final void setNextPageOptions(B builder, R result) { + final void setNextPageOptions(B builder, R result) { List items = itemsGetter().apply(result); I lastItem = items.get(items.size() - 1); K nextKey = nextKeyGetter().apply(lastItem); @@ -52,7 +52,7 @@ protected final void setNextPageOptions(B builder, R result) { } @Override - protected Long getPageSizeFromOptionsLimit(O opts) { + Long getPageSizeFromOptionsLimit(O opts) { return super.getPageSizeFromOptionsLimit(opts) + 1; } diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchBasePager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchBasePager.java index dfa705d68..6b8c9281c 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchBasePager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchBasePager.java @@ -21,17 +21,17 @@ abstract class SearchBasePager extends BookmarkPager { - protected SearchBasePager(Cloudant client, O options) { + SearchBasePager(Cloudant client, O options) { super(client, options); } @Override - protected final Function> itemsGetter() { + final Function> itemsGetter() { return SearchResult::getRows; } @Override - protected final Function bookmarkGetter() { + final Function bookmarkGetter() { return SearchResult::getBookmark; } diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchPager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchPager.java index e7b8d9942..46298bb18 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchPager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchPager.java @@ -19,38 +19,43 @@ import com.ibm.cloud.cloudant.v1.model.PostSearchOptions; import com.ibm.cloud.cloudant.v1.model.PostSearchOptions.Builder; import com.ibm.cloud.cloudant.v1.model.SearchResult; +import com.ibm.cloud.cloudant.v1.model.SearchResultRow; import com.ibm.cloud.sdk.core.http.ServiceCall; final class SearchPager extends SearchBasePager { - protected SearchPager(Cloudant client, PostSearchOptions options) { + SearchPager(Cloudant client, PostSearchOptions options) { super(client, options); } @Override - protected Function optionsToBuilderFunction() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getOptionsToBuilderFunction'"); + Function optionsToBuilderFunction() { + return PostSearchOptions::newBuilder; } @Override - protected Function builderToOptionsFunction() { + Function builderToOptionsFunction() { return Builder::build; } @Override - protected BiFunction bookmarkSetter() { + BiFunction bookmarkSetter() { return Builder::bookmark; } @Override - protected BiFunction> nextRequestFunction() { + BiFunction> nextRequestFunction() { return Cloudant::postSearch; } @Override - protected Function limitGetter() { + Function limitGetter() { return PostSearchOptions::limit; } + @Override + BiFunction> getConstructor() { + return SearchPager::new; + } + } diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchPartitionPager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchPartitionPager.java index bb19a35b4..c14b5f1c5 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchPartitionPager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchPartitionPager.java @@ -19,38 +19,43 @@ import com.ibm.cloud.cloudant.v1.model.PostPartitionSearchOptions; import com.ibm.cloud.cloudant.v1.model.PostPartitionSearchOptions.Builder; import com.ibm.cloud.cloudant.v1.model.SearchResult; +import com.ibm.cloud.cloudant.v1.model.SearchResultRow; import com.ibm.cloud.sdk.core.http.ServiceCall; final class SearchPartitionPager extends SearchBasePager { - protected SearchPartitionPager(Cloudant client, PostPartitionSearchOptions options) { + SearchPartitionPager(Cloudant client, PostPartitionSearchOptions options) { super(client, options); } @Override - protected Function optionsToBuilderFunction() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getOptionsToBuilderFunction'"); + Function optionsToBuilderFunction() { + return PostPartitionSearchOptions::newBuilder; } @Override - protected Function builderToOptionsFunction() { + Function builderToOptionsFunction() { return Builder::build; } @Override - protected BiFunction bookmarkSetter() { + BiFunction bookmarkSetter() { return Builder::bookmark; } @Override - protected BiFunction> nextRequestFunction() { + BiFunction> nextRequestFunction() { return Cloudant::postPartitionSearch; } @Override - protected Function limitGetter() { + Function limitGetter() { return PostPartitionSearchOptions::limit; } + @Override + BiFunction> getConstructor() { + return SearchPartitionPager::new; + } + } diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewBasePager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewBasePager.java index 5c555231a..ae71fea88 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewBasePager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewBasePager.java @@ -21,22 +21,22 @@ abstract class ViewBasePager extends KeyPager { - protected ViewBasePager(Cloudant client, O options) { + ViewBasePager(Cloudant client, O options) { super(client, options); } @Override - protected final Function> itemsGetter() { + final Function> itemsGetter() { return ViewResult::getRows; } @Override - protected final Function nextKeyGetter() { + final Function nextKeyGetter() { return ViewResultRow::getKey; } @Override - protected final Function nextKeyIdGetter() { + final Function nextKeyIdGetter() { return ViewResultRow::getId; } diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewPager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewPager.java index 5293b999c..c3cef2299 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewPager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewPager.java @@ -20,43 +20,48 @@ import com.ibm.cloud.cloudant.v1.model.PostViewOptions; import com.ibm.cloud.cloudant.v1.model.PostViewOptions.Builder; import com.ibm.cloud.cloudant.v1.model.ViewResult; +import com.ibm.cloud.cloudant.v1.model.ViewResultRow; import com.ibm.cloud.sdk.core.http.ServiceCall; final class ViewPager extends ViewBasePager { - protected ViewPager(Cloudant client, PostViewOptions options) { + ViewPager(Cloudant client, PostViewOptions options) { super(client, options); } @Override - protected Function optionsToBuilderFunction() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getOptionsToBuilderFunction'"); + Function optionsToBuilderFunction() { + return PostViewOptions::newBuilder; } @Override - protected Function builderToOptionsFunction() { + Function builderToOptionsFunction() { return Builder::build; } @Override - protected BiFunction> nextRequestFunction() { + BiFunction> nextRequestFunction() { return Cloudant::postView; } @Override - protected BiFunction nextKeySetter() { + BiFunction nextKeySetter() { return Builder::startKey; } @Override - protected Optional> nextKeyIdSetter() { + Optional> nextKeyIdSetter() { return Optional.of(Builder::startKeyDocId); } @Override - protected Function limitGetter() { + Function limitGetter() { return PostViewOptions::limit; } + @Override + BiFunction> getConstructor() { + return ViewPager::new; + } + } diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewPartitionPager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewPartitionPager.java index c052b87a4..55d59c361 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewPartitionPager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewPartitionPager.java @@ -20,43 +20,48 @@ import com.ibm.cloud.cloudant.v1.model.PostPartitionViewOptions; import com.ibm.cloud.cloudant.v1.model.PostPartitionViewOptions.Builder; import com.ibm.cloud.cloudant.v1.model.ViewResult; +import com.ibm.cloud.cloudant.v1.model.ViewResultRow; import com.ibm.cloud.sdk.core.http.ServiceCall; final class ViewPartitionPager extends ViewBasePager { - protected ViewPartitionPager(Cloudant client, PostPartitionViewOptions options) { + ViewPartitionPager(Cloudant client, PostPartitionViewOptions options) { super(client, options); } @Override - protected Function optionsToBuilderFunction() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getOptionsToBuilderFunction'"); + Function optionsToBuilderFunction() { + return PostPartitionViewOptions::newBuilder; } @Override - protected Function builderToOptionsFunction() { + Function builderToOptionsFunction() { return Builder::build; } @Override - protected BiFunction> nextRequestFunction() { + BiFunction> nextRequestFunction() { return Cloudant::postPartitionView; } @Override - protected BiFunction nextKeySetter() { + BiFunction nextKeySetter() { return Builder::startKey; } @Override - protected Optional> nextKeyIdSetter() { + Optional> nextKeyIdSetter() { return Optional.of(Builder::startKeyDocId); } @Override - protected Function limitGetter() { + Function limitGetter() { return PostPartitionViewOptions::limit; } + @Override + BiFunction> getConstructor() { + return ViewPartitionPager::new; + } + } diff --git a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BasePagerTest.java b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BasePagerTest.java index d49a206f2..2751e2f64 100644 --- a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BasePagerTest.java +++ b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BasePagerTest.java @@ -59,9 +59,7 @@ Cloudant getClient() { */ @Override protected Function optionsToBuilderFunction() { - return o -> { - return BasePagerTest.getRequiredTestOptionsBuilder(); - }; + return PostViewOptions::newBuilder; } @Override @@ -105,6 +103,11 @@ protected Function limitGetter() { return PostViewOptions::limit; } + @Override + BiFunction> getConstructor() { + return TestPager::new; + } + } // test constructor From 07a76b74087a5bee8a39b39e528cb1fa0e97a5c6 Mon Sep 17 00:00:00 2001 From: Rich Ellis Date: Fri, 7 Mar 2025 17:21:57 +0000 Subject: [PATCH 14/43] feat: add DesignDocsPager outline --- .../features/pagination/DesignDocsPager.java | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/DesignDocsPager.java diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/DesignDocsPager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/DesignDocsPager.java new file mode 100644 index 000000000..20cda2a82 --- /dev/null +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/DesignDocsPager.java @@ -0,0 +1,61 @@ +/** + * © Copyright IBM Corporation 2025. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.ibm.cloud.cloudant.features.pagination; + +import java.util.function.BiFunction; +import java.util.function.Function; +import com.ibm.cloud.cloudant.v1.Cloudant; +import com.ibm.cloud.cloudant.v1.model.AllDocsResult; +import com.ibm.cloud.cloudant.v1.model.DocsResultRow; +import com.ibm.cloud.cloudant.v1.model.PostDesignDocsOptions; +import com.ibm.cloud.cloudant.v1.model.PostDesignDocsOptions.Builder; +import com.ibm.cloud.sdk.core.http.ServiceCall; + +public class DesignDocsPager extends AllDocsBasePager { + + DesignDocsPager(Cloudant client, PostDesignDocsOptions options) { + super(client, options); + } + + @Override + BiFunction nextKeySetter() { + return Builder::key; + } + + @Override + Function optionsToBuilderFunction() { + return PostDesignDocsOptions::newBuilder; + } + + @Override + Function builderToOptionsFunction() { + return Builder::build; + } + + @Override + BiFunction> nextRequestFunction() { + return Cloudant::postDesignDocs; + } + + @Override + Function limitGetter() { + return PostDesignDocsOptions::limit; + } + + @Override + BiFunction> getConstructor() { + return DesignDocsPager::new; + } + +} From 8ba21fab117d1b40bea1245c52dc1045e1e2289f Mon Sep 17 00:00:00 2001 From: Rich Ellis Date: Mon, 10 Mar 2025 15:54:18 +0000 Subject: [PATCH 15/43] fix: SQ complaint about volatile Object --- .../cloud/cloudant/features/pagination/BasePager.java | 9 +++++---- .../cloudant/features/pagination/BasePagerTest.java | 6 +++--- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BasePager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BasePager.java index 4eb557aca..5e09a918f 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BasePager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BasePager.java @@ -20,6 +20,7 @@ import java.util.Optional; import java.util.Spliterator; import java.util.Spliterators; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.BiFunction; import java.util.function.Function; import java.util.stream.Collectors; @@ -32,8 +33,8 @@ abstract class BasePager implements Pager, Iterator> { protected final Cloudant client; protected final O initialOptions; protected final long pageSize; + protected final AtomicReference nextPageOptionsRef = new AtomicReference<>(); protected volatile boolean hasNext = true; - protected volatile O nextPageOptions; protected BasePager(Cloudant client, O options) { this.client = client; @@ -73,13 +74,13 @@ public final List getAll() { } List nextRequest() { - ServiceCall request = nextRequestFunction().apply(this.client, nextPageOptions); + ServiceCall request = nextRequestFunction().apply(this.client, nextPageOptionsRef.get()); R result = request.execute().getResult(); List items = itemsGetter().apply(result); if (items.size() < this.pageSize) { this.hasNext = false; } else { - B optionsBuilder = optionsToBuilderFunction().apply(nextPageOptions); + B optionsBuilder = optionsToBuilderFunction().apply(nextPageOptionsRef.get()); setNextPageOptions(optionsBuilder, result); this.buildAndSetOptions(optionsBuilder); } @@ -87,7 +88,7 @@ List nextRequest() { } private void buildAndSetOptions(B optionsBuilder) { - this.nextPageOptions = this.builderToOptionsFunction().apply(optionsBuilder); + this.nextPageOptionsRef.set(this.builderToOptionsFunction().apply(optionsBuilder)); } abstract Function optionsToBuilderFunction(); diff --git a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BasePagerTest.java b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BasePagerTest.java index 2751e2f64..f03df55be 100644 --- a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BasePagerTest.java +++ b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BasePagerTest.java @@ -324,16 +324,16 @@ void testSetNextPageOptions() { PageSupplier pageSupplier = PaginationTestHelpers.newBasePageSupplier(5*pageSize, pageSize); MockPagerClient c = new MockPagerClient(pageSupplier); TestPager pager = new TestPager(c, getDefaultTestOptions(pageSize)); - Assert.assertNull(pager.nextPageOptions.key(), "key should initially be null."); + Assert.assertNull(pager.nextPageOptionsRef.get().key(), "key should initially be null."); // Since we use a page size of 1, each next page options key, is the same as the element from the page int page = 0; while(pager.hasNext()) { pager.getNext(); if (pager.hasNext()) { - Assert.assertEquals(pager.nextPageOptions.key(), page, "The key should increment per page."); + Assert.assertEquals(pager.nextPageOptionsRef.get().key(), page, "The key should increment per page."); } else { // Options don't change for last page - Assert.assertEquals(pager.nextPageOptions.key(), page - 1, "The options should not be set for the final page."); + Assert.assertEquals(pager.nextPageOptionsRef.get().key(), page - 1, "The options should not be set for the final page."); } page++; } From 7a02b4c4f674e2c3abed5f1f1fabf2a02a9641f8 Mon Sep 17 00:00:00 2001 From: Rich Ellis Date: Mon, 10 Mar 2025 17:18:02 +0000 Subject: [PATCH 16/43] feat: KeyPager implementation --- .../features/pagination/AllDocsBasePager.java | 6 + .../features/pagination/KeyPager.java | 16 +- .../features/pagination/ViewBasePager.java | 23 ++ .../features/pagination/KeyPagerTest.java | 276 ++++++++++++++++++ .../pagination/PaginationTestHelpers.java | 4 + 5 files changed, 324 insertions(+), 1 deletion(-) create mode 100644 modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/KeyPagerTest.java diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsBasePager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsBasePager.java index 5c54d41dd..7dd775cdf 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsBasePager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsBasePager.java @@ -50,4 +50,10 @@ final Function nextKeyIdGetter() { final Optional> nextKeyIdSetter() { return Optional.empty(); } + + @Override + final Optional checkBoundary(DocsResultRow penultimateItem, DocsResultRow lastItem) { + // AllDocs and DesignDocs pagers always have unique keys (because they are document IDs) + return Optional.empty(); + } } diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/KeyPager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/KeyPager.java index 2d972abdf..e78c6f942 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/KeyPager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/KeyPager.java @@ -21,6 +21,8 @@ abstract class KeyPager extends BasePager { + private Optional boundaryFailure = Optional.empty(); + KeyPager(Cloudant client, O options) { super(client, options); } @@ -34,9 +36,19 @@ abstract class KeyPager extends BasePager { abstract Function nextKeyIdGetter(); List nextRequest() { + // If the previous request had the duplicate boundary error + // throw it now because we cannot safely get the next page. + if (boundaryFailure.isPresent()) { + throw new UnsupportedOperationException(boundaryFailure.get()); + } List items = super.nextRequest(); if (this.hasNext()) { - items.remove(items.size() - 1); + I lastItem = items.remove(items.size() - 1); + if (items.size() > 0) { + I penultimateItem = items.get(items.size() - 1); + // Defer a throw for a boundary failure to the next request + boundaryFailure = checkBoundary(penultimateItem, lastItem); + } } return items; } @@ -56,4 +68,6 @@ Long getPageSizeFromOptionsLimit(O opts) { return super.getPageSizeFromOptionsLimit(opts) + 1; } + abstract Optional checkBoundary(I penultimateItem, I lastItem); + } diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewBasePager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewBasePager.java index ae71fea88..bd7e37e59 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewBasePager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewBasePager.java @@ -14,6 +14,7 @@ package com.ibm.cloud.cloudant.features.pagination; import java.util.List; +import java.util.Optional; import java.util.function.Function; import com.ibm.cloud.cloudant.v1.Cloudant; import com.ibm.cloud.cloudant.v1.model.ViewResult; @@ -40,4 +41,26 @@ final Function nextKeyIdGetter() { return ViewResultRow::getId; } + @Override + final Optional checkBoundary(ViewResultRow penultimateItem, ViewResultRow lastItem) { + String pId = penultimateItem.getId(); + String lId = lastItem.getId(); + if (pId.equals(lId)) { + // ID's are the same, check the keys + Object pKey = penultimateItem.getKey(); + Object lKey = lastItem.getKey(); + // Check reference equality first (e.g. null) + // Then check values + if (pKey == lKey || pKey != null && pKey.equals(lKey)) { + // Identical keys, set an error message + return Optional.of( + String.format( + "Cannot paginate on a boundary containing identical keys '%s' and document IDs '%s'", + String.valueOf(lKey), + lId)); + } + } + return Optional.empty(); + } + } diff --git a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/KeyPagerTest.java b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/KeyPagerTest.java new file mode 100644 index 000000000..659d112eb --- /dev/null +++ b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/KeyPagerTest.java @@ -0,0 +1,276 @@ +/** + * © Copyright IBM Corporation 2025. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.ibm.cloud.cloudant.features.pagination; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.function.BiFunction; +import java.util.function.Function; +import org.testng.Assert; +import org.testng.annotations.Test; +import com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.MockPagerClient; +import com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.PageSupplier; +import com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.TestResult; +import com.ibm.cloud.cloudant.v1.Cloudant; +import com.ibm.cloud.cloudant.v1.model.PostViewOptions; +import com.ibm.cloud.cloudant.v1.model.PostViewOptions.Builder; +import com.ibm.cloud.sdk.core.http.ServiceCall; + +import static com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.getDefaultTestOptions; +import static com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.getRequiredTestOptionsBuilder; +import static com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.newKeyPageSupplier; + +public class KeyPagerTest { + + private Cloudant mockClient = new MockPagerClient(null); + + /** + * This test sub-class of KeyPager implicitly tests that various abstract methods are correctly + * called. + */ + class TestKeyPager extends KeyPager { + + protected TestKeyPager(Cloudant client, PostViewOptions options) { + super(client, options); + } + + Cloudant getClient() { + return this.client; + } + + /** + * Delegates to our next mock. + * If the BasePager didn't correctly call this the mocks wouldn't work. + */ + @Override + protected BiFunction> nextRequestFunction() { + return (c, o) -> { + return ((MockPagerClient) c).testCall(); + }; + } + + @Override + protected Function limitGetter() { + return PostViewOptions::limit; + } + + @Override + protected BiFunction nextKeySetter() { + return Builder::startKey; + } + + @Override + protected Optional> nextKeyIdSetter() { + return Optional.of(Builder::startKeyDocId); + } + + @Override + protected Function nextKeyGetter() { + return Function.identity(); + } + + @Override + protected Function nextKeyIdGetter() { + return String::valueOf; + } + + @Override + protected Function optionsToBuilderFunction() { + return o -> { + return new Builder(o.db(), o.ddoc(), o.view()); + }; + } + + @Override + protected Function builderToOptionsFunction() { + return Builder::build; + } + + @Override + protected Function> itemsGetter() { + return TestResult::getRows; + } + + @Override + BiFunction> getConstructor() { + return TestKeyPager::new; + } + + @Override + Optional checkBoundary(Integer penultimateItem, Integer lastItem) { + return Optional.empty(); + } + + } + + // Test page size default (+1) + @Test + void testDefaultPageSize() { + TestKeyPager pager = new TestKeyPager(mockClient, getRequiredTestOptionsBuilder().build()); + // Assert the limit provided as page size + Assert.assertEquals(pager.pageSize, 21, "The page size should be one more than the default limit."); + } + + // Test page size limit (+1) + @Test + void testLimitPageSize() { + TestKeyPager pager = new TestKeyPager(mockClient, getDefaultTestOptions(42)); + // Assert the limit provided as page size + Assert.assertEquals(pager.pageSize, 43, "The page size should be one more than the supplied limit."); + } + + // Test all items on page when no more pages + @Test + void testGetNextPageLessThanLimit() { + int pageSize = 21; + PageSupplier pageSupplier = newKeyPageSupplier(pageSize, pageSize); + MockPagerClient c = new MockPagerClient(pageSupplier); + TestKeyPager pager = new TestKeyPager(c, getDefaultTestOptions(pageSize)); + List actualPage = pager.getNext(); + // Assert first page + Assert.assertEquals(actualPage, pageSupplier.pages.get(0), "The actual page should match the expected page."); + // Assert size is pageSize + Assert.assertEquals(actualPage.size(), pageSize, "The actual page size should match the expected page size."); + // Assert hasNext false because n+1 limit is 1 more than user page size + Assert.assertFalse(pager.hasNext(), "hasNext() should return false."); + } + + // Test correct items on page when n+1 + @Test + void testGetNextPageEqualToLimit() { + int pageSize = 14; + PageSupplier pageSupplier = newKeyPageSupplier(pageSize+1, pageSize); + MockPagerClient c = new MockPagerClient(pageSupplier); + TestKeyPager pager = new TestKeyPager(c, getDefaultTestOptions(pageSize)); + List actualPage = pager.getNext(); + // Assert first page + Assert.assertEquals(actualPage, pageSupplier.pages.get(0), "The actual page should match the expected page."); + // Assert size is pageSize + Assert.assertEquals(actualPage.size(), pageSize, "The actual page size should match the expected page size."); + // Assert hasNext true + Assert.assertTrue(pager.hasNext(), "hasNext() should return true."); + // Assert start key is correct, note the result rows start at zero, so pageSize, not pageSize + 1 + Assert.assertEquals(pager.nextPageOptionsRef.get().startKey(), pageSize, "The start key should be page size."); + // Get next page + List actualSecondPage = pager.getNext(); + // Assert first item on second page is correct (again we start at zero, so pageSize not pageSize + 1) + Assert.assertEquals(actualSecondPage.get(0), pageSize, "The first item on the second page should be as expected."); + // Assert size is 1 + Assert.assertEquals(actualSecondPage.size(), 1, "The actual page size should match the expected page size."); + // Assert second page + Assert.assertEquals(actualSecondPage, pageSupplier.pages.get(1), "The actual page should match the expected page."); + // Assert hasNext false + Assert.assertFalse(pager.hasNext(), "hasNext() should return false."); + } + + // Test correct items on page when n+more + @Test + void testGetNextPageGreaterThanLimit() { + int pageSize = 7; + PageSupplier pageSupplier = newKeyPageSupplier(pageSize+2, pageSize); + MockPagerClient c = new MockPagerClient(pageSupplier); + TestKeyPager pager = new TestKeyPager(c, getDefaultTestOptions(pageSize)); + List actualPage = pager.getNext(); + // Assert first page + Assert.assertEquals(actualPage, pageSupplier.pages.get(0), "The actual page should match the expected page."); + // Assert size is pageSize + Assert.assertEquals(actualPage.size(), pageSize, "The actual page size should match the expected page size."); + // Assert hasNext true + Assert.assertTrue(pager.hasNext(), "hasNext() should return true."); + // Assert start key is correct, note the result rows start at zero, so pageSize, not pageSize + 1 + Assert.assertEquals(pager.nextPageOptionsRef.get().startKey(), pageSize, "The start key should be page size plus one."); + // Get next page + List actualSecondPage = pager.getNext(); + // Assert first item on second page is correct (again we start at zero, so pageSize not pageSize + 1) + Assert.assertEquals(actualSecondPage.get(0), pageSize, "The first item on the second page should be as expected."); + // Assert size is 2 + Assert.assertEquals(actualSecondPage.size(), 2, "The actual page size should match the expected page size."); + // Assert second page + Assert.assertEquals(actualSecondPage, pageSupplier.pages.get(1), "The actual page should match the expected page."); + // Assert hasNext false + Assert.assertFalse(pager.hasNext(), "hasNext() should return false."); + } + + // Test getting all items + @Test + void testGetAll() { + int pageSize = 3; + PageSupplier pageSupplier = newKeyPageSupplier(pageSize*12, pageSize); + MockPagerClient c = new MockPagerClient(pageSupplier); + TestKeyPager pager = new TestKeyPager(c, getDefaultTestOptions(pageSize)); + List actualItems = pager.getAll(); + Assert.assertEquals(actualItems, pageSupplier.allItems, "The results should match all the pages."); + } + + @Test + void testNoBoundaryCheckByDefault() { + int pageSize = 1; + // Make pages with identical rows + List pageOne = new ArrayList<>(List.of(1, 1)); + List pageTwo = new ArrayList<>(List.of(1, 1)); + PageSupplier pageSupplier = PaginationTestHelpers.newPageSupplierFromList(List.of(pageOne, pageTwo)); + MockPagerClient c = new MockPagerClient(pageSupplier); + TestKeyPager pager = new TestKeyPager(c, getDefaultTestOptions(pageSize)); + // Get and assert page + Assert.assertEquals(pager.getNext(), pageSupplier.pages.get(0), "The actual page should match the expected page."); + // Assert hasNext true + Assert.assertTrue(pager.hasNext(), "hasNext() should return true."); + // Boundary check implementation should not throw + Assert.assertEquals(pager.getNext(), pageSupplier.pages.get(1), "The actual page should match the expected page."); + } + + @Test + void testBoundaryFailureThrowsOnGetNext() { + int pageSize = 1; + // Make pages with identical rows + List pageOne = new ArrayList<>(List.of(1, 1)); + List pageTwo = new ArrayList<>(List.of(1, 1)); + PageSupplier pageSupplier = PaginationTestHelpers.newPageSupplierFromList(List.of(pageOne, pageTwo)); + MockPagerClient c = new MockPagerClient(pageSupplier); + TestKeyPager pager = new TestKeyPager(c, getDefaultTestOptions(pageSize)) { + @Override + Optional checkBoundary(Integer penultimateItem, Integer lastItem) { + return Optional.of("Test boundary check failure"); + }}; + // Get and assert page + Assert.assertEquals(pager.getNext(), pageSupplier.pages.get(0), "The actual page should match the expected page."); + // Assert hasNext true + Assert.assertTrue(pager.hasNext(), "hasNext() should return true."); + // The optional isn't empty so it check boundary should throw + Assert.assertThrows(UnsupportedOperationException.class, () -> { + pager.getNext(); + }); + } + + @Test + void testNoBoundaryCheckWhenNoItemsLeft() { + int pageSize = 1; + // Make pages with identical rows + List pageOne = new ArrayList<>(List.of(1)); + PageSupplier pageSupplier = PaginationTestHelpers.newPageSupplierFromList(List.of(pageOne)); + MockPagerClient c = new MockPagerClient(pageSupplier); + TestKeyPager pager = new TestKeyPager(c, getDefaultTestOptions(pageSize)) { + @Override + Optional checkBoundary(Integer penultimateItem, Integer lastItem) { + // Throw here to cause the test to fail if checkBoundary is called. + throw new RuntimeException("Test failure, checkBoundary should not be called."); + }}; + // Get and assert page + Assert.assertEquals(pager.getNext(), pageSupplier.pages.get(0), "The actual page should match the expected page."); + // Assert hasNext false + Assert.assertFalse(pager.hasNext(), "hasNext() should return false."); + } + +} diff --git a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/PaginationTestHelpers.java b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/PaginationTestHelpers.java index 54676f51b..49c729269 100644 --- a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/PaginationTestHelpers.java +++ b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/PaginationTestHelpers.java @@ -148,6 +148,10 @@ static PageSupplier newViewPageSupplier(int total, in return new PageSupplierFactory(TestViewResult::new, TestViewResultRow::new).newExtraRowPageSupplier(total, pageSize); } + static PageSupplier newPageSupplierFromList(List> pages) { + return new PageSupplier(TestResult::new, pages); + } + static PostViewOptions getDefaultTestOptions(int limit) { return getRequiredTestOptionsBuilder() .limit(limit) From 3b7e9d8da948bb8f7d3a227dfc27f2a3572851e7 Mon Sep 17 00:00:00 2001 From: Rich Ellis Date: Thu, 13 Mar 2025 10:26:43 +0000 Subject: [PATCH 17/43] feat: add getRows stream --- .../features/pagination/BasePager.java | 8 ++++++- .../cloudant/features/pagination/Pager.java | 21 +++++++++++++++---- .../features/pagination/BasePagerTest.java | 12 +++++++++++ 3 files changed, 36 insertions(+), 5 deletions(-) diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BasePager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BasePager.java index 5e09a918f..11a95bcbc 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BasePager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BasePager.java @@ -24,6 +24,7 @@ import java.util.function.BiFunction; import java.util.function.Function; import java.util.stream.Collectors; +import java.util.stream.Stream; import java.util.stream.StreamSupport; import com.ibm.cloud.cloudant.v1.Cloudant; import com.ibm.cloud.sdk.core.http.ServiceCall; @@ -69,8 +70,13 @@ public List next() { @Override public final List getAll() { + return getRows().collect(Collectors.toList()); + } + + @Override + public final Stream getRows() { return StreamSupport.stream(this.wrapIteratorAsSpliterator(this), false) - .flatMap(List::stream).collect(Collectors.toList()); + .flatMap(List::stream); } List nextRequest() { diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/Pager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/Pager.java index 93a033909..fa5b33fd9 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/Pager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/Pager.java @@ -14,7 +14,7 @@ package com.ibm.cloud.cloudant.features.pagination; import java.util.List; - +import java.util.stream.Stream; import com.ibm.cloud.cloudant.v1.Cloudant; import com.ibm.cloud.cloudant.v1.model.DocsResultRow; import com.ibm.cloud.cloudant.v1.model.Document; @@ -34,6 +34,8 @@ * * Use the static methods to instantiate a Pager instance for * the required operation. + * + * @param the item type of the page rows */ public interface Pager extends Iterable> { @@ -47,18 +49,29 @@ public interface Pager extends Iterable> { /** * Get the next page in the sequence. * - * @return java.util.List of the items from the next page + * @return java.util.List of the rows from the next page */ List getNext(); /** * Get all the avaialble pages and collect them into a - * single java.util.List. + * single java.util.List. This operation is not lazy and + * may consume significant memory to hold the entire + * results collection for large queries. * - * @return java.util.List of the items from all the pages + * @return java.util.List of the rows from all the pages */ List getAll(); + /** + * Stream all the rows from all the available pages one at + * a time. This operation is lazy and requests pages as + * needed. + * + * @return java.util.Stream of the rows from all the pages + */ + Stream getRows(); + /** * Get a Pager for the postAllDocs operation. * The page size is configured with the limit paramater of the options. diff --git a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BasePagerTest.java b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BasePagerTest.java index f03df55be..d449b9acd 100644 --- a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BasePagerTest.java +++ b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BasePagerTest.java @@ -263,6 +263,18 @@ void testGetAll() { Assert.assertEquals(actualItems, pageSupplier.allItems, "The results should match all the pages."); } + // test getRows stream + @Test + void testGetRows() { + int pageSize = 11; + PageSupplier pageSupplier = PaginationTestHelpers.newBasePageSupplier(71, pageSize); + MockPagerClient c = new MockPagerClient(pageSupplier); + TestPager pager = new TestPager(c, getDefaultTestOptions(pageSize)); + pager.getRows().forEachOrdered(i -> + Assert.assertEquals(i, pageSupplier.allItems.remove(0), "The row should be the expected one.") + ); + } + // test next @Test void testNextFirstPage() { From 028cd05acd8edb8f97422375a70c2c012b8a6cb3 Mon Sep 17 00:00:00 2001 From: Rich Ellis Date: Thu, 13 Mar 2025 14:55:41 +0000 Subject: [PATCH 18/43] test: add tests for getNext & getAll exceptions --- .../features/pagination/BasePagerTest.java | 83 +++++++++++++++++-- 1 file changed, 77 insertions(+), 6 deletions(-) diff --git a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BasePagerTest.java b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BasePagerTest.java index d449b9acd..f10d24d8d 100644 --- a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BasePagerTest.java +++ b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BasePagerTest.java @@ -34,6 +34,7 @@ import static com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.getDefaultTestOptions; import static com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.getRequiredTestOptionsBuilder; +import static com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.newPageSupplierFromList; public class BasePagerTest { @@ -43,7 +44,7 @@ public class BasePagerTest { * This test sub-class of BasePager implicitly tests that various abstract methods are correctly * called by non-abstract methods in the BasePager. */ - class TestPager extends BasePager { + private static class TestPager extends BasePager { protected TestPager(Cloudant client, PostViewOptions options) { super(client, options); @@ -73,7 +74,7 @@ protected Function> itemsGetter() { } /** - * These tests don't actually use the options, but we set a key + * These tests don't actually use the options, but we set a startKey * so we can validate calls to setNextPageOptions. */ @Override @@ -83,7 +84,7 @@ protected void setNextPageOptions(Builder builder, TestResult result) { throw new IllegalStateException("Test failure: tried to setNextPageOptions on empty page."); } else { Integer i = rows.get(rows.size() - 1); - builder.key(i); + builder.startKey(i); } } @@ -110,6 +111,39 @@ BiFunction nextRequest() { + callCounter++; + switch(callCounter) { + case 2: + throw new RuntimeException("Test issue with request"); + default: + return super.nextRequest(); + } + } + + @Override + BiFunction> getConstructor() { + ctorCounter++; + // Use the error version only initially, afterwards return a non-throwing version + if (ctorCounter >= 2) { + return TestPager::new; + } + return ThrowingTestPager::new; + } + +} + // test constructor @Test void testConstructor() { @@ -336,19 +370,56 @@ void testSetNextPageOptions() { PageSupplier pageSupplier = PaginationTestHelpers.newBasePageSupplier(5*pageSize, pageSize); MockPagerClient c = new MockPagerClient(pageSupplier); TestPager pager = new TestPager(c, getDefaultTestOptions(pageSize)); - Assert.assertNull(pager.nextPageOptionsRef.get().key(), "key should initially be null."); + Assert.assertNull(pager.nextPageOptionsRef.get().startKey(), "startKey should initially be null."); // Since we use a page size of 1, each next page options key, is the same as the element from the page int page = 0; while(pager.hasNext()) { pager.getNext(); if (pager.hasNext()) { - Assert.assertEquals(pager.nextPageOptionsRef.get().key(), page, "The key should increment per page."); + Assert.assertEquals(pager.nextPageOptionsRef.get().startKey(), page, "the startKey should increment per page."); } else { // Options don't change for last page - Assert.assertEquals(pager.nextPageOptionsRef.get().key(), page - 1, "The options should not be set for the final page."); + Assert.assertEquals(pager.nextPageOptionsRef.get().startKey(), page - 1, "The options should not be set for the final page."); } page++; } } + @Test + void testGetNextResumesAfterError() { + int pageSize = 3; + PageSupplier pageSupplier = PaginationTestHelpers.newBasePageSupplier(2*pageSize, pageSize); + MockPagerClient c = new MockPagerClient(pageSupplier); + TestPager pager = new ThrowingTestPager(c, getDefaultTestOptions(pageSize)); + List actualPage1 = pager.getNext(); + // Assert pages + Assert.assertEquals(actualPage1, pageSupplier.pages.get(0), "The actual page should match the expected page."); + // The startKey should point to row 2 (the last row we saw, note this is not doing n+1 paging) + Assert.assertEquals(pager.nextPageOptionsRef.get().startKey(), 2, "The startKey should be 2 for the second page."); + Assert.assertThrows(RuntimeException.class,() -> pager.getNext()); + // Assert hasNext + Assert.assertEquals(pager.hasNext(), true, "hasNext() should return true."); + // The startKey should still point to the second page + Assert.assertEquals(pager.nextPageOptionsRef.get().startKey(), 2, "The startKey should be 2 for the second page."); + List actualPage2 = pager.getNext(); + Assert.assertEquals(actualPage2, pageSupplier.pages.get(1), "The actual page should match the expected page."); + } + + @Test + void testGetAllRestartsAfterError() { + int pageSize = 1; + // Set up a supplier to do first page, [error], first page, second page, empty page + PageSupplier pageSupplier = newPageSupplierFromList(List.of( + List.of(1), // first page + List.of(1), // error, followed by first page replay + List.of(2), // second page + Collections.emptyList())); // final empty page + MockPagerClient c = new MockPagerClient(pageSupplier); + TestPager pager = new ThrowingTestPager(c, getDefaultTestOptions(pageSize)); + Assert.assertThrows(RuntimeException.class, () -> pager.getAll()); + // Assert no startKey set + Assert.assertNull(pager.nextPageOptionsRef.get().startKey(), "startKey should initially be null."); + Assert.assertEquals(pager.getAll(), List.of(1, 2), "The results should match all the pages."); + } + } From 4b922fbb77e0eefe4fc238a2497141bcb7798adf Mon Sep 17 00:00:00 2001 From: Rich Ellis Date: Thu, 13 Mar 2025 14:56:20 +0000 Subject: [PATCH 19/43] fix: ensure getAll/getRows uses new iterator --- .../ibm/cloud/cloudant/features/pagination/BasePager.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BasePager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BasePager.java index 11a95bcbc..882f85cec 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BasePager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BasePager.java @@ -75,7 +75,7 @@ public final List getAll() { @Override public final Stream getRows() { - return StreamSupport.stream(this.wrapIteratorAsSpliterator(this), false) + return StreamSupport.stream(this.spliterator(), false) .flatMap(List::stream); } @@ -126,11 +126,7 @@ public Iterator> iterator() { @Override public Spliterator> spliterator() { - return wrapIteratorAsSpliterator(this.iterator()); - } - - private Spliterator> wrapIteratorAsSpliterator(Iterator> iterator) { - return Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED | Spliterator.NONNULL | Spliterator.IMMUTABLE); + return Spliterators.spliteratorUnknownSize(this.iterator(), Spliterator.ORDERED | Spliterator.NONNULL | Spliterator.IMMUTABLE); } } From 78f3f8bee6bff48127f8f7899a786472757762c0 Mon Sep 17 00:00:00 2001 From: Rich Ellis Date: Fri, 14 Mar 2025 15:02:02 +0000 Subject: [PATCH 20/43] test: fix KeyPager options builder --- .../ibm/cloud/cloudant/features/pagination/KeyPagerTest.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/KeyPagerTest.java b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/KeyPagerTest.java index 659d112eb..3517dcc36 100644 --- a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/KeyPagerTest.java +++ b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/KeyPagerTest.java @@ -88,9 +88,7 @@ protected Function nextKeyIdGetter() { @Override protected Function optionsToBuilderFunction() { - return o -> { - return new Builder(o.db(), o.ddoc(), o.view()); - }; + return PostViewOptions::newBuilder; } @Override From 63bb5a6d9397b50da4b0821242992b966d323e6e Mon Sep 17 00:00:00 2001 From: Rich Ellis Date: Mon, 17 Mar 2025 09:46:25 +0000 Subject: [PATCH 21/43] test: add bookmark pager tests --- .../pagination/BookmarkPagerTest.java | 196 ++++++++++++++++++ .../pagination/PaginationTestHelpers.java | 19 +- 2 files changed, 212 insertions(+), 3 deletions(-) create mode 100644 modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BookmarkPagerTest.java diff --git a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BookmarkPagerTest.java b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BookmarkPagerTest.java new file mode 100644 index 000000000..18da3065d --- /dev/null +++ b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BookmarkPagerTest.java @@ -0,0 +1,196 @@ +/** + * © Copyright IBM Corporation 2025. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.ibm.cloud.cloudant.features.pagination; + +import static com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.getDefaultTestFindOptions; +import static com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.getRequiredTestFindOptionsBuilder; +import static com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.newBasePageSupplier; +import java.util.List; +import java.util.function.BiFunction; +import java.util.function.Function; +import org.testng.Assert; +import org.testng.annotations.Test; +import com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.MockPagerClient; +import com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.PageSupplier; +import com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.TestResult; +import com.ibm.cloud.cloudant.v1.Cloudant; +import com.ibm.cloud.cloudant.v1.model.PostFindOptions; +import com.ibm.cloud.cloudant.v1.model.PostFindOptions.Builder; +import com.ibm.cloud.sdk.core.http.ServiceCall; + +public class BookmarkPagerTest { + + private Cloudant mockClient = new MockPagerClient(null); + + /** + * This test sub-class of BookmarkPager implicitly tests that various abstract methods are correctly + * called. + */ + class TestBookmarkPager extends BookmarkPager { + + protected TestBookmarkPager(Cloudant client, PostFindOptions options) { + super(client, options); + } + + Cloudant getClient() { + return this.client; + } + + /** + * Delegates to our next mock. + * If the BasePager didn't correctly call this the mocks wouldn't work. + */ + @Override + protected BiFunction> nextRequestFunction() { + return (c, o) -> { + return ((MockPagerClient) c).testCall(); + }; + } + + @Override + protected Function limitGetter() { + return PostFindOptions::limit; + } + + @Override + protected Function optionsToBuilderFunction() { + return PostFindOptions::newBuilder; + } + + @Override + protected Function builderToOptionsFunction() { + return Builder::build; + } + + @Override + protected Function> itemsGetter() { + return TestResult::getRows; + } + + @Override + BiFunction> getConstructor() { + return TestBookmarkPager::new; + } + + @Override + Function bookmarkGetter() { + // Use the last row value as the bookmark + return result -> { + List rows = result.getRows(); + return String.valueOf(rows.get(rows.size() - 1)); + }; + } + + @Override + BiFunction bookmarkSetter() { + return Builder::bookmark; + } + + } + + // Test page size default + @Test + void testDefaultPageSize() { + TestBookmarkPager pager = new TestBookmarkPager(mockClient, getRequiredTestFindOptionsBuilder().build()); + // Assert the limit provided as page size + Assert.assertEquals(pager.pageSize, 20, "The page size should be the default limit."); + } + + // Test page size limit + @Test + void testLimitPageSize() { + TestBookmarkPager pager = new TestBookmarkPager(mockClient, getDefaultTestFindOptions(42)); + // Assert the limit provided as page size + Assert.assertEquals(pager.pageSize, 42, "The page size should be the supplied limit."); + } + + // Test all items on page when no more pages + @Test + void testGetNextPageLessThanLimit() { + int pageSize = 21; + PageSupplier pageSupplier = newBasePageSupplier(pageSize - 1, pageSize); + MockPagerClient c = new MockPagerClient(pageSupplier); + TestBookmarkPager pager = new TestBookmarkPager(c, getDefaultTestFindOptions(pageSize)); + List actualPage = pager.getNext(); + // Assert first page + Assert.assertEquals(actualPage, pageSupplier.pages.get(0), "The actual page should match the expected page."); + // Assert size + Assert.assertEquals(actualPage.size(), pageSize - 1, "The actual page size should match the expected page size."); + // Assert hasNext false + Assert.assertFalse(pager.hasNext(), "hasNext() should return false."); + } + + // Test correct items on page when limit + @Test + void testGetNextPageEqualToLimit() { + int pageSize = 14; + PageSupplier pageSupplier = newBasePageSupplier(pageSize, pageSize); + MockPagerClient c = new MockPagerClient(pageSupplier); + TestBookmarkPager pager = new TestBookmarkPager(c, getDefaultTestFindOptions(pageSize)); + List actualPage = pager.getNext(); + // Assert first page + Assert.assertEquals(actualPage, pageSupplier.pages.get(0), "The actual page should match the expected page."); + // Assert size is pageSize + Assert.assertEquals(actualPage.size(), pageSize, "The actual page size should match the expected page size."); + // Assert hasNext true + Assert.assertTrue(pager.hasNext(), "hasNext() should return true."); + // Assert bookmark is correct, note the result rows start at zero, so pageSize - 1, not pageSize + Assert.assertEquals(pager.nextPageOptionsRef.get().bookmark(), String.valueOf(pageSize - 1), "The bookmark should be one less than the page size."); + // Get next page + List actualSecondPage = pager.getNext(); + // Assert second page is empty + Assert.assertEquals(actualSecondPage.size(), 0, "The second page should be empty."); + // Assert hasNext false + Assert.assertFalse(pager.hasNext(), "hasNext() should return false."); + } + + // Test correct items on pages when more than limit + @Test + void testGetNextPageGreaterThanLimit() { + int pageSize = 7; + PageSupplier pageSupplier = newBasePageSupplier(pageSize+2, pageSize); + MockPagerClient c = new MockPagerClient(pageSupplier); + TestBookmarkPager pager = new TestBookmarkPager(c, getDefaultTestFindOptions(pageSize)); + List actualPage = pager.getNext(); + // Assert first page + Assert.assertEquals(actualPage, pageSupplier.pages.get(0), "The actual page should match the expected page."); + // Assert size is pageSize + Assert.assertEquals(actualPage.size(), pageSize, "The actual page size should match the expected page size."); + // Assert hasNext true + Assert.assertTrue(pager.hasNext(), "hasNext() should return true."); + // Assert bookmark is correct, note the result rows start at zero, so pageSize - 1 + Assert.assertEquals(pager.nextPageOptionsRef.get().bookmark(), String.valueOf(pageSize - 1), "The bookmark should be one less than the page size."); + // Get next page + List actualSecondPage = pager.getNext(); + // Assert first item on second page is correct + Assert.assertEquals(actualSecondPage.get(0), pageSize, "The first item on the second page should be as expected."); + // Assert size is 2 + Assert.assertEquals(actualSecondPage.size(), 2, "The actual page size should match the expected page size."); + // Assert second page + Assert.assertEquals(actualSecondPage, pageSupplier.pages.get(1), "The actual page should match the expected page."); + // Assert hasNext false + Assert.assertFalse(pager.hasNext(), "hasNext() should return false."); + } + + // Test getting all items + @Test + void testGetAll() { + int pageSize = 3; + PageSupplier pageSupplier = newBasePageSupplier(pageSize*12, pageSize); + MockPagerClient c = new MockPagerClient(pageSupplier); + TestBookmarkPager pager = new TestBookmarkPager(c, getDefaultTestFindOptions(pageSize)); + List actualItems = pager.getAll(); + Assert.assertEquals(actualItems, pageSupplier.allItems, "The results should match all the pages."); + } +} diff --git a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/PaginationTestHelpers.java b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/PaginationTestHelpers.java index 49c729269..3b363718f 100644 --- a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/PaginationTestHelpers.java +++ b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/PaginationTestHelpers.java @@ -14,6 +14,7 @@ package com.ibm.cloud.cloudant.features.pagination; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.function.Function; import java.util.function.Supplier; @@ -21,8 +22,8 @@ import com.ibm.cloud.cloudant.features.MockCloudant; import com.ibm.cloud.cloudant.features.MockCloudant.MockInstruction; import com.ibm.cloud.cloudant.features.MockCloudant.QueuedSupplier; +import com.ibm.cloud.cloudant.v1.model.PostFindOptions; import com.ibm.cloud.cloudant.v1.model.PostViewOptions; -import com.ibm.cloud.cloudant.v1.model.PostViewOptions.Builder; import com.ibm.cloud.cloudant.v1.model.ViewResult; import com.ibm.cloud.cloudant.v1.model.ViewResultRow; import com.ibm.cloud.sdk.core.http.ServiceCall; @@ -158,11 +159,23 @@ static PostViewOptions getDefaultTestOptions(int limit) { .build(); } - static Builder getRequiredTestOptionsBuilder() { - return new Builder() + static PostViewOptions.Builder getRequiredTestOptionsBuilder() { + return new PostViewOptions.Builder() .db("example-database") .ddoc("test-ddoc") .view("test-view"); } + static PostFindOptions getDefaultTestFindOptions(int limit) { + return getRequiredTestFindOptionsBuilder() + .limit(limit) + .build(); + } + + static PostFindOptions.Builder getRequiredTestFindOptionsBuilder() { + return new PostFindOptions.Builder() + .db("example-database") + .selector(Collections.singletonMap("testField", "testValue")); + } + } From 8f78a8727a46d64b3f03074c1486cf816b7b2ab9 Mon Sep 17 00:00:00 2001 From: Rich Ellis Date: Mon, 17 Mar 2025 10:12:04 +0000 Subject: [PATCH 22/43] feat: update default page size to 200 --- .../com/ibm/cloud/cloudant/features/pagination/BasePager.java | 2 +- .../ibm/cloud/cloudant/features/pagination/BasePagerTest.java | 2 +- .../cloud/cloudant/features/pagination/BookmarkPagerTest.java | 2 +- .../ibm/cloud/cloudant/features/pagination/KeyPagerTest.java | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BasePager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BasePager.java index 882f85cec..7f8597590 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BasePager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BasePager.java @@ -110,7 +110,7 @@ private void buildAndSetOptions(B optionsBuilder) { abstract Function limitGetter(); Long getPageSizeFromOptionsLimit(O opts) { - return Optional.ofNullable(limitGetter().apply(opts)).orElse(20L); + return Optional.ofNullable(limitGetter().apply(opts)).orElse(200L); } private BasePager newInstance() { diff --git a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BasePagerTest.java b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BasePagerTest.java index f10d24d8d..921fbab6c 100644 --- a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BasePagerTest.java +++ b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BasePagerTest.java @@ -157,7 +157,7 @@ void testConstructor() { void testDefaultPageSize() { TestPager pager = new TestPager(mockClient, getRequiredTestOptionsBuilder().build()); // Assert the default page size - Assert.assertEquals(pager.pageSize, 20, "The page size should be the default."); + Assert.assertEquals(pager.pageSize, 200, "The page size should be the default."); } // test page size limit diff --git a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BookmarkPagerTest.java b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BookmarkPagerTest.java index 18da3065d..3fb4c9e31 100644 --- a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BookmarkPagerTest.java +++ b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BookmarkPagerTest.java @@ -104,7 +104,7 @@ BiFunction bookmarkSetter() { void testDefaultPageSize() { TestBookmarkPager pager = new TestBookmarkPager(mockClient, getRequiredTestFindOptionsBuilder().build()); // Assert the limit provided as page size - Assert.assertEquals(pager.pageSize, 20, "The page size should be the default limit."); + Assert.assertEquals(pager.pageSize, 200, "The page size should be the default limit."); } // Test page size limit diff --git a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/KeyPagerTest.java b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/KeyPagerTest.java index 3517dcc36..78b215a62 100644 --- a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/KeyPagerTest.java +++ b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/KeyPagerTest.java @@ -118,7 +118,7 @@ Optional checkBoundary(Integer penultimateItem, Integer lastItem) { void testDefaultPageSize() { TestKeyPager pager = new TestKeyPager(mockClient, getRequiredTestOptionsBuilder().build()); // Assert the limit provided as page size - Assert.assertEquals(pager.pageSize, 21, "The page size should be one more than the default limit."); + Assert.assertEquals(pager.pageSize, 201, "The page size should be one more than the default limit."); } // Test page size limit (+1) From 9cd45281e2052a42e4204bab10be3eeae0380d3e Mon Sep 17 00:00:00 2001 From: Rich Ellis Date: Wed, 19 Mar 2025 17:20:51 +0000 Subject: [PATCH 23/43] test: fix incorrect comment --- .../ibm/cloud/cloudant/features/pagination/KeyPagerTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/KeyPagerTest.java b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/KeyPagerTest.java index 78b215a62..f9e3d1988 100644 --- a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/KeyPagerTest.java +++ b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/KeyPagerTest.java @@ -117,7 +117,7 @@ Optional checkBoundary(Integer penultimateItem, Integer lastItem) { @Test void testDefaultPageSize() { TestKeyPager pager = new TestKeyPager(mockClient, getRequiredTestOptionsBuilder().build()); - // Assert the limit provided as page size + // Assert the default limit as page size Assert.assertEquals(pager.pageSize, 201, "The page size should be one more than the default limit."); } From dd91548965b856bf023ca05033d15b6c0402392a Mon Sep 17 00:00:00 2001 From: Rich Ellis Date: Wed, 19 Mar 2025 17:20:37 +0000 Subject: [PATCH 24/43] refactor: factory API --- .../features/pagination/AllDocsBasePager.java | 4 +- .../features/pagination/AllDocsPager.java | 8 +- .../pagination/AllDocsPartitionPager.java | 9 +- .../features/pagination/BasePager.java | 58 +-- .../features/pagination/BookmarkPager.java | 4 +- .../features/pagination/DesignDocsPager.java | 8 +- .../features/pagination/FindBasePager.java | 4 +- .../features/pagination/FindPager.java | 8 +- .../pagination/FindPartitionPager.java | 8 +- .../features/pagination/IteratorPager.java | 77 +++ .../features/pagination/KeyPager.java | 4 +- .../features/pagination/OptionsHandler.java | 113 ++++ .../cloudant/features/pagination/Pager.java | 120 +---- .../features/pagination/Pagination.java | 273 ++++++++++ .../features/pagination/SearchBasePager.java | 4 +- .../features/pagination/SearchPager.java | 8 +- .../pagination/SearchPartitionPager.java | 8 +- .../features/pagination/ViewBasePager.java | 4 +- .../features/pagination/ViewPager.java | 8 +- .../pagination/ViewPartitionPager.java | 8 +- .../features/pagination/package-info.java | 4 +- .../features/pagination/BasePagerTest.java | 485 ++++++++++++------ .../pagination/BookmarkPagerTest.java | 91 ++-- .../features/pagination/KeyPagerTest.java | 146 +++--- .../pagination/PaginationTestHelpers.java | 62 +-- 25 files changed, 978 insertions(+), 548 deletions(-) create mode 100644 modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/IteratorPager.java create mode 100644 modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/OptionsHandler.java create mode 100644 modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/Pagination.java diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsBasePager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsBasePager.java index 7dd775cdf..b580ebb17 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsBasePager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsBasePager.java @@ -23,8 +23,8 @@ abstract class AllDocsBasePager extends KeyPager { - AllDocsBasePager(Cloudant client, O options) { - super(client, options); + AllDocsBasePager(Cloudant client, O options, OptionsHandler optsHandler) { + super(client, options, optsHandler); } @Override diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsPager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsPager.java index 63dc30720..b6af8fa2c 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsPager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsPager.java @@ -17,7 +17,6 @@ import java.util.function.Function; import com.ibm.cloud.cloudant.v1.Cloudant; import com.ibm.cloud.cloudant.v1.model.AllDocsResult; -import com.ibm.cloud.cloudant.v1.model.DocsResultRow; import com.ibm.cloud.cloudant.v1.model.PostAllDocsOptions; import com.ibm.cloud.cloudant.v1.model.PostAllDocsOptions.Builder; import com.ibm.cloud.sdk.core.http.ServiceCall; @@ -25,7 +24,7 @@ final class AllDocsPager extends AllDocsBasePager { AllDocsPager(Cloudant client, PostAllDocsOptions options) { - super(client, options); + super(client, options, OptionsHandler.POST_ALL_DOCS); } @Override @@ -53,9 +52,4 @@ Function limitGetter() { return PostAllDocsOptions::limit; } - @Override - BiFunction> getConstructor() { - return AllDocsPager::new; - } - } diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsPartitionPager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsPartitionPager.java index 370e529f6..52c08ccdf 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsPartitionPager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsPartitionPager.java @@ -17,7 +17,6 @@ import java.util.function.Function; import com.ibm.cloud.cloudant.v1.Cloudant; import com.ibm.cloud.cloudant.v1.model.AllDocsResult; -import com.ibm.cloud.cloudant.v1.model.DocsResultRow; import com.ibm.cloud.cloudant.v1.model.PostPartitionAllDocsOptions; import com.ibm.cloud.cloudant.v1.model.PostPartitionAllDocsOptions.Builder; import com.ibm.cloud.sdk.core.http.ServiceCall; @@ -25,8 +24,7 @@ final class AllDocsPartitionPager extends AllDocsBasePager { AllDocsPartitionPager(Cloudant client, PostPartitionAllDocsOptions options) { - super(client, options); - //TODO Auto-generated constructor stub + super(client, options, OptionsHandler.POST_PARTITION_ALL_DOCS); } @Override @@ -55,9 +53,4 @@ Function limitGetter() { return PostPartitionAllDocsOptions::limit; } - @Override - BiFunction> getConstructor() { - return AllDocsPartitionPager::new; - } - } diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BasePager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BasePager.java index 7f8597590..c0a067860 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BasePager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BasePager.java @@ -18,35 +18,27 @@ import java.util.List; import java.util.NoSuchElementException; import java.util.Optional; -import java.util.Spliterator; -import java.util.Spliterators; import java.util.concurrent.atomic.AtomicReference; import java.util.function.BiFunction; import java.util.function.Function; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; import com.ibm.cloud.cloudant.v1.Cloudant; import com.ibm.cloud.sdk.core.http.ServiceCall; -abstract class BasePager implements Pager, Iterator> { +abstract class BasePager implements Iterator> { protected final Cloudant client; - protected final O initialOptions; protected final long pageSize; + protected final OptionsHandler optsHandler; protected final AtomicReference nextPageOptionsRef = new AtomicReference<>(); protected volatile boolean hasNext = true; - protected BasePager(Cloudant client, O options) { + BasePager(Cloudant client, O options, OptionsHandler optsHandler) { this.client = client; - // TODO validate initial options - // Clone user provided options into initial options - this.initialOptions = this.builderToOptionsFunction() - .apply(this.optionsToBuilderFunction().apply(options)); + this.optsHandler = optsHandler; // Set the page size from the supplied options limit this.pageSize = getPageSizeFromOptionsLimit(options); // Set the first page options - this.buildAndSetOptions(this.optionsToBuilderFunction().apply(options)); + buildAndSetOptions(optsHandler.builderFromOptions(options)); } @Override @@ -54,11 +46,6 @@ public final boolean hasNext() { return this.hasNext; } - @Override - public List getNext() { - return this.next(); - } - @Override public List next() { if (this.hasNext()) { @@ -68,33 +55,22 @@ public List next() { } } - @Override - public final List getAll() { - return getRows().collect(Collectors.toList()); - } - - @Override - public final Stream getRows() { - return StreamSupport.stream(this.spliterator(), false) - .flatMap(List::stream); - } - List nextRequest() { - ServiceCall request = nextRequestFunction().apply(this.client, nextPageOptionsRef.get()); + ServiceCall request = nextRequestFunction().apply(this.client, this.nextPageOptionsRef.get()); R result = request.execute().getResult(); List items = itemsGetter().apply(result); if (items.size() < this.pageSize) { this.hasNext = false; } else { - B optionsBuilder = optionsToBuilderFunction().apply(nextPageOptionsRef.get()); + B optionsBuilder = optsHandler.builderFromOptions(this.nextPageOptionsRef.get()); setNextPageOptions(optionsBuilder, result); - this.buildAndSetOptions(optionsBuilder); + buildAndSetOptions(optionsBuilder); } return items; } private void buildAndSetOptions(B optionsBuilder) { - this.nextPageOptionsRef.set(this.builderToOptionsFunction().apply(optionsBuilder)); + this.nextPageOptionsRef.set(optsHandler.optionsFromBuilder(optionsBuilder)); } abstract Function optionsToBuilderFunction(); @@ -113,20 +89,4 @@ Long getPageSizeFromOptionsLimit(O opts) { return Optional.ofNullable(limitGetter().apply(opts)).orElse(200L); } - private BasePager newInstance() { - return this.getConstructor().apply(this.client, this.initialOptions); - } - - abstract BiFunction> getConstructor(); - - @Override - public Iterator> iterator() { - return this.newInstance(); - } - - @Override - public Spliterator> spliterator() { - return Spliterators.spliteratorUnknownSize(this.iterator(), Spliterator.ORDERED | Spliterator.NONNULL | Spliterator.IMMUTABLE); - } - } diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BookmarkPager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BookmarkPager.java index c85b2331e..e8eb4936a 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BookmarkPager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BookmarkPager.java @@ -19,8 +19,8 @@ abstract class BookmarkPager extends BasePager { - BookmarkPager(Cloudant client, O options) { - super(client, options); + BookmarkPager(Cloudant client, O options, OptionsHandler optsHandler) { + super(client, options, optsHandler); } abstract Function bookmarkGetter(); diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/DesignDocsPager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/DesignDocsPager.java index 20cda2a82..c2a9c3948 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/DesignDocsPager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/DesignDocsPager.java @@ -17,7 +17,6 @@ import java.util.function.Function; import com.ibm.cloud.cloudant.v1.Cloudant; import com.ibm.cloud.cloudant.v1.model.AllDocsResult; -import com.ibm.cloud.cloudant.v1.model.DocsResultRow; import com.ibm.cloud.cloudant.v1.model.PostDesignDocsOptions; import com.ibm.cloud.cloudant.v1.model.PostDesignDocsOptions.Builder; import com.ibm.cloud.sdk.core.http.ServiceCall; @@ -25,7 +24,7 @@ public class DesignDocsPager extends AllDocsBasePager { DesignDocsPager(Cloudant client, PostDesignDocsOptions options) { - super(client, options); + super(client, options, OptionsHandler.POST_DESIGN_DOCS); } @Override @@ -53,9 +52,4 @@ Function limitGetter() { return PostDesignDocsOptions::limit; } - @Override - BiFunction> getConstructor() { - return DesignDocsPager::new; - } - } diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindBasePager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindBasePager.java index 04928ee11..6b2fdecbe 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindBasePager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindBasePager.java @@ -21,8 +21,8 @@ abstract class FindBasePager extends BookmarkPager { - FindBasePager(Cloudant client, O options) { - super(client, options); + FindBasePager(Cloudant client, O options, OptionsHandler optsHandler) { + super(client, options, optsHandler); } @Override diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindPager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindPager.java index 7bacf08fe..143b917dc 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindPager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindPager.java @@ -16,7 +16,6 @@ import java.util.function.BiFunction; import java.util.function.Function; import com.ibm.cloud.cloudant.v1.Cloudant; -import com.ibm.cloud.cloudant.v1.model.Document; import com.ibm.cloud.cloudant.v1.model.FindResult; import com.ibm.cloud.cloudant.v1.model.PostFindOptions; import com.ibm.cloud.cloudant.v1.model.PostFindOptions.Builder; @@ -25,7 +24,7 @@ final class FindPager extends FindBasePager { FindPager(Cloudant client, PostFindOptions options) { - super(client, options); + super(client, options, OptionsHandler.POST_FIND); } @Override @@ -53,9 +52,4 @@ Function limitGetter() { return PostFindOptions::limit; } - @Override - BiFunction> getConstructor() { - return FindPager::new; - } - } diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindPartitionPager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindPartitionPager.java index 13f969b71..4d69aaf40 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindPartitionPager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindPartitionPager.java @@ -16,7 +16,6 @@ import java.util.function.BiFunction; import java.util.function.Function; import com.ibm.cloud.cloudant.v1.Cloudant; -import com.ibm.cloud.cloudant.v1.model.Document; import com.ibm.cloud.cloudant.v1.model.FindResult; import com.ibm.cloud.cloudant.v1.model.PostPartitionFindOptions; import com.ibm.cloud.cloudant.v1.model.PostPartitionFindOptions.Builder; @@ -25,7 +24,7 @@ final class FindPartitionPager extends FindBasePager { FindPartitionPager(Cloudant client, PostPartitionFindOptions options) { - super(client, options); + super(client, options, OptionsHandler.POST_PARTITION_FIND); } @Override @@ -53,9 +52,4 @@ Function limitGetter() { return PostPartitionFindOptions::limit; } - @Override - BiFunction> getConstructor() { - return FindPartitionPager::new; - } - } diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/IteratorPager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/IteratorPager.java new file mode 100644 index 000000000..71f7f5146 --- /dev/null +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/IteratorPager.java @@ -0,0 +1,77 @@ +/** + * © Copyright IBM Corporation 2025. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + + +package com.ibm.cloud.cloudant.features.pagination; + +import java.util.Iterator; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +final class IteratorPager implements Pager { + + private State state = State.NEW; + private final Iterable> pageIterable; + private final Iterator> pageIterator; + + enum State { + NEW, GET_NEXT, GET_ALL, CONSUMED + } + + IteratorPager(Iterable> pageIterable) { + this.pageIterable = pageIterable; + this.pageIterator = pageIterable.iterator(); + } + + @Override + public boolean hasNext() { + return this.pageIterator.hasNext(); + } + + @Override + public List getNext() { + checkState(State.GET_NEXT); + List page = this.pageIterator.next(); + if (!this.hasNext()) { + state = State.CONSUMED; + } + return page; + } + + @Override + public List getAll() { + checkState(State.GET_ALL); + List allRows = StreamSupport.stream(this.pageIterable.spliterator(), false) + .flatMap(List::stream).collect(Collectors.toList()); + // If it didn't throw we can set the consumed state + state = State.CONSUMED; + return allRows; + } + + private void checkState(State mode) { + if (state == mode) { + return; + } + switch (state) { + case NEW: + state = mode; + break; + case CONSUMED: + throw new IllegalStateException("This pager has been consumed use a new Pager."); + default: + throw new IllegalStateException( + "Cannot mix getAll() and getNext() use only one method or get a a new Pager."); + } + } +} diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/KeyPager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/KeyPager.java index e78c6f942..04d5c0305 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/KeyPager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/KeyPager.java @@ -23,8 +23,8 @@ abstract class KeyPager extends BasePager { private Optional boundaryFailure = Optional.empty(); - KeyPager(Cloudant client, O options) { - super(client, options); + KeyPager(Cloudant client, O options, OptionsHandler optsHandler) { + super(client, options, optsHandler); } abstract BiFunction nextKeySetter(); diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/OptionsHandler.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/OptionsHandler.java new file mode 100644 index 000000000..6c0c5608c --- /dev/null +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/OptionsHandler.java @@ -0,0 +1,113 @@ +/** + * © Copyright IBM Corporation 2025. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + + +package com.ibm.cloud.cloudant.features.pagination; + +import java.util.function.Function; +import com.ibm.cloud.cloudant.v1.model.PostAllDocsOptions; +import com.ibm.cloud.cloudant.v1.model.PostDesignDocsOptions; +import com.ibm.cloud.cloudant.v1.model.PostFindOptions; +import com.ibm.cloud.cloudant.v1.model.PostPartitionAllDocsOptions; +import com.ibm.cloud.cloudant.v1.model.PostPartitionFindOptions; +import com.ibm.cloud.cloudant.v1.model.PostPartitionSearchOptions; +import com.ibm.cloud.cloudant.v1.model.PostPartitionViewOptions; +import com.ibm.cloud.cloudant.v1.model.PostSearchOptions; +import com.ibm.cloud.cloudant.v1.model.PostViewOptions; + +class OptionsHandler { + + static final OptionsHandler POST_ALL_DOCS = + new OptionsHandler<>(PostAllDocsOptions.Builder::build, PostAllDocsOptions::newBuilder); + static final OptionsHandler POST_DESIGN_DOCS = + new OptionsHandler<>(PostDesignDocsOptions.Builder::build, PostDesignDocsOptions::newBuilder); + static final OptionsHandler POST_FIND = + new OptionsHandler<>(PostFindOptions.Builder::build, PostFindOptions::newBuilder); + static final OptionsHandler POST_PARTITION_ALL_DOCS = + new OptionsHandler<>(PostPartitionAllDocsOptions.Builder::build, + PostPartitionAllDocsOptions::newBuilder); + static final OptionsHandler POST_PARTITION_FIND = + new OptionsHandler<>(PostPartitionFindOptions.Builder::build, + PostPartitionFindOptions::newBuilder); + static final OptionsHandler POST_PARTITION_SEARCH = + new OptionsHandler<>(PostPartitionSearchOptions.Builder::build, + PostPartitionSearchOptions::newBuilder); + static final OptionsHandler POST_PARTITION_VIEW = + new OptionsHandler<>(PostPartitionViewOptions.Builder::build, + PostPartitionViewOptions::newBuilder); + static final OptionsHandler POST_SEARCH = + new OptionsHandler<>(PostSearchOptions.Builder::build, PostSearchOptions::newBuilder); + static final OptionsHandler POST_VIEW = + new OptionsHandler<>(PostViewOptions.Builder::build, PostViewOptions::newBuilder); + + private final Function builderToOptions; + private final Function optionsToBuilder; + + private OptionsHandler(Function builderToOptions, Function optionsToBuilder) { + this.builderToOptions = builderToOptions; + this.optionsToBuilder = optionsToBuilder; + } + + B builderFromOptions(O options) { + return this.optionsToBuilder.apply(options); + } + + O optionsFromBuilder(B builder) { + return this.builderToOptions.apply(builder); + } + + void validate(O options) { + throw new UnsupportedOperationException("Not yet implemented."); + } + + O clone(O options) { + return this.optionsFromBuilder(this.builderFromOptions(options)); + } + + static final PostAllDocsOptions duplicate(PostAllDocsOptions opts) { + return POST_ALL_DOCS.clone(opts); + } + + static final PostDesignDocsOptions duplicate(PostDesignDocsOptions opts) { + return POST_DESIGN_DOCS.clone(opts); + } + + static final PostFindOptions duplicate(PostFindOptions opts) { + return POST_FIND.clone(opts); + } + + static final PostPartitionAllDocsOptions duplicate(PostPartitionAllDocsOptions opts) { + return POST_PARTITION_ALL_DOCS.clone(opts); + } + + static final PostPartitionFindOptions duplicate(PostPartitionFindOptions opts) { + return POST_PARTITION_FIND.clone(opts); + } + + static final PostPartitionSearchOptions duplicate(PostPartitionSearchOptions opts) { + return POST_PARTITION_SEARCH.clone(opts); + } + + static final PostPartitionViewOptions duplicate(PostPartitionViewOptions opts) { + return POST_PARTITION_VIEW.clone(opts); + } + + static final PostSearchOptions duplicate(PostSearchOptions opts) { + return POST_SEARCH.clone(opts); + } + + static final PostViewOptions duplicate(PostViewOptions opts) { + return POST_VIEW.clone(opts); + } + +} diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/Pager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/Pager.java index fa5b33fd9..48203c503 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/Pager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/Pager.java @@ -14,20 +14,6 @@ package com.ibm.cloud.cloudant.features.pagination; import java.util.List; -import java.util.stream.Stream; -import com.ibm.cloud.cloudant.v1.Cloudant; -import com.ibm.cloud.cloudant.v1.model.DocsResultRow; -import com.ibm.cloud.cloudant.v1.model.Document; -import com.ibm.cloud.cloudant.v1.model.PostAllDocsOptions; -import com.ibm.cloud.cloudant.v1.model.PostFindOptions; -import com.ibm.cloud.cloudant.v1.model.PostPartitionAllDocsOptions; -import com.ibm.cloud.cloudant.v1.model.PostPartitionFindOptions; -import com.ibm.cloud.cloudant.v1.model.PostPartitionSearchOptions; -import com.ibm.cloud.cloudant.v1.model.PostPartitionViewOptions; -import com.ibm.cloud.cloudant.v1.model.PostSearchOptions; -import com.ibm.cloud.cloudant.v1.model.PostViewOptions; -import com.ibm.cloud.cloudant.v1.model.SearchResultRow; -import com.ibm.cloud.cloudant.v1.model.ViewResultRow; /** * Interface for pagination of database operations. @@ -37,7 +23,7 @@ * * @param the item type of the page rows */ -public interface Pager extends Iterable> { +public interface Pager { /** * Returns {@code true} if there may be another page. @@ -63,108 +49,4 @@ public interface Pager extends Iterable> { */ List getAll(); - /** - * Stream all the rows from all the available pages one at - * a time. This operation is lazy and requests pages as - * needed. - * - * @return java.util.Stream of the rows from all the pages - */ - Stream getRows(); - - /** - * Get a Pager for the postAllDocs operation. - * The page size is configured with the limit paramater of the options. - * - * @param client com.ibm.cloud.cloudant.v1.Cloudant instance to make page requests - * @param options com.ibm.cloud.cloudant.v1.model.PostAllDocsOptions for the query - * @return a pager for all the documents in the database - */ - static Pager newPager(Cloudant client, PostAllDocsOptions options) { - throw new UnsupportedOperationException("Not yet implemented."); - } - - /** - * Get a Pager for the postPartitionAllDocs operation. - * The page size is configured with the limit paramater of the options. - * - * @param client com.ibm.cloud.cloudant.v1.Cloudant instance to make page requests - * @param options com.ibm.cloud.cloudant.v1.model.PostPartitionAllDocsOptions for the query - * @return a pager for all the documents in a database partition - */ - static Pager newPager(Cloudant client, PostPartitionAllDocsOptions options) { - throw new UnsupportedOperationException("Not yet implemented."); - } - - /** - * Get a Pager for the postFind operation. - * The page size is configured with the limit paramater of the options. - * - * @param client com.ibm.cloud.cloudant.v1.Cloudant instance to make page requests - * @param options com.ibm.cloud.cloudant.v1.model.PostFindOptions for the query - * @return a pager for the result of a find query - */ - static Pager newPager(Cloudant client, PostFindOptions options) { - throw new UnsupportedOperationException("Not yet implemented."); - } - - /** - * Get a Pager for the postPartitionFind operation. - * - * @param client com.ibm.cloud.cloudant.v1.Cloudant instance to make page requests - * @param options com.ibm.cloud.cloudant.v1.model.PostPartitionFindOptions for the query - * @return a pager for the result of a partition find query - */ - static Pager newPager(Cloudant client, PostPartitionFindOptions options) { - throw new UnsupportedOperationException("Not yet implemented."); - } - - /** - * Get a Pager for the postSearch operation. - * The page size is configured with the limit paramater of the options. - * - * @param client com.ibm.cloud.cloudant.v1.Cloudant instance to make page requests - * @param options com.ibm.cloud.cloudant.v1.model.PostSearchOptions for the query - * @return a pager for the result of a search query - */ - static Pager newPager(Cloudant client, PostSearchOptions options) { - throw new UnsupportedOperationException("Not yet implemented."); - } - - /** - * Get a Pager for the postPartitionSearch operation. - * The page size is configured with the limit paramater of the options. - * - * @param client com.ibm.cloud.cloudant.v1.Cloudant instance to make page requests - * @param options com.ibm.cloud.cloudant.v1.model.PostPartitionSearchOptions for the query - * @return a pager for the result of a partition search query - */ - static Pager newPager(Cloudant client, PostPartitionSearchOptions options) { - throw new UnsupportedOperationException("Not yet implemented."); - } - - /** - * Get a Pager for the postView operation. - * The page size is configured with the limit paramater of the options. - * - * @param client com.ibm.cloud.cloudant.v1.Cloudant instance to make page requests - * @param options com.ibm.cloud.cloudant.v1.model.PostViewOptions for the query - * @return a pager for the result of a view query - */ - static Pager newPager(Cloudant client, PostViewOptions options) { - throw new UnsupportedOperationException("Not yet implemented."); - } - - /** - * Get a Pager for the postPartitionView operation. - * The page size is configured with the limit paramater of the options. - * - * @param client com.ibm.cloud.cloudant.v1.Cloudant instance to make page requests - * @param options com.ibm.cloud.cloudant.v1.model.PostPartitionViewOptions for the query - * @return a pager for the result of a partition view query - */ - static Pager newPager(Cloudant client, PostPartitionViewOptions options) { - throw new UnsupportedOperationException("Not yet implemented."); - } - } diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/Pagination.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/Pagination.java new file mode 100644 index 000000000..7639dd850 --- /dev/null +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/Pagination.java @@ -0,0 +1,273 @@ +/** + * © Copyright IBM Corporation 2025. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + + +package com.ibm.cloud.cloudant.features.pagination; + +import java.util.Iterator; +import java.util.List; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.function.BiFunction; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; +import com.ibm.cloud.cloudant.v1.Cloudant; +import com.ibm.cloud.cloudant.v1.model.DocsResultRow; +import com.ibm.cloud.cloudant.v1.model.Document; +import com.ibm.cloud.cloudant.v1.model.PostAllDocsOptions; +import com.ibm.cloud.cloudant.v1.model.PostFindOptions; +import com.ibm.cloud.cloudant.v1.model.PostPartitionAllDocsOptions; +import com.ibm.cloud.cloudant.v1.model.PostPartitionFindOptions; +import com.ibm.cloud.cloudant.v1.model.PostPartitionSearchOptions; +import com.ibm.cloud.cloudant.v1.model.PostPartitionViewOptions; +import com.ibm.cloud.cloudant.v1.model.PostSearchOptions; +import com.ibm.cloud.cloudant.v1.model.PostViewOptions; +import com.ibm.cloud.cloudant.v1.model.SearchResultRow; +import com.ibm.cloud.cloudant.v1.model.ViewResultRow; + +/** + * {@link Pagination} is the entry point for pagination features. + * + * Use the static methods to create new {@link Pagination} instances. The instances in turn can be + * used to create: + *
    + *
  • {@link Stream} of result rows via {@link #rowStream()} + *
  • {@link Stream} of pages via {@link #pageStream()} + *
  • {@link Iterator}s of result rows via the {@link Iterable} {@link #rows()} + *
  • {@link Iterator}s of pages via the {@link Iterable}{@link #pages()} + *
  • IBM Cloud SDK style {@link Pager}s via {@link #pager()} + *
+ * + * @param the type of the options object used to configure the operation. + * @param the reslt row type of the operation. + */ +public class Pagination { + + private final Cloudant client; + private final O opts; + private final BiFunction> iteratorCtor; + private final Iterable> pageIterable = new PageIterable(); + private final Iterable rowIterable = new RowIterable(); + + Pagination(Cloudant client, O opts, BiFunction> iteratorCtor) { + this.client = client; + this.opts = opts; + this.iteratorCtor = iteratorCtor; + } + + private final class PageIterable implements Iterable> { + + /** + * Makes a new lazy {@link Iterator} over the operation result pages as defined by the options. + * + * @return new {@link Iterator} over the pages + */ + @Override + public Iterator> iterator() { + return Pagination.this.iteratorCtor.apply(Pagination.this.client, Pagination.this.opts); + } + + /** + * Makes a new lazy {@link Spliterator.ORDERED} {@link Spliterator.NONNULL} + * {@link Spliterator.IMMUTABLE} {@link Spliterator} over the operation result pages as defined + * by the options. + * + * @return new {@link Spliterator} over the pages + */ + @Override + public Spliterator> spliterator() { + return Spliterators.spliteratorUnknownSize(this.iterator(), + Spliterator.ORDERED | Spliterator.NONNULL | Spliterator.IMMUTABLE); + } + } + + private final class RowIterable implements Iterable { + + /** + * Makes a new lazy (page at a time) {@link Iterator} over the operation result rows from all + * the pages as defined by the options. + * + * @return new {@link Iterator} over the result rows + */ + @Override + public Iterator iterator() { + return Pagination.this.rowStream().iterator(); + } + } + + /** + * Get a new IBM Cloud SDK style Pager for the operation. + * + * This type is useful for retrieving one page at a time through a function call. + * + * @return a new IBM Cloud SDK style Pager + */ + public Pager pager() { + return new IteratorPager(this.pages()); + } + + /** + * Get an Iterable for all the pages. + * + * This method is useful for handling pages in an enhanced for loop e.g. {@code + * for (List page : Pagination.newPagination(client, allDocsOptions).pages()) { + * } } + * + * @return an {@link Iterable} over all the pages + */ + public Iterable> pages() { + return this.pageIterable; + } + + /** + * Get a page by page stream of all the pages. + * + * @return a {@link Stream} of all the pages + */ + public Stream> pageStream() { + return StreamSupport.stream(this.pages().spliterator(), false); + } + + /** + * Get an Iterable for all the rows from all the pages. + * + * This type is useful for handling rows in an enhanced for loop e.g. {@code + * for (AllDocsResultRow row : Pagination.newPagination(client, allDocsOptions).rows()) { } } + * + * @return an {@link Iterable} over all the result rows + */ + public Iterable rows() { + return this.rowIterable; + } + + /** + * Get a row by row stream of all the rows from all the pages. + * + * @return a {@link Stream} of all the result rows + */ + public Stream rowStream() { + return this.pageStream().flatMap(List::stream); + } + + /** + * Get a Pagination for the postAllDocs operation. The page size is configured with the limit + * paramater of the options. + * + * @param client com.ibm.cloud.cloudant.v1.Cloudant instance to make page requests + * @param options com.ibm.cloud.cloudant.v1.model.PostAllDocsOptions for the query + * @return a Pagination for all the documents in the database + */ + public static Pagination newPagination(Cloudant client, + PostAllDocsOptions options) { + return new Pagination(client, + OptionsHandler.duplicate(options), AllDocsPager::new); + } + + /** + * Get a Pagination for the postPartitionAllDocs operation. The page size is configured with the + * limit paramater of the options. + * + * @param client com.ibm.cloud.cloudant.v1.Cloudant instance to make page requests + * @param options com.ibm.cloud.cloudant.v1.model.PostPartitionAllDocsOptions for the query + * @return a Pagination for all the documents in a database partition + */ + public static Pagination newPagination( + Cloudant client, PostPartitionAllDocsOptions options) { + return new Pagination(client, + OptionsHandler.duplicate(options), AllDocsPartitionPager::new); + } + + /** + * Get a Pagination for the postFind operation. The page size is configured with the limit + * paramater of the options. + * + * @param client com.ibm.cloud.cloudant.v1.Cloudant instance to make page requests + * @param options com.ibm.cloud.cloudant.v1.model.PostFindOptions for the query + * @return a Pagination for the result of a find query + */ + public static Pagination newPagination(Cloudant client, + PostFindOptions options) { + return new Pagination(client, OptionsHandler.duplicate(options), + FindPager::new); + } + + /** + * Get a Pagination for the postPartitionFind operation. + * + * @param client com.ibm.cloud.cloudant.v1.Cloudant instance to make page requests + * @param options com.ibm.cloud.cloudant.v1.model.PostPartitionFindOptions for the query + * @return a Pagination for the result of a partition find query + */ + public static Pagination newPagination(Cloudant client, + PostPartitionFindOptions options) { + return new Pagination(client, + OptionsHandler.duplicate(options), FindPartitionPager::new); + } + + /** + * Get a Pagination for the postSearch operation. The page size is configured with the limit + * paramater of the options. + * + * @param client com.ibm.cloud.cloudant.v1.Cloudant instance to make page requests + * @param options com.ibm.cloud.cloudant.v1.model.PostSearchOptions for the query + * @return a Pagination for the result of a search query + */ + public static Pagination newPagination(Cloudant client, + PostSearchOptions options) { + return new Pagination(client, + OptionsHandler.duplicate(options), SearchPager::new); + } + + /** + * Get a Pagination for the postPartitionSearch operation. The page size is configured with the + * limit paramater of the options. + * + * @param client com.ibm.cloud.cloudant.v1.Cloudant instance to make page requests + * @param options com.ibm.cloud.cloudant.v1.model.PostPartitionSearchOptions for the query + * @return a Pagination for the result of a partition search query + */ + public static Pagination newPagination( + Cloudant client, PostPartitionSearchOptions options) { + return new Pagination(client, + OptionsHandler.duplicate(options), SearchPartitionPager::new); + } + + /** + * Get a Pagination for the postView operation. The page size is configured with the limit + * paramater of the options. + * + * @param client com.ibm.cloud.cloudant.v1.Cloudant instance to make page requests + * @param options com.ibm.cloud.cloudant.v1.model.PostViewOptions for the query + * @return a Pagination for the result of a view query + */ + public static Pagination newPagination(Cloudant client, + PostViewOptions options) { + return new Pagination(client, OptionsHandler.duplicate(options), + ViewPager::new); + } + + /** + * Get a Pagination for the postPartitionView operation. The page size is configured with the + * limit paramater of the options. + * + * @param client com.ibm.cloud.cloudant.v1.Cloudant instance to make page requests + * @param options com.ibm.cloud.cloudant.v1.model.PostPartitionViewOptions for the query + * @return a Pagination for the result of a partition view query + */ + public static Pagination newPagination(Cloudant client, + PostPartitionViewOptions options) { + return new Pagination(client, + OptionsHandler.duplicate(options), ViewPartitionPager::new); + } + +} diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchBasePager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchBasePager.java index 6b8c9281c..ca00bbf98 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchBasePager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchBasePager.java @@ -21,8 +21,8 @@ abstract class SearchBasePager extends BookmarkPager { - SearchBasePager(Cloudant client, O options) { - super(client, options); + SearchBasePager(Cloudant client, O options, OptionsHandler optsHandler) { + super(client, options, optsHandler); } @Override diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchPager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchPager.java index 46298bb18..4ec7907f3 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchPager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchPager.java @@ -19,13 +19,12 @@ import com.ibm.cloud.cloudant.v1.model.PostSearchOptions; import com.ibm.cloud.cloudant.v1.model.PostSearchOptions.Builder; import com.ibm.cloud.cloudant.v1.model.SearchResult; -import com.ibm.cloud.cloudant.v1.model.SearchResultRow; import com.ibm.cloud.sdk.core.http.ServiceCall; final class SearchPager extends SearchBasePager { SearchPager(Cloudant client, PostSearchOptions options) { - super(client, options); + super(client, options, OptionsHandler.POST_SEARCH); } @Override @@ -53,9 +52,4 @@ Function limitGetter() { return PostSearchOptions::limit; } - @Override - BiFunction> getConstructor() { - return SearchPager::new; - } - } diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchPartitionPager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchPartitionPager.java index c14b5f1c5..7818d890e 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchPartitionPager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchPartitionPager.java @@ -19,13 +19,12 @@ import com.ibm.cloud.cloudant.v1.model.PostPartitionSearchOptions; import com.ibm.cloud.cloudant.v1.model.PostPartitionSearchOptions.Builder; import com.ibm.cloud.cloudant.v1.model.SearchResult; -import com.ibm.cloud.cloudant.v1.model.SearchResultRow; import com.ibm.cloud.sdk.core.http.ServiceCall; final class SearchPartitionPager extends SearchBasePager { SearchPartitionPager(Cloudant client, PostPartitionSearchOptions options) { - super(client, options); + super(client, options, OptionsHandler.POST_PARTITION_SEARCH); } @Override @@ -53,9 +52,4 @@ Function limitGetter() { return PostPartitionSearchOptions::limit; } - @Override - BiFunction> getConstructor() { - return SearchPartitionPager::new; - } - } diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewBasePager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewBasePager.java index bd7e37e59..ec000f545 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewBasePager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewBasePager.java @@ -22,8 +22,8 @@ abstract class ViewBasePager extends KeyPager { - ViewBasePager(Cloudant client, O options) { - super(client, options); + ViewBasePager(Cloudant client, O options, OptionsHandler optsHandler) { + super(client, options, optsHandler); } @Override diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewPager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewPager.java index c3cef2299..5e03b3b04 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewPager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewPager.java @@ -20,13 +20,12 @@ import com.ibm.cloud.cloudant.v1.model.PostViewOptions; import com.ibm.cloud.cloudant.v1.model.PostViewOptions.Builder; import com.ibm.cloud.cloudant.v1.model.ViewResult; -import com.ibm.cloud.cloudant.v1.model.ViewResultRow; import com.ibm.cloud.sdk.core.http.ServiceCall; final class ViewPager extends ViewBasePager { ViewPager(Cloudant client, PostViewOptions options) { - super(client, options); + super(client, options, OptionsHandler.POST_VIEW); } @Override @@ -59,9 +58,4 @@ Function limitGetter() { return PostViewOptions::limit; } - @Override - BiFunction> getConstructor() { - return ViewPager::new; - } - } diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewPartitionPager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewPartitionPager.java index 55d59c361..bbfdeaa1b 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewPartitionPager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewPartitionPager.java @@ -20,13 +20,12 @@ import com.ibm.cloud.cloudant.v1.model.PostPartitionViewOptions; import com.ibm.cloud.cloudant.v1.model.PostPartitionViewOptions.Builder; import com.ibm.cloud.cloudant.v1.model.ViewResult; -import com.ibm.cloud.cloudant.v1.model.ViewResultRow; import com.ibm.cloud.sdk.core.http.ServiceCall; final class ViewPartitionPager extends ViewBasePager { ViewPartitionPager(Cloudant client, PostPartitionViewOptions options) { - super(client, options); + super(client, options, OptionsHandler.POST_PARTITION_VIEW); } @Override @@ -59,9 +58,4 @@ Function limitGetter() { return PostPartitionViewOptions::limit; } - @Override - BiFunction> getConstructor() { - return ViewPartitionPager::new; - } - } diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/package-info.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/package-info.java index 1450bdcd5..289e2f0c2 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/package-info.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/package-info.java @@ -12,9 +12,9 @@ */ /** - * Feature for paginating requests. + * Feature for paginating operation requests. * - * Accessed via the com.ibm.cloud.cloudant.features.pagination.Pager interface. + * Accessed via the {@link Pagination} class. * */ package com.ibm.cloud.cloudant.features.pagination; diff --git a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BasePagerTest.java b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BasePagerTest.java index 921fbab6c..dfc3148df 100644 --- a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BasePagerTest.java +++ b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BasePagerTest.java @@ -1,14 +1,15 @@ /** * © Copyright IBM Corporation 2025. All Rights Reserved. * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. */ package com.ibm.cloud.cloudant.features.pagination; @@ -18,7 +19,7 @@ import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; -import java.util.Spliterator; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BiFunction; import java.util.function.Function; import org.testng.Assert; @@ -37,7 +38,7 @@ import static com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.newPageSupplierFromList; public class BasePagerTest { - + private Cloudant mockClient = new MockPagerClient(null); /** @@ -47,16 +48,16 @@ public class BasePagerTest { private static class TestPager extends BasePager { protected TestPager(Cloudant client, PostViewOptions options) { - super(client, options); + super(client, options, OptionsHandler.POST_VIEW); } - + Cloudant getClient() { return this.client; } /** - * Implicitly tests that limit gets applied per page, otherwise the default would - * be used and page counts would be wrong. + * Implicitly tests that limit gets applied per page, otherwise the default would be used and + * page counts would be wrong. */ @Override protected Function optionsToBuilderFunction() { @@ -74,8 +75,8 @@ protected Function> itemsGetter() { } /** - * These tests don't actually use the options, but we set a startKey - * so we can validate calls to setNextPageOptions. + * These tests don't actually use the options, but we set a startKey so we can validate calls to + * setNextPageOptions. */ @Override protected void setNextPageOptions(Builder builder, TestResult result) { @@ -89,8 +90,8 @@ protected void setNextPageOptions(Builder builder, TestResult result) { } /** - * Delegates to our next mock. - * If the BasePager didn't correctly call this the mocks wouldn't work. + * Delegates to our next mock. If the BasePager didn't correctly call this the mocks wouldn't + * work. */ @Override protected BiFunction> nextRequestFunction() { @@ -104,52 +105,46 @@ protected Function limitGetter() { return PostViewOptions::limit; } - @Override - BiFunction> getConstructor() { - return TestPager::new; - } - } -private static class ThrowingTestPager extends TestPager { + private static class ThrowingTestPager extends TestPager { - // This has to be static because we get new Pager for iterables - private static int ctorCounter = 0; - private int callCounter = 0; + private int callCounter = 0; - protected ThrowingTestPager(Cloudant client, PostViewOptions options) { - super(client, options); - } + protected ThrowingTestPager(Cloudant client, PostViewOptions options) { + super(client, options); + } - @Override - List nextRequest() { - callCounter++; - switch(callCounter) { - case 2: - throw new RuntimeException("Test issue with request"); - default: - return super.nextRequest(); + @Override + List nextRequest() { + callCounter++; + switch (callCounter) { + case 2: + throw new RuntimeException("Test issue with request"); + default: + return super.nextRequest(); + } } + } - @Override - BiFunction> getConstructor() { - ctorCounter++; - // Use the error version only initially, afterwards return a non-throwing version - if (ctorCounter >= 2) { - return TestPager::new; - } - return ThrowingTestPager::new; + private Pagination newPagination(Cloudant client, + PostViewOptions options) { + return new Pagination<>(client, options, TestPager::new); } -} + private Pagination newThrowingPagination(Cloudant client, + PostViewOptions options) { + return new Pagination<>(client, options, ThrowingTestPager::new); + } // test constructor @Test void testConstructor() { TestPager pager = new TestPager(mockClient, getDefaultTestOptions(42)); // Assert the client - Assert.assertEquals(pager.getClient(), mockClient, "The client should be the supplied one."); + Assert.assertEquals(((TestPager) pager).getClient(), mockClient, + "The client should be the supplied one."); } // test page size default @@ -183,7 +178,7 @@ void testHasNextIsTrueForResultEqualToLimit() { }); TestPager pager = new TestPager(c, getDefaultTestOptions(1)); // Get the first page (size 1) - pager.getNext(); + pager.next(); // hasNext should return true because results size is the same as limit Assert.assertEquals(pager.hasNext(), true, "hasNext() should return true."); } @@ -196,230 +191,380 @@ void testHasNextIsFalseForResultLessThanLimit() { }); TestPager pager = new TestPager(c, getDefaultTestOptions(1)); // Get the first page (size 0) - pager.getNext(); + pager.next(); // hasNext should return false because results size smaller than limit Assert.assertEquals(pager.hasNext(), false, "hasNext() should return false."); } - // test getNext + // test next @Test - void testGetNextFirstPage() { + void testNextFirstPage() { int pageSize = 25; - PageSupplier pageSupplier = PaginationTestHelpers.newBasePageSupplier(pageSize, pageSize); + PageSupplier pageSupplier = + PaginationTestHelpers.newBasePageSupplier(pageSize, pageSize); MockPagerClient c = new MockPagerClient(pageSupplier); TestPager pager = new TestPager(c, getDefaultTestOptions(pageSize)); - List actualPage = pager.getNext(); + List actualPage = pager.next(); // Assert first page - Assert.assertEquals(actualPage, pageSupplier.pages.get(0), "The actual page should match the expected page."); + Assert.assertEquals(actualPage, pageSupplier.pages.get(0), + "The actual page should match the expected page."); } @Test - void testGetNextTwoPages() { + void testNextTwoPages() { int pageSize = 3; - PageSupplier pageSupplier = PaginationTestHelpers.newBasePageSupplier(2*pageSize, pageSize); + PageSupplier pageSupplier = + PaginationTestHelpers.newBasePageSupplier(2 * pageSize, pageSize); MockPagerClient c = new MockPagerClient(pageSupplier); TestPager pager = new TestPager(c, getDefaultTestOptions(pageSize)); Assert.assertEquals(pager.hasNext(), true, "hasNext() should return true."); - List actualPage1 = pager.getNext(); + List actualPage1 = pager.next(); // Assert pages - Assert.assertEquals(actualPage1, pageSupplier.pages.get(0), "The actual page should match the expected page."); + Assert.assertEquals(actualPage1, pageSupplier.pages.get(0), + "The actual page should match the expected page."); Assert.assertEquals(pager.hasNext(), true, "hasNext() should return true."); - List actualPage2 = pager.getNext(); - Assert.assertEquals(actualPage2, pageSupplier.pages.get(1), "The actual page should match the expected page."); + List actualPage2 = pager.next(); + Assert.assertEquals(actualPage2, pageSupplier.pages.get(1), + "The actual page should match the expected page."); Assert.assertEquals(pager.hasNext(), true, "hasNext() should return true."); } @Test - void testGetNextUntilEmpty() { + void testNextUntilEmpty() { int pageSize = 3; - PageSupplier pageSupplier = PaginationTestHelpers.newBasePageSupplier(3*pageSize, pageSize); + PageSupplier pageSupplier = + PaginationTestHelpers.newBasePageSupplier(3 * pageSize, pageSize); MockPagerClient c = new MockPagerClient(pageSupplier); TestPager pager = new TestPager(c, getDefaultTestOptions(pageSize)); List actualItems = new ArrayList<>(); int pageCount = 0; - while(pager.hasNext()) { + while (pager.hasNext()) { pageCount++; - List page = pager.getNext(); + List page = pager.next(); actualItems.addAll(page); // Assert each page is the same or smaller than the limit // to make sure we aren't getting all the results in a single page - Assert.assertTrue(page.size() <= pageSize, "The actual page size should be smaller or equal to the expected page size."); + Assert.assertTrue(page.size() <= pageSize, + "The actual page size should be smaller or equal to the expected page size."); } - Assert.assertEquals(actualItems, pageSupplier.allItems, "The results should match all the pages."); - Assert.assertEquals(pageCount, pageSupplier.pages.size(), "There should have been the correct number of pages."); + Assert.assertEquals(actualItems, pageSupplier.allItems, + "The results should match all the pages."); + Assert.assertEquals(pageCount, pageSupplier.pages.size(), + "There should have been the correct number of pages."); } @Test - void testGetNextUntilSmaller() { + void testNextUntilSmaller() { int pageSize = 3; - PageSupplier pageSupplier = PaginationTestHelpers.newBasePageSupplier(10, pageSize); + PageSupplier pageSupplier = + PaginationTestHelpers.newBasePageSupplier(10, pageSize); MockPagerClient c = new MockPagerClient(pageSupplier); TestPager pager = new TestPager(c, getDefaultTestOptions(pageSize)); List actualItems = new ArrayList<>(); int pageCount = 0; - while(pager.hasNext()) { + while (pager.hasNext()) { pageCount++; - List page = pager.getNext(); + List page = pager.next(); actualItems.addAll(page); // Assert each page is the same or smaller than the limit // to make sure we aren't getting all the results in a single page - Assert.assertTrue(page.size() <= pageSize, "The actual page size should be smaller or equal to the expected page size."); + Assert.assertTrue(page.size() <= pageSize, + "The actual page size should be smaller or equal to the expected page size."); } - Assert.assertEquals(actualItems, pageSupplier.allItems, "The results should match all the pages."); - Assert.assertEquals(pageCount, pageSupplier.pages.size(), "There should have been the correct number of pages."); + Assert.assertEquals(actualItems, pageSupplier.allItems, + "The results should match all the pages."); + Assert.assertEquals(pageCount, pageSupplier.pages.size(), + "There should have been the correct number of pages."); } @Test - void testGetNextException() { + void testNextException() { int pageSize = 2; - PageSupplier pageSupplier = PaginationTestHelpers.newBasePageSupplier(pageSize - 1, pageSize); + PageSupplier pageSupplier = + PaginationTestHelpers.newBasePageSupplier(pageSize - 1, pageSize); MockPagerClient c = new MockPagerClient(pageSupplier); TestPager pager = new TestPager(c, getDefaultTestOptions(pageSize)); - List actualPage = pager.getNext(); + List actualPage = pager.next(); // Assert first page - Assert.assertEquals(actualPage, pageSupplier.pages.get(0), "The actual page should match the expected page."); + Assert.assertEquals(actualPage, pageSupplier.pages.get(0), + "The actual page should match the expected page."); // hasNext false Assert.assertEquals(pager.hasNext(), false, "hasNext() should return false."); - // getNext throws + // next throws Assert.assertThrows(NoSuchElementException.class, () -> { - pager.getNext(); + pager.next(); }); } - // test getAll - @Test - void testGetAll() { - int pageSize = 11; - PageSupplier pageSupplier = PaginationTestHelpers.newBasePageSupplier(71, pageSize); - MockPagerClient c = new MockPagerClient(pageSupplier); - TestPager pager = new TestPager(c, getDefaultTestOptions(pageSize)); - List actualItems = pager.getAll(); - Assert.assertEquals(actualItems, pageSupplier.allItems, "The results should match all the pages."); - } - - // test getRows stream - @Test - void testGetRows() { - int pageSize = 11; - PageSupplier pageSupplier = PaginationTestHelpers.newBasePageSupplier(71, pageSize); - MockPagerClient c = new MockPagerClient(pageSupplier); - TestPager pager = new TestPager(c, getDefaultTestOptions(pageSize)); - pager.getRows().forEachOrdered(i -> - Assert.assertEquals(i, pageSupplier.allItems.remove(0), "The row should be the expected one.") - ); - } - - // test next - @Test - void testNextFirstPage() { - int pageSize = 7; - PageSupplier pageSupplier = PaginationTestHelpers.newBasePageSupplier(2*pageSize, pageSize); - MockPagerClient c = new MockPagerClient(pageSupplier); - TestPager pager = new TestPager(c, getDefaultTestOptions(pageSize)); - List actualPage = pager.next(); - // Assert first page - Assert.assertEquals(actualPage, pageSupplier.pages.get(0), "The actual page should match the expected page."); - } - - // test iterator - @Test - void testIterator() { - int pageSize = 23; - PageSupplier pageSupplier = PaginationTestHelpers.newBasePageSupplier(3*pageSize - 1, pageSize); - MockPagerClient c = new MockPagerClient(pageSupplier); - TestPager pager = new TestPager(c, getDefaultTestOptions(pageSize)); - Assert.assertNotNull(pager.iterator(), "The iterator should not be null."); - // Check pager is Iterable - Iterator> expectedPages = pageSupplier.pages.iterator(); - for (List page : pager) { - Assert.assertEquals(page, expectedPages.next()); - } - } - - // test spliterator - @Test - void testSpliterator() { - int pageSize = 23; - PageSupplier pageSupplier = PaginationTestHelpers.newBasePageSupplier(3*pageSize - 1, pageSize); - MockPagerClient c = new MockPagerClient(pageSupplier); - TestPager pager = new TestPager(c, getDefaultTestOptions(pageSize)); - Spliterator> s = pager.spliterator(); - Assert.assertNotNull(s, "The spliterator should not be null."); - Assert.assertTrue(s.hasCharacteristics(Spliterator.ORDERED), "The spliterator should be ordered."); - Assert.assertTrue(s.hasCharacteristics(Spliterator.NONNULL), "The spliterator should be nonnull."); - Assert.assertTrue(s.hasCharacteristics(Spliterator.IMMUTABLE), "The spliterator should be immutable."); - } - @Test void testPagesAreImmutable() { int pageSize = 1; - PageSupplier pageSupplier = PaginationTestHelpers.newBasePageSupplier(pageSize, pageSize); + PageSupplier pageSupplier = + PaginationTestHelpers.newBasePageSupplier(pageSize, pageSize); MockPagerClient c = new MockPagerClient(pageSupplier); TestPager pager = new TestPager(c, getDefaultTestOptions(pageSize)); - List actualPage = pager.getNext(); + List actualPage = pager.next(); // Assert immutable - Assert.assertThrows( UnsupportedOperationException.class, () -> { + Assert.assertThrows(UnsupportedOperationException.class, () -> { actualPage.add(17); }); } - //test setNextPageOptions + // test setNextPageOptions @Test void testSetNextPageOptions() { int pageSize = 1; - PageSupplier pageSupplier = PaginationTestHelpers.newBasePageSupplier(5*pageSize, pageSize); + PageSupplier pageSupplier = + PaginationTestHelpers.newBasePageSupplier(5 * pageSize, pageSize); MockPagerClient c = new MockPagerClient(pageSupplier); TestPager pager = new TestPager(c, getDefaultTestOptions(pageSize)); - Assert.assertNull(pager.nextPageOptionsRef.get().startKey(), "startKey should initially be null."); - // Since we use a page size of 1, each next page options key, is the same as the element from the page + Assert.assertNull(pager.nextPageOptionsRef.get().startKey(), + "startKey should initially be null."); + // Since we use a page size of 1, each next page options key, is the same as the element from + // the page int page = 0; - while(pager.hasNext()) { - pager.getNext(); + while (pager.hasNext()) { + pager.next(); if (pager.hasNext()) { - Assert.assertEquals(pager.nextPageOptionsRef.get().startKey(), page, "the startKey should increment per page."); + Assert.assertEquals(pager.nextPageOptionsRef.get().startKey(), page, + "the startKey should increment per page."); } else { // Options don't change for last page - Assert.assertEquals(pager.nextPageOptionsRef.get().startKey(), page - 1, "The options should not be set for the final page."); + Assert.assertEquals(pager.nextPageOptionsRef.get().startKey(), page - 1, + "The options should not be set for the final page."); } page++; } } @Test - void testGetNextResumesAfterError() { + void testNextResumesAfterError() { int pageSize = 3; - PageSupplier pageSupplier = PaginationTestHelpers.newBasePageSupplier(2*pageSize, pageSize); + PageSupplier pageSupplier = + PaginationTestHelpers.newBasePageSupplier(2 * pageSize, pageSize); MockPagerClient c = new MockPagerClient(pageSupplier); TestPager pager = new ThrowingTestPager(c, getDefaultTestOptions(pageSize)); - List actualPage1 = pager.getNext(); + List actualPage1 = pager.next(); // Assert pages - Assert.assertEquals(actualPage1, pageSupplier.pages.get(0), "The actual page should match the expected page."); + Assert.assertEquals(actualPage1, pageSupplier.pages.get(0), + "The actual page should match the expected page."); // The startKey should point to row 2 (the last row we saw, note this is not doing n+1 paging) - Assert.assertEquals(pager.nextPageOptionsRef.get().startKey(), 2, "The startKey should be 2 for the second page."); - Assert.assertThrows(RuntimeException.class,() -> pager.getNext()); + Assert.assertEquals(pager.nextPageOptionsRef.get().startKey(), 2, + "The startKey should be 2 for the second page."); + Assert.assertThrows(RuntimeException.class, () -> pager.next()); // Assert hasNext Assert.assertEquals(pager.hasNext(), true, "hasNext() should return true."); // The startKey should still point to the second page - Assert.assertEquals(pager.nextPageOptionsRef.get().startKey(), 2, "The startKey should be 2 for the second page."); + Assert.assertEquals(pager.nextPageOptionsRef.get().startKey(), 2, + "The startKey should be 2 for the second page."); + List actualPage2 = pager.next(); + Assert.assertEquals(actualPage2, pageSupplier.pages.get(1), + "The actual page should match the expected page."); + } + + // asPager (getNext) + @Test + void testAsPagerGetNextFirstPage() { + int pageSize = 7; + PageSupplier pageSupplier = + PaginationTestHelpers.newBasePageSupplier(2 * pageSize, pageSize); + MockPagerClient c = new MockPagerClient(pageSupplier); + Pager pager = newPagination(c, getDefaultTestOptions(pageSize)).pager(); + List actualPage = pager.getNext(); + // Assert first page + Assert.assertEquals(actualPage, pageSupplier.pages.get(0), + "The actual page should match the expected page."); + } + + // asPager (getNext until consumed) + @Test + void testAsPagerGetNextUntilConsumed() { + int pageSize = 7; + PageSupplier pageSupplier = + PaginationTestHelpers.newBasePageSupplier(2 * pageSize, pageSize); + MockPagerClient c = new MockPagerClient(pageSupplier); + Pager pager = newPagination(c, getDefaultTestOptions(pageSize)).pager(); + List actualItems = new ArrayList<>(); + Iterator> expectedPages = pageSupplier.pages.iterator(); + int pageCount = 0; + while (pager.hasNext()) { + List page = pager.getNext(); + Assert.assertEquals(page, expectedPages.next()); + actualItems.addAll(page); + pageCount++; + } + // Assert items + Assert.assertEquals(actualItems, pageSupplier.allItems, + "The results should match all the pages."); + // Assert page count, note 3 because third page is empty + Assert.assertEquals(pageCount, 3, + "There should have been the correct number of pages."); + // Assert cannot be called again + Assert.assertThrows(IllegalStateException.class, + () -> pager.getNext() + ); + } + + // asPager (getAll) + @Test + void testAsPagerGetAll() { + int pageSize = 11; + PageSupplier pageSupplier = + PaginationTestHelpers.newBasePageSupplier(71, pageSize); + MockPagerClient c = new MockPagerClient(pageSupplier); + Pager pager = newPagination(c, getDefaultTestOptions(pageSize)).pager(); + List actualItems = pager.getAll(); + Assert.assertEquals(actualItems, pageSupplier.allItems, + "The results should match all the pages."); + // Assert consumed state prevents calling again + Assert.assertThrows(IllegalStateException.class, + () -> pager.getAll() + ); + } + + // asPager (getNext exception) + @Test + void testAsPagerGetNextResumesAfterError() { + // This is like testNextResumesAfterError but for Pager getNext + // We can't introspect the options here, but if we get the right results the options must be + // right since we tested that arleady in testNextResumesAfterError + int pageSize = 3; + PageSupplier pageSupplier = + PaginationTestHelpers.newBasePageSupplier(2 * pageSize, pageSize); + MockPagerClient c = new MockPagerClient(pageSupplier); + Pager pager = newThrowingPagination(c, getDefaultTestOptions(pageSize)).pager(); + List actualPage1 = pager.getNext(); + // Assert pages + Assert.assertEquals(actualPage1, pageSupplier.pages.get(0), + "The actual page should match the expected page."); + Assert.assertThrows(RuntimeException.class, () -> pager.getNext()); + // Assert hasNext + Assert.assertEquals(pager.hasNext(), true, "hasNext() should return true."); List actualPage2 = pager.getNext(); - Assert.assertEquals(actualPage2, pageSupplier.pages.get(1), "The actual page should match the expected page."); + Assert.assertEquals(actualPage2, pageSupplier.pages.get(1), + "The actual page should match the expected page."); } + // asPager (getAll exception) @Test - void testGetAllRestartsAfterError() { + void testAsPagerGetAllRestartsAfterError() { int pageSize = 1; // Set up a supplier to do first page, [error], first page, second page, empty page - PageSupplier pageSupplier = newPageSupplierFromList(List.of( - List.of(1), // first page - List.of(1), // error, followed by first page replay - List.of(2), // second page - Collections.emptyList())); // final empty page + PageSupplier pageSupplier = newPageSupplierFromList( + List.of(List.of(1), // first page + List.of(1), // error, followed by first page replay + List.of(2), // second page + Collections.emptyList())); // final empty page MockPagerClient c = new MockPagerClient(pageSupplier); - TestPager pager = new ThrowingTestPager(c, getDefaultTestOptions(pageSize)); + final AtomicInteger constructedOnce = new AtomicInteger(); + Pagination errorOnFirst = new Pagination(c, + getDefaultTestOptions(pageSize), (client, opts) -> { + // Note that Pager automatically makes an iterator for hasNext/getNext so that is call 0 + // Call 1 is the first getAll (that will throw) + // Call 2 should not throw + if (constructedOnce.getAndIncrement() > 1) { + return new TestPager(client, opts); + } else { + return new ThrowingTestPager(client, opts); + } + }); + Pager pager = errorOnFirst.pager(); Assert.assertThrows(RuntimeException.class, () -> pager.getAll()); - // Assert no startKey set - Assert.assertNull(pager.nextPageOptionsRef.get().startKey(), "startKey should initially be null."); Assert.assertEquals(pager.getAll(), List.of(1, 2), "The results should match all the pages."); + // Assert consumed state prevents calling again + Assert.assertThrows(IllegalStateException.class, + () -> pager.getAll() + ); + } + + // asPager (getNext then getAll) + @Test + void testAsPagerGetNextGetAllThrows() { + int pageSize = 7; + PageSupplier pageSupplier = + PaginationTestHelpers.newBasePageSupplier(2 * pageSize, pageSize); + MockPagerClient c = new MockPagerClient(pageSupplier); + Pager pager = newPagination(c, getDefaultTestOptions(pageSize)).pager(); + // Assert first page + Assert.assertEquals(pager.getNext(), pageSupplier.pages.get(0), + "The actual page should match the expected page."); + // Assert cannot call getAll once getNext has been called + Assert.assertThrows(IllegalStateException.class, + () -> pager.getAll() + ); + // Ensure we can call getNext() again + // Assert second page + Assert.assertEquals(pager.getNext(), pageSupplier.pages.get(1), + "The actual page should match the expected page."); + } + + // asPager (getAll then getNext) + @Test + void testAsPagerGetAllGetNextThrows() { + int pageSize = 7; + PageSupplier pageSupplier = + PaginationTestHelpers.newBasePageSupplier(2 * pageSize, pageSize); + MockPagerClient c = new MockPagerClient(pageSupplier); + Pager pager = new Pagination(c, + getDefaultTestOptions(pageSize), ThrowingTestPager::new).pager(); + // Make sure we stop the getAll in a non-consumed state + Assert.assertThrows(RuntimeException.class, + () -> pager.getAll() + ); + // Assert cannot call getNext once getAll has been called + Assert.assertThrows(IllegalStateException.class, + () -> pager.getNext() + ); + } + + // asIterable (pages) + @Test + void testPagesIterable() { + int pageSize = 23; + PageSupplier pageSupplier = + PaginationTestHelpers.newBasePageSupplier(3 * pageSize - 1, pageSize); + MockPagerClient c = new MockPagerClient(pageSupplier); + Iterator> expectedPages = pageSupplier.pages.iterator(); + for (List page : newPagination(c, getDefaultTestOptions(pageSize)).pages()) { + Assert.assertEquals(page, expectedPages.next()); + } + } + + // asIterable (rows) + @Test + void testRowsIterable() { + int pageSize = 23; + PageSupplier pageSupplier = + PaginationTestHelpers.newBasePageSupplier(3 * pageSize - 1, pageSize); + MockPagerClient c = new MockPagerClient(pageSupplier); + Iterator expectedRows = pageSupplier.allItems.iterator(); + for (Integer row : newPagination(c, getDefaultTestOptions(pageSize)).rows()) { + Assert.assertEquals(row, expectedRows.next()); + } + } + + // asStream (pages) + @Test + void testPagesStream() { + int pageSize = 23; + PageSupplier pageSupplier = + PaginationTestHelpers.newBasePageSupplier(4 * pageSize, pageSize); + MockPagerClient c = new MockPagerClient(pageSupplier); + Iterator> expectedPages = pageSupplier.pages.iterator(); + newPagination(c, getDefaultTestOptions(pageSize)).pages() + .forEach(i -> Assert.assertEquals(i, expectedPages.next())); + } + + // asStream (rows) + @Test + void testRowsStream() { + int pageSize = 17; + PageSupplier pageSupplier = + PaginationTestHelpers.newBasePageSupplier(4 * pageSize, pageSize); + MockPagerClient c = new MockPagerClient(pageSupplier); + Iterator expectedRows = pageSupplier.allItems.iterator(); + newPagination(c, getDefaultTestOptions(pageSize)).rows() + .forEach(i -> Assert.assertEquals(i, expectedRows.next())); } } diff --git a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BookmarkPagerTest.java b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BookmarkPagerTest.java index 3fb4c9e31..9750a0bef 100644 --- a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BookmarkPagerTest.java +++ b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BookmarkPagerTest.java @@ -1,14 +1,15 @@ /** * © Copyright IBM Corporation 2025. All Rights Reserved. * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. */ package com.ibm.cloud.cloudant.features.pagination; @@ -34,22 +35,22 @@ public class BookmarkPagerTest { private Cloudant mockClient = new MockPagerClient(null); /** - * This test sub-class of BookmarkPager implicitly tests that various abstract methods are correctly - * called. + * This test sub-class of BookmarkPager implicitly tests that various abstract methods are + * correctly called. */ class TestBookmarkPager extends BookmarkPager { protected TestBookmarkPager(Cloudant client, PostFindOptions options) { - super(client, options); + super(client, options, OptionsHandler.POST_FIND); } - + Cloudant getClient() { return this.client; } /** - * Delegates to our next mock. - * If the BasePager didn't correctly call this the mocks wouldn't work. + * Delegates to our next mock. If the BasePager didn't correctly call this the mocks wouldn't + * work. */ @Override protected BiFunction> nextRequestFunction() { @@ -78,11 +79,6 @@ protected Function> itemsGetter() { return TestResult::getRows; } - @Override - BiFunction> getConstructor() { - return TestBookmarkPager::new; - } - @Override Function bookmarkGetter() { // Use the last row value as the bookmark @@ -102,7 +98,8 @@ BiFunction bookmarkSetter() { // Test page size default @Test void testDefaultPageSize() { - TestBookmarkPager pager = new TestBookmarkPager(mockClient, getRequiredTestFindOptionsBuilder().build()); + TestBookmarkPager pager = + new TestBookmarkPager(mockClient, getRequiredTestFindOptionsBuilder().build()); // Assert the limit provided as page size Assert.assertEquals(pager.pageSize, 200, "The page size should be the default limit."); } @@ -117,38 +114,43 @@ void testLimitPageSize() { // Test all items on page when no more pages @Test - void testGetNextPageLessThanLimit() { + void testNextPageLessThanLimit() { int pageSize = 21; PageSupplier pageSupplier = newBasePageSupplier(pageSize - 1, pageSize); MockPagerClient c = new MockPagerClient(pageSupplier); TestBookmarkPager pager = new TestBookmarkPager(c, getDefaultTestFindOptions(pageSize)); - List actualPage = pager.getNext(); + List actualPage = pager.next(); // Assert first page - Assert.assertEquals(actualPage, pageSupplier.pages.get(0), "The actual page should match the expected page."); + Assert.assertEquals(actualPage, pageSupplier.pages.get(0), + "The actual page should match the expected page."); // Assert size - Assert.assertEquals(actualPage.size(), pageSize - 1, "The actual page size should match the expected page size."); + Assert.assertEquals(actualPage.size(), pageSize - 1, + "The actual page size should match the expected page size."); // Assert hasNext false Assert.assertFalse(pager.hasNext(), "hasNext() should return false."); } // Test correct items on page when limit @Test - void testGetNextPageEqualToLimit() { + void testNextPageEqualToLimit() { int pageSize = 14; PageSupplier pageSupplier = newBasePageSupplier(pageSize, pageSize); MockPagerClient c = new MockPagerClient(pageSupplier); TestBookmarkPager pager = new TestBookmarkPager(c, getDefaultTestFindOptions(pageSize)); - List actualPage = pager.getNext(); + List actualPage = pager.next(); // Assert first page - Assert.assertEquals(actualPage, pageSupplier.pages.get(0), "The actual page should match the expected page."); + Assert.assertEquals(actualPage, pageSupplier.pages.get(0), + "The actual page should match the expected page."); // Assert size is pageSize - Assert.assertEquals(actualPage.size(), pageSize, "The actual page size should match the expected page size."); + Assert.assertEquals(actualPage.size(), pageSize, + "The actual page size should match the expected page size."); // Assert hasNext true Assert.assertTrue(pager.hasNext(), "hasNext() should return true."); // Assert bookmark is correct, note the result rows start at zero, so pageSize - 1, not pageSize - Assert.assertEquals(pager.nextPageOptionsRef.get().bookmark(), String.valueOf(pageSize - 1), "The bookmark should be one less than the page size."); + Assert.assertEquals(pager.nextPageOptionsRef.get().bookmark(), String.valueOf(pageSize - 1), + "The bookmark should be one less than the page size."); // Get next page - List actualSecondPage = pager.getNext(); + List actualSecondPage = pager.next(); // Assert second page is empty Assert.assertEquals(actualSecondPage.size(), 0, "The second page should be empty."); // Assert hasNext false @@ -157,28 +159,34 @@ void testGetNextPageEqualToLimit() { // Test correct items on pages when more than limit @Test - void testGetNextPageGreaterThanLimit() { + void testNextPageGreaterThanLimit() { int pageSize = 7; - PageSupplier pageSupplier = newBasePageSupplier(pageSize+2, pageSize); + PageSupplier pageSupplier = newBasePageSupplier(pageSize + 2, pageSize); MockPagerClient c = new MockPagerClient(pageSupplier); TestBookmarkPager pager = new TestBookmarkPager(c, getDefaultTestFindOptions(pageSize)); - List actualPage = pager.getNext(); + List actualPage = pager.next(); // Assert first page - Assert.assertEquals(actualPage, pageSupplier.pages.get(0), "The actual page should match the expected page."); + Assert.assertEquals(actualPage, pageSupplier.pages.get(0), + "The actual page should match the expected page."); // Assert size is pageSize - Assert.assertEquals(actualPage.size(), pageSize, "The actual page size should match the expected page size."); + Assert.assertEquals(actualPage.size(), pageSize, + "The actual page size should match the expected page size."); // Assert hasNext true Assert.assertTrue(pager.hasNext(), "hasNext() should return true."); // Assert bookmark is correct, note the result rows start at zero, so pageSize - 1 - Assert.assertEquals(pager.nextPageOptionsRef.get().bookmark(), String.valueOf(pageSize - 1), "The bookmark should be one less than the page size."); + Assert.assertEquals(pager.nextPageOptionsRef.get().bookmark(), String.valueOf(pageSize - 1), + "The bookmark should be one less than the page size."); // Get next page - List actualSecondPage = pager.getNext(); + List actualSecondPage = pager.next(); // Assert first item on second page is correct - Assert.assertEquals(actualSecondPage.get(0), pageSize, "The first item on the second page should be as expected."); + Assert.assertEquals(actualSecondPage.get(0), pageSize, + "The first item on the second page should be as expected."); // Assert size is 2 - Assert.assertEquals(actualSecondPage.size(), 2, "The actual page size should match the expected page size."); + Assert.assertEquals(actualSecondPage.size(), 2, + "The actual page size should match the expected page size."); // Assert second page - Assert.assertEquals(actualSecondPage, pageSupplier.pages.get(1), "The actual page should match the expected page."); + Assert.assertEquals(actualSecondPage, pageSupplier.pages.get(1), + "The actual page should match the expected page."); // Assert hasNext false Assert.assertFalse(pager.hasNext(), "hasNext() should return false."); } @@ -187,10 +195,11 @@ void testGetNextPageGreaterThanLimit() { @Test void testGetAll() { int pageSize = 3; - PageSupplier pageSupplier = newBasePageSupplier(pageSize*12, pageSize); + PageSupplier pageSupplier = newBasePageSupplier(pageSize * 12, pageSize); MockPagerClient c = new MockPagerClient(pageSupplier); - TestBookmarkPager pager = new TestBookmarkPager(c, getDefaultTestFindOptions(pageSize)); - List actualItems = pager.getAll(); - Assert.assertEquals(actualItems, pageSupplier.allItems, "The results should match all the pages."); + List actualItems = new Pagination(c, + getDefaultTestFindOptions(pageSize), TestBookmarkPager::new).pager().getAll(); + Assert.assertEquals(actualItems, pageSupplier.allItems, + "The results should match all the pages."); } } diff --git a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/KeyPagerTest.java b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/KeyPagerTest.java index f9e3d1988..eee6a62bb 100644 --- a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/KeyPagerTest.java +++ b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/KeyPagerTest.java @@ -1,14 +1,15 @@ /** * © Copyright IBM Corporation 2025. All Rights Reserved. * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. */ package com.ibm.cloud.cloudant.features.pagination; @@ -43,16 +44,16 @@ public class KeyPagerTest { class TestKeyPager extends KeyPager { protected TestKeyPager(Cloudant client, PostViewOptions options) { - super(client, options); + super(client, options, OptionsHandler.POST_VIEW); } - + Cloudant getClient() { return this.client; } /** - * Delegates to our next mock. - * If the BasePager didn't correctly call this the mocks wouldn't work. + * Delegates to our next mock. If the BasePager didn't correctly call this the mocks wouldn't + * work. */ @Override protected BiFunction> nextRequestFunction() { @@ -101,11 +102,6 @@ protected Function> itemsGetter() { return TestResult::getRows; } - @Override - BiFunction> getConstructor() { - return TestKeyPager::new; - } - @Override Optional checkBoundary(Integer penultimateItem, Integer lastItem) { return Optional.empty(); @@ -118,7 +114,8 @@ Optional checkBoundary(Integer penultimateItem, Integer lastItem) { void testDefaultPageSize() { TestKeyPager pager = new TestKeyPager(mockClient, getRequiredTestOptionsBuilder().build()); // Assert the default limit as page size - Assert.assertEquals(pager.pageSize, 201, "The page size should be one more than the default limit."); + Assert.assertEquals(pager.pageSize, 201, + "The page size should be one more than the default limit."); } // Test page size limit (+1) @@ -126,77 +123,96 @@ void testDefaultPageSize() { void testLimitPageSize() { TestKeyPager pager = new TestKeyPager(mockClient, getDefaultTestOptions(42)); // Assert the limit provided as page size - Assert.assertEquals(pager.pageSize, 43, "The page size should be one more than the supplied limit."); + Assert.assertEquals(pager.pageSize, 43, + "The page size should be one more than the supplied limit."); } // Test all items on page when no more pages @Test - void testGetNextPageLessThanLimit() { + void testnextPageLessThanLimit() { int pageSize = 21; PageSupplier pageSupplier = newKeyPageSupplier(pageSize, pageSize); MockPagerClient c = new MockPagerClient(pageSupplier); TestKeyPager pager = new TestKeyPager(c, getDefaultTestOptions(pageSize)); - List actualPage = pager.getNext(); + List actualPage = pager.next(); // Assert first page - Assert.assertEquals(actualPage, pageSupplier.pages.get(0), "The actual page should match the expected page."); + Assert.assertEquals(actualPage, pageSupplier.pages.get(0), + "The actual page should match the expected page."); // Assert size is pageSize - Assert.assertEquals(actualPage.size(), pageSize, "The actual page size should match the expected page size."); + Assert.assertEquals(actualPage.size(), pageSize, + "The actual page size should match the expected page size."); // Assert hasNext false because n+1 limit is 1 more than user page size Assert.assertFalse(pager.hasNext(), "hasNext() should return false."); } // Test correct items on page when n+1 @Test - void testGetNextPageEqualToLimit() { + void testnextPageEqualToLimit() { int pageSize = 14; - PageSupplier pageSupplier = newKeyPageSupplier(pageSize+1, pageSize); + PageSupplier pageSupplier = newKeyPageSupplier(pageSize + 1, pageSize); MockPagerClient c = new MockPagerClient(pageSupplier); TestKeyPager pager = new TestKeyPager(c, getDefaultTestOptions(pageSize)); - List actualPage = pager.getNext(); + List actualPage = pager.next(); // Assert first page - Assert.assertEquals(actualPage, pageSupplier.pages.get(0), "The actual page should match the expected page."); + Assert.assertEquals(actualPage, pageSupplier.pages.get(0), + "The actual page should match the expected page."); // Assert size is pageSize - Assert.assertEquals(actualPage.size(), pageSize, "The actual page size should match the expected page size."); + Assert.assertEquals(actualPage.size(), pageSize, + "The actual page size should match the expected page size."); // Assert hasNext true Assert.assertTrue(pager.hasNext(), "hasNext() should return true."); - // Assert start key is correct, note the result rows start at zero, so pageSize, not pageSize + 1 - Assert.assertEquals(pager.nextPageOptionsRef.get().startKey(), pageSize, "The start key should be page size."); + // Assert start key is correct, note the result rows start at zero, so pageSize, not pageSize + + // 1 + Assert.assertEquals(pager.nextPageOptionsRef.get().startKey(), pageSize, + "The start key should be page size."); // Get next page - List actualSecondPage = pager.getNext(); - // Assert first item on second page is correct (again we start at zero, so pageSize not pageSize + 1) - Assert.assertEquals(actualSecondPage.get(0), pageSize, "The first item on the second page should be as expected."); + List actualSecondPage = pager.next(); + // Assert first item on second page is correct (again we start at zero, so pageSize not pageSize + // + 1) + Assert.assertEquals(actualSecondPage.get(0), pageSize, + "The first item on the second page should be as expected."); // Assert size is 1 - Assert.assertEquals(actualSecondPage.size(), 1, "The actual page size should match the expected page size."); + Assert.assertEquals(actualSecondPage.size(), 1, + "The actual page size should match the expected page size."); // Assert second page - Assert.assertEquals(actualSecondPage, pageSupplier.pages.get(1), "The actual page should match the expected page."); + Assert.assertEquals(actualSecondPage, pageSupplier.pages.get(1), + "The actual page should match the expected page."); // Assert hasNext false Assert.assertFalse(pager.hasNext(), "hasNext() should return false."); } - // Test correct items on page when n+more + // Test correct items on page when n+more @Test - void testGetNextPageGreaterThanLimit() { + void testNextPageGreaterThanLimit() { int pageSize = 7; - PageSupplier pageSupplier = newKeyPageSupplier(pageSize+2, pageSize); + PageSupplier pageSupplier = newKeyPageSupplier(pageSize + 2, pageSize); MockPagerClient c = new MockPagerClient(pageSupplier); TestKeyPager pager = new TestKeyPager(c, getDefaultTestOptions(pageSize)); - List actualPage = pager.getNext(); + List actualPage = pager.next(); // Assert first page - Assert.assertEquals(actualPage, pageSupplier.pages.get(0), "The actual page should match the expected page."); + Assert.assertEquals(actualPage, pageSupplier.pages.get(0), + "The actual page should match the expected page."); // Assert size is pageSize - Assert.assertEquals(actualPage.size(), pageSize, "The actual page size should match the expected page size."); + Assert.assertEquals(actualPage.size(), pageSize, + "The actual page size should match the expected page size."); // Assert hasNext true Assert.assertTrue(pager.hasNext(), "hasNext() should return true."); - // Assert start key is correct, note the result rows start at zero, so pageSize, not pageSize + 1 - Assert.assertEquals(pager.nextPageOptionsRef.get().startKey(), pageSize, "The start key should be page size plus one."); + // Assert start key is correct, note the result rows start at zero, so pageSize, not pageSize + + // 1 + Assert.assertEquals(pager.nextPageOptionsRef.get().startKey(), pageSize, + "The start key should be page size plus one."); // Get next page - List actualSecondPage = pager.getNext(); - // Assert first item on second page is correct (again we start at zero, so pageSize not pageSize + 1) - Assert.assertEquals(actualSecondPage.get(0), pageSize, "The first item on the second page should be as expected."); + List actualSecondPage = pager.next(); + // Assert first item on second page is correct (again we start at zero, so pageSize not pageSize + // + 1) + Assert.assertEquals(actualSecondPage.get(0), pageSize, + "The first item on the second page should be as expected."); // Assert size is 2 - Assert.assertEquals(actualSecondPage.size(), 2, "The actual page size should match the expected page size."); + Assert.assertEquals(actualSecondPage.size(), 2, + "The actual page size should match the expected page size."); // Assert second page - Assert.assertEquals(actualSecondPage, pageSupplier.pages.get(1), "The actual page should match the expected page."); + Assert.assertEquals(actualSecondPage, pageSupplier.pages.get(1), + "The actual page should match the expected page."); // Assert hasNext false Assert.assertFalse(pager.hasNext(), "hasNext() should return false."); } @@ -205,11 +221,12 @@ void testGetNextPageGreaterThanLimit() { @Test void testGetAll() { int pageSize = 3; - PageSupplier pageSupplier = newKeyPageSupplier(pageSize*12, pageSize); + PageSupplier pageSupplier = newKeyPageSupplier(pageSize * 12, pageSize); MockPagerClient c = new MockPagerClient(pageSupplier); - TestKeyPager pager = new TestKeyPager(c, getDefaultTestOptions(pageSize)); - List actualItems = pager.getAll(); - Assert.assertEquals(actualItems, pageSupplier.allItems, "The results should match all the pages."); + List actualItems = new Pagination(c, + getDefaultTestOptions(pageSize), TestKeyPager::new).pager().getAll(); + Assert.assertEquals(actualItems, pageSupplier.allItems, + "The results should match all the pages."); } @Test @@ -218,37 +235,43 @@ void testNoBoundaryCheckByDefault() { // Make pages with identical rows List pageOne = new ArrayList<>(List.of(1, 1)); List pageTwo = new ArrayList<>(List.of(1, 1)); - PageSupplier pageSupplier = PaginationTestHelpers.newPageSupplierFromList(List.of(pageOne, pageTwo)); + PageSupplier pageSupplier = + PaginationTestHelpers.newPageSupplierFromList(List.of(pageOne, pageTwo)); MockPagerClient c = new MockPagerClient(pageSupplier); TestKeyPager pager = new TestKeyPager(c, getDefaultTestOptions(pageSize)); // Get and assert page - Assert.assertEquals(pager.getNext(), pageSupplier.pages.get(0), "The actual page should match the expected page."); + Assert.assertEquals(pager.next(), pageSupplier.pages.get(0), + "The actual page should match the expected page."); // Assert hasNext true Assert.assertTrue(pager.hasNext(), "hasNext() should return true."); // Boundary check implementation should not throw - Assert.assertEquals(pager.getNext(), pageSupplier.pages.get(1), "The actual page should match the expected page."); + Assert.assertEquals(pager.next(), pageSupplier.pages.get(1), + "The actual page should match the expected page."); } @Test - void testBoundaryFailureThrowsOnGetNext() { + void testBoundaryFailureThrowsOnNext() { int pageSize = 1; // Make pages with identical rows List pageOne = new ArrayList<>(List.of(1, 1)); List pageTwo = new ArrayList<>(List.of(1, 1)); - PageSupplier pageSupplier = PaginationTestHelpers.newPageSupplierFromList(List.of(pageOne, pageTwo)); + PageSupplier pageSupplier = + PaginationTestHelpers.newPageSupplierFromList(List.of(pageOne, pageTwo)); MockPagerClient c = new MockPagerClient(pageSupplier); TestKeyPager pager = new TestKeyPager(c, getDefaultTestOptions(pageSize)) { @Override Optional checkBoundary(Integer penultimateItem, Integer lastItem) { return Optional.of("Test boundary check failure"); - }}; + } + }; // Get and assert page - Assert.assertEquals(pager.getNext(), pageSupplier.pages.get(0), "The actual page should match the expected page."); + Assert.assertEquals(pager.next(), pageSupplier.pages.get(0), + "The actual page should match the expected page."); // Assert hasNext true Assert.assertTrue(pager.hasNext(), "hasNext() should return true."); // The optional isn't empty so it check boundary should throw Assert.assertThrows(UnsupportedOperationException.class, () -> { - pager.getNext(); + pager.next(); }); } @@ -257,16 +280,19 @@ void testNoBoundaryCheckWhenNoItemsLeft() { int pageSize = 1; // Make pages with identical rows List pageOne = new ArrayList<>(List.of(1)); - PageSupplier pageSupplier = PaginationTestHelpers.newPageSupplierFromList(List.of(pageOne)); + PageSupplier pageSupplier = + PaginationTestHelpers.newPageSupplierFromList(List.of(pageOne)); MockPagerClient c = new MockPagerClient(pageSupplier); TestKeyPager pager = new TestKeyPager(c, getDefaultTestOptions(pageSize)) { @Override Optional checkBoundary(Integer penultimateItem, Integer lastItem) { // Throw here to cause the test to fail if checkBoundary is called. throw new RuntimeException("Test failure, checkBoundary should not be called."); - }}; + } + }; // Get and assert page - Assert.assertEquals(pager.getNext(), pageSupplier.pages.get(0), "The actual page should match the expected page."); + Assert.assertEquals(pager.next(), pageSupplier.pages.get(0), + "The actual page should match the expected page."); // Assert hasNext false Assert.assertFalse(pager.hasNext(), "hasNext() should return false."); } diff --git a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/PaginationTestHelpers.java b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/PaginationTestHelpers.java index 3b363718f..f0d104d1e 100644 --- a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/PaginationTestHelpers.java +++ b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/PaginationTestHelpers.java @@ -1,14 +1,15 @@ /** * © Copyright IBM Corporation 2025. All Rights Reserved. * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. */ package com.ibm.cloud.cloudant.features.pagination; @@ -35,7 +36,7 @@ static class MockPagerClient extends MockCloudant { MockPagerClient(Supplier> instructionSupplier) { super(instructionSupplier); } - + ServiceCall testCall() { return new MockServiceCall(mocks.get()); } @@ -44,6 +45,7 @@ ServiceCall testCall() { static class TestResult { private List rows; + TestResult(List rows) { this.rows = rows; } @@ -58,7 +60,8 @@ private static class PageSupplierFactory { Function, R> itemsToPageResultFn; Function integerRowWrapFn; - PageSupplierFactory(Function, R> itemsToPageResultFn, Function integerRowWrapFn) { + PageSupplierFactory(Function, R> itemsToPageResultFn, + Function integerRowWrapFn) { this.itemsToPageResultFn = itemsToPageResultFn; this.integerRowWrapFn = integerRowWrapFn; } @@ -69,8 +72,8 @@ private List> getPages(int total, int pageSize) { for (int i = 0; i < total; i++) { page.add(this.integerRowWrapFn.apply(i)); if (i % pageSize == pageSize - 1) { - pages.add(page); - page = new ArrayList<>(); + pages.add(page); + page = new ArrayList<>(); } } // Add the final page, empty or otherwise @@ -97,7 +100,7 @@ private List> repageForKeyBased(List> pages) { try { List nextPage = pages.get(index + 1); responsePage.add(nextPage.get(0)); - } catch(IndexOutOfBoundsException e) { + } catch (IndexOutOfBoundsException e) { // Suppress exception if pages/elements exhuasted } responsePages.add(responsePage); @@ -109,21 +112,23 @@ private List> repageForKeyBased(List> pages) { } static class PageSupplier extends QueuedSupplier { - + final List allItems; final List> pages; - + private PageSupplier(Function, R> itemsToPageFn, List> pages) { this(itemsToPageFn, pages, pages); } - private PageSupplier(Function, R> itemsToPageFn, List> pages, List> responsePages) { - super(responsePages.stream().map(itemsToPageFn).map(MockInstruction::new).collect(Collectors.toList())); + private PageSupplier(Function, R> itemsToPageFn, List> pages, + List> responsePages) { + super(responsePages.stream().map(itemsToPageFn).map(MockInstruction::new) + .collect(Collectors.toList())); this.pages = pages; this.allItems = this.pages.stream().flatMap(List::stream).collect(Collectors.toList()); } } - + private static class TestViewResultRow extends ViewResultRow { private TestViewResultRow(Integer i) { this.id = "testdoc" + String.valueOf(i); @@ -138,15 +143,18 @@ private TestViewResult(List rows) { } static PageSupplier newBasePageSupplier(int total, int pageSize) { - return new PageSupplierFactory<>(TestResult::new, Function.identity()).newPageSupplier(total, pageSize); + return new PageSupplierFactory<>(TestResult::new, Function.identity()).newPageSupplier(total, + pageSize); } static PageSupplier newKeyPageSupplier(int total, int pageSize) { - return new PageSupplierFactory(TestResult::new, Function.identity()).newExtraRowPageSupplier(total, pageSize); + return new PageSupplierFactory(TestResult::new, Function.identity()) + .newExtraRowPageSupplier(total, pageSize); } static PageSupplier newViewPageSupplier(int total, int pageSize) { - return new PageSupplierFactory(TestViewResult::new, TestViewResultRow::new).newExtraRowPageSupplier(total, pageSize); + return new PageSupplierFactory(TestViewResult::new, + TestViewResultRow::new).newExtraRowPageSupplier(total, pageSize); } static PageSupplier newPageSupplierFromList(List> pages) { @@ -154,28 +162,20 @@ static PageSupplier newPageSupplierFromList(List Date: Tue, 25 Mar 2025 17:34:53 +0000 Subject: [PATCH 25/43] refactor: renames --- ...sePager.java => AllDocsBasePageIterator.java} | 4 ++-- ...llDocsPager.java => AllDocsPageIterator.java} | 4 ++-- ...er.java => AllDocsPartitionPageIterator.java} | 4 ++-- .../{BasePager.java => BasePageIterator.java} | 4 ++-- ...kmarkPager.java => BookmarkPageIterator.java} | 4 ++-- ...ocsPager.java => DesignDocsPageIterator.java} | 4 ++-- ...dBasePager.java => FindBasePageIterator.java} | 4 ++-- .../cloudant/features/pagination/FindPager.java | 2 +- .../features/pagination/FindPartitionPager.java | 2 +- .../{KeyPager.java => KeyPageIterator.java} | 4 ++-- .../cloudant/features/pagination/Pagination.java | 16 ++++++++-------- ...asePager.java => SearchBasePageIterator.java} | 4 ++-- ...{SearchPager.java => SearchPageIterator.java} | 4 ++-- ...ger.java => SearchPartitionPageIterator.java} | 4 ++-- ...wBasePager.java => ViewBasePageIterator.java} | 4 ++-- .../{ViewPager.java => ViewPageIterator.java} | 4 ++-- ...Pager.java => ViewPartitionPageIterator.java} | 4 ++-- ...ePagerTest.java => BasePageIteratorTest.java} | 4 ++-- ...erTest.java => BookmarkPageIteratorTest.java} | 4 ++-- ...eyPagerTest.java => KeyPageIteratorTest.java} | 4 ++-- 20 files changed, 44 insertions(+), 44 deletions(-) rename modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/{AllDocsBasePager.java => AllDocsBasePageIterator.java} (89%) rename modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/{AllDocsPager.java => AllDocsPageIterator.java} (91%) rename modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/{AllDocsPartitionPager.java => AllDocsPartitionPageIterator.java} (88%) rename modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/{BasePager.java => BasePageIterator.java} (94%) rename modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/{BookmarkPager.java => BookmarkPageIterator.java} (86%) rename modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/{DesignDocsPager.java => DesignDocsPageIterator.java} (90%) rename modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/{FindBasePager.java => FindBasePageIterator.java} (85%) rename modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/{KeyPager.java => KeyPageIterator.java} (93%) rename modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/{SearchBasePager.java => SearchBasePageIterator.java} (85%) rename modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/{SearchPager.java => SearchPageIterator.java} (90%) rename modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/{SearchPartitionPager.java => SearchPartitionPageIterator.java} (88%) rename modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/{ViewBasePager.java => ViewBasePageIterator.java} (91%) rename modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/{ViewPager.java => ViewPageIterator.java} (91%) rename modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/{ViewPartitionPager.java => ViewPartitionPageIterator.java} (90%) rename modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/{BasePagerTest.java => BasePageIteratorTest.java} (99%) rename modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/{BookmarkPagerTest.java => BookmarkPageIteratorTest.java} (98%) rename modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/{KeyPagerTest.java => KeyPageIteratorTest.java} (98%) diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsBasePager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsBasePageIterator.java similarity index 89% rename from modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsBasePager.java rename to modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsBasePageIterator.java index b580ebb17..f0fbd27fc 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsBasePager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsBasePageIterator.java @@ -21,9 +21,9 @@ import com.ibm.cloud.cloudant.v1.model.AllDocsResult; import com.ibm.cloud.cloudant.v1.model.DocsResultRow; -abstract class AllDocsBasePager extends KeyPager { +abstract class AllDocsBasePageIterator extends KeyPageIterator { - AllDocsBasePager(Cloudant client, O options, OptionsHandler optsHandler) { + AllDocsBasePageIterator(Cloudant client, O options, OptionsHandler optsHandler) { super(client, options, optsHandler); } diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsPager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsPageIterator.java similarity index 91% rename from modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsPager.java rename to modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsPageIterator.java index b6af8fa2c..02871a546 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsPager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsPageIterator.java @@ -21,9 +21,9 @@ import com.ibm.cloud.cloudant.v1.model.PostAllDocsOptions.Builder; import com.ibm.cloud.sdk.core.http.ServiceCall; -final class AllDocsPager extends AllDocsBasePager { +final class AllDocsPageIterator extends AllDocsBasePageIterator { - AllDocsPager(Cloudant client, PostAllDocsOptions options) { + AllDocsPageIterator(Cloudant client, PostAllDocsOptions options) { super(client, options, OptionsHandler.POST_ALL_DOCS); } diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsPartitionPager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsPartitionPageIterator.java similarity index 88% rename from modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsPartitionPager.java rename to modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsPartitionPageIterator.java index 52c08ccdf..f48fa8534 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsPartitionPager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/AllDocsPartitionPageIterator.java @@ -21,9 +21,9 @@ import com.ibm.cloud.cloudant.v1.model.PostPartitionAllDocsOptions.Builder; import com.ibm.cloud.sdk.core.http.ServiceCall; -final class AllDocsPartitionPager extends AllDocsBasePager { +final class AllDocsPartitionPageIterator extends AllDocsBasePageIterator { - AllDocsPartitionPager(Cloudant client, PostPartitionAllDocsOptions options) { + AllDocsPartitionPageIterator(Cloudant client, PostPartitionAllDocsOptions options) { super(client, options, OptionsHandler.POST_PARTITION_ALL_DOCS); } diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BasePager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BasePageIterator.java similarity index 94% rename from modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BasePager.java rename to modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BasePageIterator.java index c0a067860..fe3930d53 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BasePager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BasePageIterator.java @@ -24,7 +24,7 @@ import com.ibm.cloud.cloudant.v1.Cloudant; import com.ibm.cloud.sdk.core.http.ServiceCall; -abstract class BasePager implements Iterator> { +abstract class BasePageIterator implements Iterator> { protected final Cloudant client; protected final long pageSize; @@ -32,7 +32,7 @@ abstract class BasePager implements Iterator> { protected final AtomicReference nextPageOptionsRef = new AtomicReference<>(); protected volatile boolean hasNext = true; - BasePager(Cloudant client, O options, OptionsHandler optsHandler) { + BasePageIterator(Cloudant client, O options, OptionsHandler optsHandler) { this.client = client; this.optsHandler = optsHandler; // Set the page size from the supplied options limit diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BookmarkPager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BookmarkPageIterator.java similarity index 86% rename from modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BookmarkPager.java rename to modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BookmarkPageIterator.java index e8eb4936a..6662a709b 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BookmarkPager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/BookmarkPageIterator.java @@ -17,9 +17,9 @@ import java.util.function.Function; import com.ibm.cloud.cloudant.v1.Cloudant; -abstract class BookmarkPager extends BasePager { +abstract class BookmarkPageIterator extends BasePageIterator { - BookmarkPager(Cloudant client, O options, OptionsHandler optsHandler) { + BookmarkPageIterator(Cloudant client, O options, OptionsHandler optsHandler) { super(client, options, optsHandler); } diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/DesignDocsPager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/DesignDocsPageIterator.java similarity index 90% rename from modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/DesignDocsPager.java rename to modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/DesignDocsPageIterator.java index c2a9c3948..c593d5a41 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/DesignDocsPager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/DesignDocsPageIterator.java @@ -21,9 +21,9 @@ import com.ibm.cloud.cloudant.v1.model.PostDesignDocsOptions.Builder; import com.ibm.cloud.sdk.core.http.ServiceCall; -public class DesignDocsPager extends AllDocsBasePager { +public class DesignDocsPageIterator extends AllDocsBasePageIterator { - DesignDocsPager(Cloudant client, PostDesignDocsOptions options) { + DesignDocsPageIterator(Cloudant client, PostDesignDocsOptions options) { super(client, options, OptionsHandler.POST_DESIGN_DOCS); } diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindBasePager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindBasePageIterator.java similarity index 85% rename from modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindBasePager.java rename to modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindBasePageIterator.java index 6b2fdecbe..0dbdffead 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindBasePager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindBasePageIterator.java @@ -19,9 +19,9 @@ import com.ibm.cloud.cloudant.v1.model.Document; import com.ibm.cloud.cloudant.v1.model.FindResult; -abstract class FindBasePager extends BookmarkPager { +abstract class FindBasePageIterator extends BookmarkPageIterator { - FindBasePager(Cloudant client, O options, OptionsHandler optsHandler) { + FindBasePageIterator(Cloudant client, O options, OptionsHandler optsHandler) { super(client, options, optsHandler); } diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindPager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindPager.java index 143b917dc..d05a462e4 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindPager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindPager.java @@ -21,7 +21,7 @@ import com.ibm.cloud.cloudant.v1.model.PostFindOptions.Builder; import com.ibm.cloud.sdk.core.http.ServiceCall; -final class FindPager extends FindBasePager { +final class FindPager extends FindBasePageIterator { FindPager(Cloudant client, PostFindOptions options) { super(client, options, OptionsHandler.POST_FIND); diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindPartitionPager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindPartitionPager.java index 4d69aaf40..ca2467306 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindPartitionPager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/FindPartitionPager.java @@ -21,7 +21,7 @@ import com.ibm.cloud.cloudant.v1.model.PostPartitionFindOptions.Builder; import com.ibm.cloud.sdk.core.http.ServiceCall; -final class FindPartitionPager extends FindBasePager { +final class FindPartitionPager extends FindBasePageIterator { FindPartitionPager(Cloudant client, PostPartitionFindOptions options) { super(client, options, OptionsHandler.POST_PARTITION_FIND); diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/KeyPager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/KeyPageIterator.java similarity index 93% rename from modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/KeyPager.java rename to modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/KeyPageIterator.java index 04d5c0305..8f3e8be7d 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/KeyPager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/KeyPageIterator.java @@ -19,11 +19,11 @@ import java.util.function.Function; import com.ibm.cloud.cloudant.v1.Cloudant; -abstract class KeyPager extends BasePager { +abstract class KeyPageIterator extends BasePageIterator { private Optional boundaryFailure = Optional.empty(); - KeyPager(Cloudant client, O options, OptionsHandler optsHandler) { + KeyPageIterator(Cloudant client, O options, OptionsHandler optsHandler) { super(client, options, optsHandler); } diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/Pagination.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/Pagination.java index 7639dd850..9bc8e4602 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/Pagination.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/Pagination.java @@ -55,11 +55,11 @@ public class Pagination { private final Cloudant client; private final O opts; - private final BiFunction> iteratorCtor; + private final BiFunction> iteratorCtor; private final Iterable> pageIterable = new PageIterable(); private final Iterable rowIterable = new RowIterable(); - Pagination(Cloudant client, O opts, BiFunction> iteratorCtor) { + Pagination(Cloudant client, O opts, BiFunction> iteratorCtor) { this.client = client; this.opts = opts; this.iteratorCtor = iteratorCtor; @@ -170,7 +170,7 @@ public Stream rowStream() { public static Pagination newPagination(Cloudant client, PostAllDocsOptions options) { return new Pagination(client, - OptionsHandler.duplicate(options), AllDocsPager::new); + OptionsHandler.duplicate(options), AllDocsPageIterator::new); } /** @@ -184,7 +184,7 @@ public static Pagination newPagination(Clouda public static Pagination newPagination( Cloudant client, PostPartitionAllDocsOptions options) { return new Pagination(client, - OptionsHandler.duplicate(options), AllDocsPartitionPager::new); + OptionsHandler.duplicate(options), AllDocsPartitionPageIterator::new); } /** @@ -225,7 +225,7 @@ public static Pagination newPagination(Cloud public static Pagination newPagination(Cloudant client, PostSearchOptions options) { return new Pagination(client, - OptionsHandler.duplicate(options), SearchPager::new); + OptionsHandler.duplicate(options), SearchPageIterator::new); } /** @@ -239,7 +239,7 @@ public static Pagination newPagination(Cloud public static Pagination newPagination( Cloudant client, PostPartitionSearchOptions options) { return new Pagination(client, - OptionsHandler.duplicate(options), SearchPartitionPager::new); + OptionsHandler.duplicate(options), SearchPartitionPageIterator::new); } /** @@ -253,7 +253,7 @@ public static Pagination newPaginat public static Pagination newPagination(Cloudant client, PostViewOptions options) { return new Pagination(client, OptionsHandler.duplicate(options), - ViewPager::new); + ViewPageIterator::new); } /** @@ -267,7 +267,7 @@ public static Pagination newPagination(Cloudant public static Pagination newPagination(Cloudant client, PostPartitionViewOptions options) { return new Pagination(client, - OptionsHandler.duplicate(options), ViewPartitionPager::new); + OptionsHandler.duplicate(options), ViewPartitionPageIterator::new); } } diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchBasePager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchBasePageIterator.java similarity index 85% rename from modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchBasePager.java rename to modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchBasePageIterator.java index ca00bbf98..09d1365d1 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchBasePager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchBasePageIterator.java @@ -19,9 +19,9 @@ import com.ibm.cloud.cloudant.v1.model.SearchResult; import com.ibm.cloud.cloudant.v1.model.SearchResultRow; -abstract class SearchBasePager extends BookmarkPager { +abstract class SearchBasePageIterator extends BookmarkPageIterator { - SearchBasePager(Cloudant client, O options, OptionsHandler optsHandler) { + SearchBasePageIterator(Cloudant client, O options, OptionsHandler optsHandler) { super(client, options, optsHandler); } diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchPager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchPageIterator.java similarity index 90% rename from modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchPager.java rename to modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchPageIterator.java index 4ec7907f3..c7ac7a858 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchPager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchPageIterator.java @@ -21,9 +21,9 @@ import com.ibm.cloud.cloudant.v1.model.SearchResult; import com.ibm.cloud.sdk.core.http.ServiceCall; -final class SearchPager extends SearchBasePager { +final class SearchPageIterator extends SearchBasePageIterator { - SearchPager(Cloudant client, PostSearchOptions options) { + SearchPageIterator(Cloudant client, PostSearchOptions options) { super(client, options, OptionsHandler.POST_SEARCH); } diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchPartitionPager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchPartitionPageIterator.java similarity index 88% rename from modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchPartitionPager.java rename to modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchPartitionPageIterator.java index 7818d890e..6b599d734 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchPartitionPager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/SearchPartitionPageIterator.java @@ -21,9 +21,9 @@ import com.ibm.cloud.cloudant.v1.model.SearchResult; import com.ibm.cloud.sdk.core.http.ServiceCall; -final class SearchPartitionPager extends SearchBasePager { +final class SearchPartitionPageIterator extends SearchBasePageIterator { - SearchPartitionPager(Cloudant client, PostPartitionSearchOptions options) { + SearchPartitionPageIterator(Cloudant client, PostPartitionSearchOptions options) { super(client, options, OptionsHandler.POST_PARTITION_SEARCH); } diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewBasePager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewBasePageIterator.java similarity index 91% rename from modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewBasePager.java rename to modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewBasePageIterator.java index ec000f545..ab15baa2d 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewBasePager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewBasePageIterator.java @@ -20,9 +20,9 @@ import com.ibm.cloud.cloudant.v1.model.ViewResult; import com.ibm.cloud.cloudant.v1.model.ViewResultRow; -abstract class ViewBasePager extends KeyPager { +abstract class ViewBasePageIterator extends KeyPageIterator { - ViewBasePager(Cloudant client, O options, OptionsHandler optsHandler) { + ViewBasePageIterator(Cloudant client, O options, OptionsHandler optsHandler) { super(client, options, optsHandler); } diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewPager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewPageIterator.java similarity index 91% rename from modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewPager.java rename to modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewPageIterator.java index 5e03b3b04..2bb58f78e 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewPager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewPageIterator.java @@ -22,9 +22,9 @@ import com.ibm.cloud.cloudant.v1.model.ViewResult; import com.ibm.cloud.sdk.core.http.ServiceCall; -final class ViewPager extends ViewBasePager { +final class ViewPageIterator extends ViewBasePageIterator { - ViewPager(Cloudant client, PostViewOptions options) { + ViewPageIterator(Cloudant client, PostViewOptions options) { super(client, options, OptionsHandler.POST_VIEW); } diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewPartitionPager.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewPartitionPageIterator.java similarity index 90% rename from modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewPartitionPager.java rename to modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewPartitionPageIterator.java index bbfdeaa1b..3663aa957 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewPartitionPager.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/ViewPartitionPageIterator.java @@ -22,9 +22,9 @@ import com.ibm.cloud.cloudant.v1.model.ViewResult; import com.ibm.cloud.sdk.core.http.ServiceCall; -final class ViewPartitionPager extends ViewBasePager { +final class ViewPartitionPageIterator extends ViewBasePageIterator { - ViewPartitionPager(Cloudant client, PostPartitionViewOptions options) { + ViewPartitionPageIterator(Cloudant client, PostPartitionViewOptions options) { super(client, options, OptionsHandler.POST_PARTITION_VIEW); } diff --git a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BasePagerTest.java b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BasePageIteratorTest.java similarity index 99% rename from modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BasePagerTest.java rename to modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BasePageIteratorTest.java index dfc3148df..d8bb10fff 100644 --- a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BasePagerTest.java +++ b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BasePageIteratorTest.java @@ -37,7 +37,7 @@ import static com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.getRequiredTestOptionsBuilder; import static com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.newPageSupplierFromList; -public class BasePagerTest { +public class BasePageIteratorTest { private Cloudant mockClient = new MockPagerClient(null); @@ -45,7 +45,7 @@ public class BasePagerTest { * This test sub-class of BasePager implicitly tests that various abstract methods are correctly * called by non-abstract methods in the BasePager. */ - private static class TestPager extends BasePager { + private static class TestPager extends BasePageIterator { protected TestPager(Cloudant client, PostViewOptions options) { super(client, options, OptionsHandler.POST_VIEW); diff --git a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BookmarkPagerTest.java b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BookmarkPageIteratorTest.java similarity index 98% rename from modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BookmarkPagerTest.java rename to modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BookmarkPageIteratorTest.java index 9750a0bef..5de76065b 100644 --- a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BookmarkPagerTest.java +++ b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/BookmarkPageIteratorTest.java @@ -30,7 +30,7 @@ import com.ibm.cloud.cloudant.v1.model.PostFindOptions.Builder; import com.ibm.cloud.sdk.core.http.ServiceCall; -public class BookmarkPagerTest { +public class BookmarkPageIteratorTest { private Cloudant mockClient = new MockPagerClient(null); @@ -38,7 +38,7 @@ public class BookmarkPagerTest { * This test sub-class of BookmarkPager implicitly tests that various abstract methods are * correctly called. */ - class TestBookmarkPager extends BookmarkPager { + class TestBookmarkPager extends BookmarkPageIterator { protected TestBookmarkPager(Cloudant client, PostFindOptions options) { super(client, options, OptionsHandler.POST_FIND); diff --git a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/KeyPagerTest.java b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/KeyPageIteratorTest.java similarity index 98% rename from modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/KeyPagerTest.java rename to modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/KeyPageIteratorTest.java index eee6a62bb..da3b6b5cc 100644 --- a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/KeyPagerTest.java +++ b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/KeyPageIteratorTest.java @@ -33,7 +33,7 @@ import static com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.getRequiredTestOptionsBuilder; import static com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.newKeyPageSupplier; -public class KeyPagerTest { +public class KeyPageIteratorTest { private Cloudant mockClient = new MockPagerClient(null); @@ -41,7 +41,7 @@ public class KeyPagerTest { * This test sub-class of KeyPager implicitly tests that various abstract methods are correctly * called. */ - class TestKeyPager extends KeyPager { + class TestKeyPager extends KeyPageIterator { protected TestKeyPager(Cloudant client, PostViewOptions options) { super(client, options, OptionsHandler.POST_VIEW); From fdf776f29940eebf8f02fbf3958a98a837633872 Mon Sep 17 00:00:00 2001 From: Rich Ellis Date: Tue, 25 Mar 2025 17:36:25 +0000 Subject: [PATCH 26/43] feat: add design docs pagination factory --- .../cloudant/features/pagination/Pagination.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/Pagination.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/Pagination.java index 9bc8e4602..8f518ccec 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/Pagination.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/Pagination.java @@ -25,6 +25,7 @@ import com.ibm.cloud.cloudant.v1.model.DocsResultRow; import com.ibm.cloud.cloudant.v1.model.Document; import com.ibm.cloud.cloudant.v1.model.PostAllDocsOptions; +import com.ibm.cloud.cloudant.v1.model.PostDesignDocsOptions; import com.ibm.cloud.cloudant.v1.model.PostFindOptions; import com.ibm.cloud.cloudant.v1.model.PostPartitionAllDocsOptions; import com.ibm.cloud.cloudant.v1.model.PostPartitionFindOptions; @@ -187,6 +188,20 @@ public static Pagination newPaginati OptionsHandler.duplicate(options), AllDocsPartitionPageIterator::new); } + /** + * Get a Pagination for the postPartitionAllDocs operation. The page size is configured with the + * limit paramater of the options. + * + * @param client com.ibm.cloud.cloudant.v1.Cloudant instance to make page requests + * @param options com.ibm.cloud.cloudant.v1.model.PostPartitionAllDocsOptions for the query + * @return a Pagination for all the documents in a database partition + */ + public static Pagination newPagination( + Cloudant client, PostDesignDocsOptions options) { + return new Pagination(client, + OptionsHandler.duplicate(options), DesignDocsPageIterator::new); + } + /** * Get a Pagination for the postFind operation. The page size is configured with the limit * paramater of the options. From 103df943226786d369e6a0e11dc5a4f10e0af9e5 Mon Sep 17 00:00:00 2001 From: Rich Ellis Date: Tue, 1 Apr 2025 13:43:40 +0100 Subject: [PATCH 27/43] docs: fix function -> method --- .../com/ibm/cloud/cloudant/features/pagination/Pagination.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/Pagination.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/Pagination.java index 8f518ccec..244b2c8b4 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/Pagination.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/Pagination.java @@ -109,7 +109,7 @@ public Iterator iterator() { /** * Get a new IBM Cloud SDK style Pager for the operation. * - * This type is useful for retrieving one page at a time through a function call. + * This type is useful for retrieving one page at a time from a method call. * * @return a new IBM Cloud SDK style Pager */ From 8eccb68181d7afec83104779a9f6a8506c1634b2 Mon Sep 17 00:00:00 2001 From: Rich Ellis Date: Mon, 28 Apr 2025 11:47:23 +0100 Subject: [PATCH 28/43] feat: add option validators outline --- .../features/pagination/OptionsHandler.java | 181 +++++++++++++++--- 1 file changed, 156 insertions(+), 25 deletions(-) diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/OptionsHandler.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/OptionsHandler.java index 6c0c5608c..1fd6a4016 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/OptionsHandler.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/OptionsHandler.java @@ -11,10 +11,13 @@ * specific language governing permissions and limitations under the License. */ - package com.ibm.cloud.cloudant.features.pagination; +import java.util.Collections; +import java.util.Map; +import java.util.Optional; import java.util.function.Function; +import java.util.function.Supplier; import com.ibm.cloud.cloudant.v1.model.PostAllDocsOptions; import com.ibm.cloud.cloudant.v1.model.PostDesignDocsOptions; import com.ibm.cloud.cloudant.v1.model.PostFindOptions; @@ -25,30 +28,19 @@ import com.ibm.cloud.cloudant.v1.model.PostSearchOptions; import com.ibm.cloud.cloudant.v1.model.PostViewOptions; -class OptionsHandler { +abstract class OptionsHandler { - static final OptionsHandler POST_ALL_DOCS = - new OptionsHandler<>(PostAllDocsOptions.Builder::build, PostAllDocsOptions::newBuilder); - static final OptionsHandler POST_DESIGN_DOCS = - new OptionsHandler<>(PostDesignDocsOptions.Builder::build, PostDesignDocsOptions::newBuilder); - static final OptionsHandler POST_FIND = - new OptionsHandler<>(PostFindOptions.Builder::build, PostFindOptions::newBuilder); + static final OptionsHandler POST_ALL_DOCS = new AllDocsOptionsHandler(); + static final OptionsHandler POST_DESIGN_DOCS = new DesignDocsOptionsHandler(); + static final OptionsHandler POST_FIND = new FindOptionsHandler(); static final OptionsHandler POST_PARTITION_ALL_DOCS = - new OptionsHandler<>(PostPartitionAllDocsOptions.Builder::build, - PostPartitionAllDocsOptions::newBuilder); - static final OptionsHandler POST_PARTITION_FIND = - new OptionsHandler<>(PostPartitionFindOptions.Builder::build, - PostPartitionFindOptions::newBuilder); + new PartitionAllDocsOptionsHandler(); + static final OptionsHandler POST_PARTITION_FIND = new PartitionFindOptionsHandler(); static final OptionsHandler POST_PARTITION_SEARCH = - new OptionsHandler<>(PostPartitionSearchOptions.Builder::build, - PostPartitionSearchOptions::newBuilder); - static final OptionsHandler POST_PARTITION_VIEW = - new OptionsHandler<>(PostPartitionViewOptions.Builder::build, - PostPartitionViewOptions::newBuilder); - static final OptionsHandler POST_SEARCH = - new OptionsHandler<>(PostSearchOptions.Builder::build, PostSearchOptions::newBuilder); - static final OptionsHandler POST_VIEW = - new OptionsHandler<>(PostViewOptions.Builder::build, PostViewOptions::newBuilder); + new PartitionSearchOptionsHandler(); + static final OptionsHandler POST_PARTITION_VIEW = new PartitionViewOptionsHandler(); + static final OptionsHandler POST_SEARCH = new SearchOptionsHandler(); + static final OptionsHandler POST_VIEW = new ViewOptionsHandler(); private final Function builderToOptions; private final Function optionsToBuilder; @@ -66,9 +58,7 @@ O optionsFromBuilder(B builder) { return this.builderToOptions.apply(builder); } - void validate(O options) { - throw new UnsupportedOperationException("Not yet implemented."); - } + abstract void validate(O options); O clone(O options) { return this.optionsFromBuilder(this.builderFromOptions(options)); @@ -110,4 +100,145 @@ static final PostViewOptions duplicate(PostViewOptions opts) { return POST_VIEW.clone(opts); } + private static boolean optionIsPresent(final Supplier optionSupplier) { + return Optional.ofNullable(optionSupplier.get()).isPresent(); + } + + private static void validateOptionsAbsent(final Map> options) { + for (Map.Entry> option : options.entrySet()) { + if (optionIsPresent(option.getValue())) { + throw new IllegalArgumentException( + String.format("The option '%s' is invalid when using pagination.", option.getKey()) + ); + } + } + } + + private static final class AllDocsOptionsHandler + extends OptionsHandler { + + private AllDocsOptionsHandler() { + super(PostAllDocsOptions.Builder::build, PostAllDocsOptions::newBuilder); + } + + @Override + void validate(PostAllDocsOptions options) { + throw new UnsupportedOperationException("Not yet implemented."); + } + + } + + private static final class DesignDocsOptionsHandler + extends OptionsHandler { + + private DesignDocsOptionsHandler() { + super(PostDesignDocsOptions.Builder::build, PostDesignDocsOptions::newBuilder); + } + + @Override + void validate(PostDesignDocsOptions options) { + throw new UnsupportedOperationException("Not yet implemented."); + } + + } + + private static final class FindOptionsHandler + extends OptionsHandler { + + private FindOptionsHandler() { + super(PostFindOptions.Builder::build, PostFindOptions::newBuilder); + } + + @Override + void validate(PostFindOptions options) { + throw new UnsupportedOperationException("Not yet implemented."); + } + + } + + private static final class PartitionAllDocsOptionsHandler + extends OptionsHandler { + + private PartitionAllDocsOptionsHandler() { + super(PostPartitionAllDocsOptions.Builder::build, PostPartitionAllDocsOptions::newBuilder); + } + + @Override + void validate(PostPartitionAllDocsOptions options) { + throw new UnsupportedOperationException("Not yet implemented."); + } + + } + + private static final class PartitionFindOptionsHandler + extends OptionsHandler { + + private PartitionFindOptionsHandler() { + super(PostPartitionFindOptions.Builder::build, PostPartitionFindOptions::newBuilder); + } + + @Override + void validate(PostPartitionFindOptions options) { + throw new UnsupportedOperationException("Not yet implemented."); + } + + } + + private static final class PartitionSearchOptionsHandler + extends OptionsHandler { + + private PartitionSearchOptionsHandler() { + super(PostPartitionSearchOptions.Builder::build, PostPartitionSearchOptions::newBuilder); + } + + @Override + void validate(PostPartitionSearchOptions options) { + throw new UnsupportedOperationException("Not yet implemented."); + } + + } + + private static final class PartitionViewOptionsHandler + extends OptionsHandler { + + private PartitionViewOptionsHandler() { + super(PostPartitionViewOptions.Builder::build, PostPartitionViewOptions::newBuilder); + } + + @Override + void validate(PostPartitionViewOptions options) { + throw new UnsupportedOperationException("Not yet implemented."); + } + + } + + private static final class SearchOptionsHandler + extends OptionsHandler { + + private SearchOptionsHandler() { + super(PostSearchOptions.Builder::build, PostSearchOptions::newBuilder); + } + + @Override + void validate(PostSearchOptions options) { + throw new UnsupportedOperationException("Not yet implemented."); + } + + } + + + private static final class ViewOptionsHandler + extends OptionsHandler { + + private ViewOptionsHandler() { + super(PostViewOptions.Builder::build, PostViewOptions::newBuilder); + } + + @Override + void validate(PostViewOptions options) { + throw new UnsupportedOperationException("Not yet implemented."); + } + + } + } From 02092f2639cfc2b341f396daa6fe54680a324b58 Mon Sep 17 00:00:00 2001 From: Rich Ellis Date: Mon, 28 Apr 2025 12:02:17 +0100 Subject: [PATCH 29/43] feat: add page size validation --- .../features/pagination/OptionsHandler.java | 21 ++ .../pagination/OptionsValidationTest.java | 258 ++++++++++++++++++ 2 files changed, 279 insertions(+) create mode 100644 modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/OptionsValidationTest.java diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/OptionsHandler.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/OptionsHandler.java index 1fd6a4016..de1d973d6 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/OptionsHandler.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/OptionsHandler.java @@ -42,6 +42,10 @@ abstract class OptionsHandler { static final OptionsHandler POST_SEARCH = new SearchOptionsHandler(); static final OptionsHandler POST_VIEW = new ViewOptionsHandler(); + // The maximum and minimum limit values (i.e. page size) + static final Long MAX_LIMIT = 200L; + static final Long MIN_LIMIT = 1L; + private final Function builderToOptions; private final Function optionsToBuilder; @@ -100,6 +104,23 @@ static final PostViewOptions duplicate(PostViewOptions opts) { return POST_VIEW.clone(opts); } + private static void validateLimit(Supplier limitSupplier) { + // If limit is set check it is within range + // Else it is unset and we will set the valid default value later + if (optionIsPresent(limitSupplier)) { + Long limit = limitSupplier.get(); + if (limit > MAX_LIMIT) { + throw new IllegalArgumentException(String.format( + "The provided limit %d exceeds the maximum page size value of %d.", limit, MAX_LIMIT)); + } + if (limit < MIN_LIMIT) { + throw new IllegalArgumentException( + String.format("The provided limit %d is lower than the minimum page size value of %d.", + limit, MIN_LIMIT)); + } + } + } + private static boolean optionIsPresent(final Supplier optionSupplier) { return Optional.ofNullable(optionSupplier.get()).isPresent(); } diff --git a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/OptionsValidationTest.java b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/OptionsValidationTest.java new file mode 100644 index 000000000..67f37e93d --- /dev/null +++ b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/OptionsValidationTest.java @@ -0,0 +1,258 @@ +/** + * © Copyright IBM Corporation 2025. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.ibm.cloud.cloudant.features.pagination; + +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; +import org.testng.Assert; +import com.ibm.cloud.cloudant.v1.model.PostAllDocsOptions; +import com.ibm.cloud.cloudant.v1.model.PostDesignDocsOptions; +import com.ibm.cloud.cloudant.v1.model.PostFindOptions; +import com.ibm.cloud.cloudant.v1.model.PostPartitionAllDocsOptions; +import com.ibm.cloud.cloudant.v1.model.PostPartitionFindOptions; +import com.ibm.cloud.cloudant.v1.model.PostPartitionSearchOptions; +import com.ibm.cloud.cloudant.v1.model.PostPartitionViewOptions; +import com.ibm.cloud.cloudant.v1.model.PostSearchOptions; +import com.ibm.cloud.cloudant.v1.model.PostViewOptions; + +public class OptionsValidationTest { + + private static final class OptionsWrapper { + + private final OptionsHandler handler; + private final Supplier builderSupplier; + + OptionsWrapper(OptionsHandler handler, Supplier builderSupplier) { + this.handler = handler; + this.builderSupplier = builderSupplier; + } + + OptionsProvider newProvider() { + return new OptionsProvider(this.handler, this.builderSupplier.get()); + } + + } + + private static final class OptionsProvider { + + private final OptionsHandler handler; + private final B builder; + + OptionsProvider(OptionsHandler handler, B builder) { + this.handler = handler; + this.builder = builder; + } + + void setRequiredOpts() throws Exception { + // database is always required + this.set("db", "testdb"); + + // For partitioned operations we need + // partitionKey + if (OptionsHandler.POST_PARTITION_ALL_DOCS.equals(handler) + || OptionsHandler.POST_PARTITION_FIND.equals(handler) + || OptionsHandler.POST_PARTITION_SEARCH.equals(handler) + || OptionsHandler.POST_PARTITION_VIEW.equals(handler)) { + this.set("partitionKey", "testpart"); + } + + // For find operations we need + // selector + if (OptionsHandler.POST_FIND.equals(handler) + || OptionsHandler.POST_PARTITION_FIND.equals(handler)) { + Map selector = Collections.emptyMap(); + this.set("selector", selector); + } + + // For search operations we need + // ddoc + // index + // query + if (OptionsHandler.POST_PARTITION_SEARCH.equals(handler) + || OptionsHandler.POST_SEARCH.equals(handler)) { + this.set("ddoc", "testddoc"); + this.set("index", "testsearchindex"); + this.set("query", "*:*"); + } + + // For view operations we need + // ddoc + // view + if (OptionsHandler.POST_PARTITION_VIEW.equals(handler) + || OptionsHandler.POST_VIEW.equals(handler)) { + this.set("ddoc", "testddoc"); + this.set("view", "testview"); + } + } + + void set(String name, Object... values) throws Exception { + List> argTypes = Arrays.asList(values).stream().map(a -> { + Class c = a.getClass(); + if (List.class.isAssignableFrom(c)) { + c = List.class; + } + if (Long.class.equals(c)) { + c = long.class; + } + if (Map.class.isAssignableFrom(c)) { + c = Map.class; + } + return c; + }).collect(Collectors.toList()); + Class[] argTypesArray = argTypes.toArray(new Class[argTypes.size()]); + Method method = this.builder.getClass().getMethod(name, argTypesArray); + method.invoke(this.builder, values); + } + + O build() { + return this.handler.optionsFromBuilder(this.builder); + } + + } + + List> allDocsOptions = List.of( + new OptionsWrapper<>(OptionsHandler.POST_ALL_DOCS, PostAllDocsOptions.Builder::new), + new OptionsWrapper<>(OptionsHandler.POST_PARTITION_ALL_DOCS, + PostPartitionAllDocsOptions.Builder::new), + new OptionsWrapper<>(OptionsHandler.POST_DESIGN_DOCS, PostDesignDocsOptions.Builder::new)); + + List> viewOptions = + List.of(new OptionsWrapper<>(OptionsHandler.POST_VIEW, PostViewOptions.Builder::new), + new OptionsWrapper<>(OptionsHandler.POST_PARTITION_VIEW, + PostPartitionViewOptions.Builder::new)); + + List> viewLikeOptions = + Stream.of(allDocsOptions, viewOptions).flatMap(List::stream).collect(Collectors.toList()); + + List> findOptions = + List.of(new OptionsWrapper<>(OptionsHandler.POST_FIND, PostFindOptions.Builder::new), + new OptionsWrapper<>(OptionsHandler.POST_PARTITION_FIND, + PostPartitionFindOptions.Builder::new)); + + List> searchOptions = + List.of(new OptionsWrapper<>(OptionsHandler.POST_SEARCH, PostSearchOptions.Builder::new), + new OptionsWrapper<>(OptionsHandler.POST_PARTITION_SEARCH, + PostPartitionSearchOptions.Builder::new)); + + // TODO add findOptions and searchOptions to allOptions when those validations are implemented + List> allOptions = + Stream.of(viewLikeOptions).flatMap(List::stream).collect(Collectors.toList()); + + @DataProvider(name = "allOptions") + public Iterator allOptions() { + return allOptions.stream().map(w -> { + return new Object[] {w.newProvider()}; + }).iterator(); + } + + @DataProvider(name = "findOptions") + public Iterator findOptions() { + return findOptions.stream().map(w -> { + return new Object[] {w.newProvider()}; + }).iterator(); + } + + @DataProvider(name = "searchOptions") + public Iterator searchOptions() { + return searchOptions.stream().map(w -> { + return new Object[] {w.newProvider()}; + }).iterator(); + } + + @DataProvider(name = "viewLikeOptions") + public Iterator viewLikeOptions() { + return viewLikeOptions.stream().map(w -> { + return new Object[] {w.newProvider()}; + }).iterator(); + } + + @Test(dataProvider = "allOptions") + public void testOptionsValidationLimitLessThanMin(OptionsProvider provider) + throws Exception { + provider.setRequiredOpts(); + provider.set("limit", OptionsHandler.MIN_LIMIT - 1); + Assert.assertThrows("There should be a validation exception", IllegalArgumentException.class, + () -> { + provider.handler.validate(provider.build()); + }); + } + + @Test(dataProvider = "allOptions") + public void testOptionsValidationLimitEqualToMin(OptionsProvider provider) + throws Exception { + provider.setRequiredOpts(); + provider.set("limit", OptionsHandler.MIN_LIMIT); + try { + provider.handler.validate(provider.build()); + } catch (IllegalArgumentException e) { + Assert.fail("There should be no validation exception.", e); + } + } + + @Test(dataProvider = "allOptions") + public void testOptionsValidationLimitLessThanMax(OptionsProvider provider) + throws Exception { + provider.setRequiredOpts(); + provider.set("limit", OptionsHandler.MAX_LIMIT - 1); + try { + provider.handler.validate(provider.build()); + } catch (IllegalArgumentException e) { + Assert.fail("There should be no validation exception.", e); + } + } + + @Test(dataProvider = "allOptions") + public void testOptionsValidationLimitEqualToMax(OptionsProvider provider) + throws Exception { + provider.setRequiredOpts(); + provider.set("limit", OptionsHandler.MAX_LIMIT); + try { + provider.handler.validate(provider.build()); + } catch (IllegalArgumentException e) { + Assert.fail("There should be no validation exception.", e); + } + } + + @Test(dataProvider = "allOptions") + public void testOptionsValidationLimitGreaterThanMax(OptionsProvider provider) + throws Exception { + provider.setRequiredOpts(); + provider.set("limit", OptionsHandler.MAX_LIMIT + 1); + Assert.assertThrows("There should be a validation exception", IllegalArgumentException.class, + () -> { + provider.handler.validate(provider.build()); + }); + } + + @Test(dataProvider = "allOptions") + public void testOptionsValidationLimitUnset(OptionsProvider provider) + throws Exception { + provider.setRequiredOpts(); + try { + provider.handler.validate(provider.build()); + } catch (IllegalArgumentException e) { + Assert.fail("There should be no validation exception.", e); + } + } + +} From 887b3a132358cbda6c97e55a9b33d9e9ca1105ea Mon Sep 17 00:00:00 2001 From: Rich Ellis Date: Mon, 28 Apr 2025 13:51:24 +0100 Subject: [PATCH 30/43] feat: add opt validation for view-like operations --- .../features/pagination/OptionsHandler.java | 39 ++++++++++++------- .../features/pagination/Pagination.java | 5 +++ .../pagination/OptionsValidationTest.java | 11 ++++++ 3 files changed, 41 insertions(+), 14 deletions(-) diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/OptionsHandler.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/OptionsHandler.java index de1d973d6..ccb0dd2b5 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/OptionsHandler.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/OptionsHandler.java @@ -30,17 +30,24 @@ abstract class OptionsHandler { - static final OptionsHandler POST_ALL_DOCS = new AllDocsOptionsHandler(); - static final OptionsHandler POST_DESIGN_DOCS = new DesignDocsOptionsHandler(); - static final OptionsHandler POST_FIND = new FindOptionsHandler(); + static final OptionsHandler POST_ALL_DOCS = + new AllDocsOptionsHandler(); + static final OptionsHandler POST_DESIGN_DOCS = + new DesignDocsOptionsHandler(); + static final OptionsHandler POST_FIND = + new FindOptionsHandler(); static final OptionsHandler POST_PARTITION_ALL_DOCS = new PartitionAllDocsOptionsHandler(); - static final OptionsHandler POST_PARTITION_FIND = new PartitionFindOptionsHandler(); + static final OptionsHandler POST_PARTITION_FIND = + new PartitionFindOptionsHandler(); static final OptionsHandler POST_PARTITION_SEARCH = new PartitionSearchOptionsHandler(); - static final OptionsHandler POST_PARTITION_VIEW = new PartitionViewOptionsHandler(); - static final OptionsHandler POST_SEARCH = new SearchOptionsHandler(); - static final OptionsHandler POST_VIEW = new ViewOptionsHandler(); + static final OptionsHandler POST_PARTITION_VIEW = + new PartitionViewOptionsHandler(); + static final OptionsHandler POST_SEARCH = + new SearchOptionsHandler(); + static final OptionsHandler POST_VIEW = + new ViewOptionsHandler(); // The maximum and minimum limit values (i.e. page size) static final Long MAX_LIMIT = 200L; @@ -129,8 +136,7 @@ private static void validateOptionsAbsent(final Map> options for (Map.Entry> option : options.entrySet()) { if (optionIsPresent(option.getValue())) { throw new IllegalArgumentException( - String.format("The option '%s' is invalid when using pagination.", option.getKey()) - ); + String.format("The option '%s' is invalid when using pagination.", option.getKey())); } } } @@ -144,7 +150,8 @@ private AllDocsOptionsHandler() { @Override void validate(PostAllDocsOptions options) { - throw new UnsupportedOperationException("Not yet implemented."); + validateLimit(options::limit); + validateOptionsAbsent(Collections.singletonMap("keys", options::keys)); } } @@ -158,7 +165,8 @@ private DesignDocsOptionsHandler() { @Override void validate(PostDesignDocsOptions options) { - throw new UnsupportedOperationException("Not yet implemented."); + validateLimit(options::limit); + validateOptionsAbsent(Collections.singletonMap("keys", options::keys)); } } @@ -186,7 +194,8 @@ private PartitionAllDocsOptionsHandler() { @Override void validate(PostPartitionAllDocsOptions options) { - throw new UnsupportedOperationException("Not yet implemented."); + validateLimit(options::limit); + validateOptionsAbsent(Collections.singletonMap("keys", options::keys)); } } @@ -228,7 +237,8 @@ private PartitionViewOptionsHandler() { @Override void validate(PostPartitionViewOptions options) { - throw new UnsupportedOperationException("Not yet implemented."); + validateLimit(options::limit); + validateOptionsAbsent(Collections.singletonMap("keys", options::keys)); } } @@ -257,7 +267,8 @@ private ViewOptionsHandler() { @Override void validate(PostViewOptions options) { - throw new UnsupportedOperationException("Not yet implemented."); + validateLimit(options::limit); + validateOptionsAbsent(Collections.singletonMap("keys", options::keys)); } } diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/Pagination.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/Pagination.java index 244b2c8b4..c613e50aa 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/Pagination.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/Pagination.java @@ -170,6 +170,7 @@ public Stream rowStream() { */ public static Pagination newPagination(Cloudant client, PostAllDocsOptions options) { + OptionsHandler.POST_ALL_DOCS.validate(options); return new Pagination(client, OptionsHandler.duplicate(options), AllDocsPageIterator::new); } @@ -184,6 +185,7 @@ public static Pagination newPagination(Clouda */ public static Pagination newPagination( Cloudant client, PostPartitionAllDocsOptions options) { + OptionsHandler.POST_PARTITION_ALL_DOCS.validate(options); return new Pagination(client, OptionsHandler.duplicate(options), AllDocsPartitionPageIterator::new); } @@ -198,6 +200,7 @@ public static Pagination newPaginati */ public static Pagination newPagination( Cloudant client, PostDesignDocsOptions options) { + OptionsHandler.POST_DESIGN_DOCS.validate(options); return new Pagination(client, OptionsHandler.duplicate(options), DesignDocsPageIterator::new); } @@ -267,6 +270,7 @@ public static Pagination newPaginat */ public static Pagination newPagination(Cloudant client, PostViewOptions options) { + OptionsHandler.POST_VIEW.validate(options); return new Pagination(client, OptionsHandler.duplicate(options), ViewPageIterator::new); } @@ -281,6 +285,7 @@ public static Pagination newPagination(Cloudant */ public static Pagination newPagination(Cloudant client, PostPartitionViewOptions options) { + OptionsHandler.POST_PARTITION_VIEW.validate(options); return new Pagination(client, OptionsHandler.duplicate(options), ViewPartitionPageIterator::new); } diff --git a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/OptionsValidationTest.java b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/OptionsValidationTest.java index 67f37e93d..2fe9987f1 100644 --- a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/OptionsValidationTest.java +++ b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/OptionsValidationTest.java @@ -255,4 +255,15 @@ public void testOptionsValidationLimitUnset(OptionsProvider provide } } + @Test(dataProvider = "viewLikeOptions") + public void testValidationExceptionForKeys(OptionsProvider provider) + throws Exception { + provider.setRequiredOpts(); + provider.set("keys", List.of("key1", "key2")); + Assert.assertThrows("There should be a validation exception", IllegalArgumentException.class, + () -> { + provider.handler.validate(provider.build()); + }); + } + } From 5cfaeadc9960ae51e039854afed092dc64d85923 Mon Sep 17 00:00:00 2001 From: Rich Ellis Date: Tue, 29 Apr 2025 14:11:49 +0100 Subject: [PATCH 31/43] feat: query option validation --- .../cloud/cloudant/features/pagination/OptionsHandler.java | 4 ++-- .../ibm/cloud/cloudant/features/pagination/Pagination.java | 2 ++ .../cloudant/features/pagination/OptionsValidationTest.java | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/OptionsHandler.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/OptionsHandler.java index ccb0dd2b5..bfb0d53de 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/OptionsHandler.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/OptionsHandler.java @@ -180,7 +180,7 @@ private FindOptionsHandler() { @Override void validate(PostFindOptions options) { - throw new UnsupportedOperationException("Not yet implemented."); + validateLimit(options::limit); } } @@ -209,7 +209,7 @@ private PartitionFindOptionsHandler() { @Override void validate(PostPartitionFindOptions options) { - throw new UnsupportedOperationException("Not yet implemented."); + validateLimit(options::limit); } } diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/Pagination.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/Pagination.java index c613e50aa..bf2642b12 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/Pagination.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/Pagination.java @@ -215,6 +215,7 @@ public static Pagination newPagination( */ public static Pagination newPagination(Cloudant client, PostFindOptions options) { + OptionsHandler.POST_FIND.validate(options); return new Pagination(client, OptionsHandler.duplicate(options), FindPager::new); } @@ -228,6 +229,7 @@ public static Pagination newPagination(Cloudant clien */ public static Pagination newPagination(Cloudant client, PostPartitionFindOptions options) { + OptionsHandler.POST_PARTITION_FIND.validate(options); return new Pagination(client, OptionsHandler.duplicate(options), FindPartitionPager::new); } diff --git a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/OptionsValidationTest.java b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/OptionsValidationTest.java index 2fe9987f1..3c9429c85 100644 --- a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/OptionsValidationTest.java +++ b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/OptionsValidationTest.java @@ -154,9 +154,9 @@ O build() { new OptionsWrapper<>(OptionsHandler.POST_PARTITION_SEARCH, PostPartitionSearchOptions.Builder::new)); - // TODO add findOptions and searchOptions to allOptions when those validations are implemented + // TODO add searchOptions to allOptions when that validation is implemented List> allOptions = - Stream.of(viewLikeOptions).flatMap(List::stream).collect(Collectors.toList()); + Stream.of(findOptions, viewLikeOptions).flatMap(List::stream).collect(Collectors.toList()); @DataProvider(name = "allOptions") public Iterator allOptions() { From 55bc9d7f00524a8b745b90b18bb82cd153ea44e8 Mon Sep 17 00:00:00 2001 From: Rich Ellis Date: Tue, 29 Apr 2025 16:38:06 +0100 Subject: [PATCH 32/43] feat: search option validation --- .../features/pagination/OptionsHandler.java | 13 +++++-- .../features/pagination/Pagination.java | 2 + .../pagination/OptionsValidationTest.java | 38 ++++++++++++++++--- 3 files changed, 44 insertions(+), 9 deletions(-) diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/OptionsHandler.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/OptionsHandler.java index bfb0d53de..13523e438 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/OptionsHandler.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/OptionsHandler.java @@ -14,6 +14,7 @@ package com.ibm.cloud.cloudant.features.pagination; import java.util.Collections; +import java.util.HashMap; import java.util.Map; import java.util.Optional; import java.util.function.Function; @@ -223,7 +224,7 @@ private PartitionSearchOptionsHandler() { @Override void validate(PostPartitionSearchOptions options) { - throw new UnsupportedOperationException("Not yet implemented."); + validateLimit(options::limit); } } @@ -252,12 +253,18 @@ private SearchOptionsHandler() { @Override void validate(PostSearchOptions options) { - throw new UnsupportedOperationException("Not yet implemented."); + validateLimit(options::limit); + Map> invalidOptions = new HashMap<>(5); + invalidOptions.put("counts", options::counts); + invalidOptions.put("groupField", options::groupField); + invalidOptions.put("groupLimit", options::groupLimit); + invalidOptions.put("groupSort", options::groupSort); + invalidOptions.put("ranges", options::ranges); + validateOptionsAbsent(invalidOptions); } } - private static final class ViewOptionsHandler extends OptionsHandler { diff --git a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/Pagination.java b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/Pagination.java index bf2642b12..9271c704d 100644 --- a/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/Pagination.java +++ b/modules/cloudant/src/main/java/com/ibm/cloud/cloudant/features/pagination/Pagination.java @@ -244,6 +244,7 @@ public static Pagination newPagination(Cloud */ public static Pagination newPagination(Cloudant client, PostSearchOptions options) { + OptionsHandler.POST_SEARCH.validate(options); return new Pagination(client, OptionsHandler.duplicate(options), SearchPageIterator::new); } @@ -258,6 +259,7 @@ public static Pagination newPagination(Cloud */ public static Pagination newPagination( Cloudant client, PostPartitionSearchOptions options) { + OptionsHandler.POST_PARTITION_SEARCH.validate(options); return new Pagination(client, OptionsHandler.duplicate(options), SearchPartitionPageIterator::new); } diff --git a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/OptionsValidationTest.java b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/OptionsValidationTest.java index 3c9429c85..10ce270db 100644 --- a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/OptionsValidationTest.java +++ b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/OptionsValidationTest.java @@ -14,6 +14,7 @@ package com.ibm.cloud.cloudant.features.pagination; import java.lang.reflect.Method; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; @@ -37,6 +38,9 @@ public class OptionsValidationTest { + private static final OptionsWrapper SEARCH_OPTS_WRAPPER = + new OptionsWrapper<>(OptionsHandler.POST_SEARCH, PostSearchOptions.Builder::new); + private static final class OptionsWrapper { private final OptionsHandler handler; @@ -150,13 +154,11 @@ O build() { PostPartitionFindOptions.Builder::new)); List> searchOptions = - List.of(new OptionsWrapper<>(OptionsHandler.POST_SEARCH, PostSearchOptions.Builder::new), - new OptionsWrapper<>(OptionsHandler.POST_PARTITION_SEARCH, - PostPartitionSearchOptions.Builder::new)); + List.of(SEARCH_OPTS_WRAPPER, new OptionsWrapper<>(OptionsHandler.POST_PARTITION_SEARCH, + PostPartitionSearchOptions.Builder::new)); - // TODO add searchOptions to allOptions when that validation is implemented - List> allOptions = - Stream.of(findOptions, viewLikeOptions).flatMap(List::stream).collect(Collectors.toList()); + List> allOptions = Stream.of(findOptions, searchOptions, viewLikeOptions) + .flatMap(List::stream).collect(Collectors.toList()); @DataProvider(name = "allOptions") public Iterator allOptions() { @@ -179,6 +181,18 @@ public Iterator searchOptions() { }).iterator(); } + @DataProvider(name = "searchFacetOptions") + public Iterator facets() { + List options = new ArrayList<>(5); + options.add(new Object[] {"counts", Collections.singletonList("aTestFieldToCount")}); + options.add(new Object[] {"groupField", "testField"}); + options.add(new Object[] {"groupLimit", 6L}); + options.add(new Object[] {"groupSort", Collections.singletonList("aTestFieldToGroupSort")}); + options.add(new Object[] {"ranges", Collections.singletonMap("aTestFieldForRanges", + Map.of("low", "[0 TO 5}", "high", "[5 TO 10]"))}); + return options.iterator(); + } + @DataProvider(name = "viewLikeOptions") public Iterator viewLikeOptions() { return viewLikeOptions.stream().map(w -> { @@ -266,4 +280,16 @@ public void testValidationExceptionForKeys(OptionsProvider provider }); } + public void testValidationExceptionForFacetedSearch(String optionName, Object optionValue) + throws Exception { + OptionsProvider provider = + SEARCH_OPTS_WRAPPER.newProvider(); + provider.setRequiredOpts(); + provider.set(optionName, optionValue); + Assert.assertThrows("There should be a validation exception", IllegalArgumentException.class, + () -> { + provider.handler.validate(provider.build()); + }); + } + } From ec547aedcc8d4ac36a29c07a5cbf934cbfa9c050 Mon Sep 17 00:00:00 2001 From: Rich Ellis Date: Thu, 8 May 2025 11:44:23 +0100 Subject: [PATCH 33/43] test: fix search facet options validation test --- .../cloudant/features/pagination/OptionsValidationTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/OptionsValidationTest.java b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/OptionsValidationTest.java index 10ce270db..25822b23c 100644 --- a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/OptionsValidationTest.java +++ b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/OptionsValidationTest.java @@ -280,6 +280,7 @@ public void testValidationExceptionForKeys(OptionsProvider provider }); } + @Test(dataProvider = "searchFacetOptions") public void testValidationExceptionForFacetedSearch(String optionName, Object optionValue) throws Exception { OptionsProvider provider = From 946adac055e3ea7d5dca5d090741a33b1af1eea9 Mon Sep 17 00:00:00 2001 From: Rich Ellis Date: Tue, 13 May 2025 16:04:05 +0100 Subject: [PATCH 34/43] test: refactor pagination test helper --- .../pagination/OptionsValidationTest.java | 200 +--------- .../pagination/PaginationTestHelpers.java | 369 ++++++++++++++++-- 2 files changed, 352 insertions(+), 217 deletions(-) diff --git a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/OptionsValidationTest.java b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/OptionsValidationTest.java index 25822b23c..b1979b113 100644 --- a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/OptionsValidationTest.java +++ b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/OptionsValidationTest.java @@ -13,194 +13,16 @@ package com.ibm.cloud.cloudant.features.pagination; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Iterator; import java.util.List; -import java.util.Map; -import java.util.function.Supplier; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import org.testng.Assert; -import com.ibm.cloud.cloudant.v1.model.PostAllDocsOptions; -import com.ibm.cloud.cloudant.v1.model.PostDesignDocsOptions; -import com.ibm.cloud.cloudant.v1.model.PostFindOptions; -import com.ibm.cloud.cloudant.v1.model.PostPartitionAllDocsOptions; -import com.ibm.cloud.cloudant.v1.model.PostPartitionFindOptions; -import com.ibm.cloud.cloudant.v1.model.PostPartitionSearchOptions; -import com.ibm.cloud.cloudant.v1.model.PostPartitionViewOptions; +import com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.OptionsProvider; +import com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.OptionsWrapper; import com.ibm.cloud.cloudant.v1.model.PostSearchOptions; -import com.ibm.cloud.cloudant.v1.model.PostViewOptions; public class OptionsValidationTest { - private static final OptionsWrapper SEARCH_OPTS_WRAPPER = - new OptionsWrapper<>(OptionsHandler.POST_SEARCH, PostSearchOptions.Builder::new); - - private static final class OptionsWrapper { - - private final OptionsHandler handler; - private final Supplier builderSupplier; - - OptionsWrapper(OptionsHandler handler, Supplier builderSupplier) { - this.handler = handler; - this.builderSupplier = builderSupplier; - } - - OptionsProvider newProvider() { - return new OptionsProvider(this.handler, this.builderSupplier.get()); - } - - } - - private static final class OptionsProvider { - - private final OptionsHandler handler; - private final B builder; - - OptionsProvider(OptionsHandler handler, B builder) { - this.handler = handler; - this.builder = builder; - } - - void setRequiredOpts() throws Exception { - // database is always required - this.set("db", "testdb"); - - // For partitioned operations we need - // partitionKey - if (OptionsHandler.POST_PARTITION_ALL_DOCS.equals(handler) - || OptionsHandler.POST_PARTITION_FIND.equals(handler) - || OptionsHandler.POST_PARTITION_SEARCH.equals(handler) - || OptionsHandler.POST_PARTITION_VIEW.equals(handler)) { - this.set("partitionKey", "testpart"); - } - - // For find operations we need - // selector - if (OptionsHandler.POST_FIND.equals(handler) - || OptionsHandler.POST_PARTITION_FIND.equals(handler)) { - Map selector = Collections.emptyMap(); - this.set("selector", selector); - } - - // For search operations we need - // ddoc - // index - // query - if (OptionsHandler.POST_PARTITION_SEARCH.equals(handler) - || OptionsHandler.POST_SEARCH.equals(handler)) { - this.set("ddoc", "testddoc"); - this.set("index", "testsearchindex"); - this.set("query", "*:*"); - } - - // For view operations we need - // ddoc - // view - if (OptionsHandler.POST_PARTITION_VIEW.equals(handler) - || OptionsHandler.POST_VIEW.equals(handler)) { - this.set("ddoc", "testddoc"); - this.set("view", "testview"); - } - } - - void set(String name, Object... values) throws Exception { - List> argTypes = Arrays.asList(values).stream().map(a -> { - Class c = a.getClass(); - if (List.class.isAssignableFrom(c)) { - c = List.class; - } - if (Long.class.equals(c)) { - c = long.class; - } - if (Map.class.isAssignableFrom(c)) { - c = Map.class; - } - return c; - }).collect(Collectors.toList()); - Class[] argTypesArray = argTypes.toArray(new Class[argTypes.size()]); - Method method = this.builder.getClass().getMethod(name, argTypesArray); - method.invoke(this.builder, values); - } - - O build() { - return this.handler.optionsFromBuilder(this.builder); - } - - } - - List> allDocsOptions = List.of( - new OptionsWrapper<>(OptionsHandler.POST_ALL_DOCS, PostAllDocsOptions.Builder::new), - new OptionsWrapper<>(OptionsHandler.POST_PARTITION_ALL_DOCS, - PostPartitionAllDocsOptions.Builder::new), - new OptionsWrapper<>(OptionsHandler.POST_DESIGN_DOCS, PostDesignDocsOptions.Builder::new)); - - List> viewOptions = - List.of(new OptionsWrapper<>(OptionsHandler.POST_VIEW, PostViewOptions.Builder::new), - new OptionsWrapper<>(OptionsHandler.POST_PARTITION_VIEW, - PostPartitionViewOptions.Builder::new)); - - List> viewLikeOptions = - Stream.of(allDocsOptions, viewOptions).flatMap(List::stream).collect(Collectors.toList()); - - List> findOptions = - List.of(new OptionsWrapper<>(OptionsHandler.POST_FIND, PostFindOptions.Builder::new), - new OptionsWrapper<>(OptionsHandler.POST_PARTITION_FIND, - PostPartitionFindOptions.Builder::new)); - - List> searchOptions = - List.of(SEARCH_OPTS_WRAPPER, new OptionsWrapper<>(OptionsHandler.POST_PARTITION_SEARCH, - PostPartitionSearchOptions.Builder::new)); - - List> allOptions = Stream.of(findOptions, searchOptions, viewLikeOptions) - .flatMap(List::stream).collect(Collectors.toList()); - - @DataProvider(name = "allOptions") - public Iterator allOptions() { - return allOptions.stream().map(w -> { - return new Object[] {w.newProvider()}; - }).iterator(); - } - - @DataProvider(name = "findOptions") - public Iterator findOptions() { - return findOptions.stream().map(w -> { - return new Object[] {w.newProvider()}; - }).iterator(); - } - - @DataProvider(name = "searchOptions") - public Iterator searchOptions() { - return searchOptions.stream().map(w -> { - return new Object[] {w.newProvider()}; - }).iterator(); - } - - @DataProvider(name = "searchFacetOptions") - public Iterator facets() { - List options = new ArrayList<>(5); - options.add(new Object[] {"counts", Collections.singletonList("aTestFieldToCount")}); - options.add(new Object[] {"groupField", "testField"}); - options.add(new Object[] {"groupLimit", 6L}); - options.add(new Object[] {"groupSort", Collections.singletonList("aTestFieldToGroupSort")}); - options.add(new Object[] {"ranges", Collections.singletonMap("aTestFieldForRanges", - Map.of("low", "[0 TO 5}", "high", "[5 TO 10]"))}); - return options.iterator(); - } - - @DataProvider(name = "viewLikeOptions") - public Iterator viewLikeOptions() { - return viewLikeOptions.stream().map(w -> { - return new Object[] {w.newProvider()}; - }).iterator(); - } - - @Test(dataProvider = "allOptions") + @Test(dataProvider = "allOptions", dataProviderClass = PaginationTestHelpers.class) public void testOptionsValidationLimitLessThanMin(OptionsProvider provider) throws Exception { provider.setRequiredOpts(); @@ -211,7 +33,7 @@ public void testOptionsValidationLimitLessThanMin(OptionsProvider p }); } - @Test(dataProvider = "allOptions") + @Test(dataProvider = "allOptions", dataProviderClass = PaginationTestHelpers.class) public void testOptionsValidationLimitEqualToMin(OptionsProvider provider) throws Exception { provider.setRequiredOpts(); @@ -223,7 +45,7 @@ public void testOptionsValidationLimitEqualToMin(OptionsProvider pr } } - @Test(dataProvider = "allOptions") + @Test(dataProvider = "allOptions", dataProviderClass = PaginationTestHelpers.class) public void testOptionsValidationLimitLessThanMax(OptionsProvider provider) throws Exception { provider.setRequiredOpts(); @@ -235,7 +57,7 @@ public void testOptionsValidationLimitLessThanMax(OptionsProvider p } } - @Test(dataProvider = "allOptions") + @Test(dataProvider = "allOptions", dataProviderClass = PaginationTestHelpers.class) public void testOptionsValidationLimitEqualToMax(OptionsProvider provider) throws Exception { provider.setRequiredOpts(); @@ -247,7 +69,7 @@ public void testOptionsValidationLimitEqualToMax(OptionsProvider pr } } - @Test(dataProvider = "allOptions") + @Test(dataProvider = "allOptions", dataProviderClass = PaginationTestHelpers.class) public void testOptionsValidationLimitGreaterThanMax(OptionsProvider provider) throws Exception { provider.setRequiredOpts(); @@ -258,7 +80,7 @@ public void testOptionsValidationLimitGreaterThanMax(OptionsProvider void testOptionsValidationLimitUnset(OptionsProvider provider) throws Exception { provider.setRequiredOpts(); @@ -269,7 +91,7 @@ public void testOptionsValidationLimitUnset(OptionsProvider provide } } - @Test(dataProvider = "viewLikeOptions") + @Test(dataProvider = "viewLikeOptions", dataProviderClass = PaginationTestHelpers.class) public void testValidationExceptionForKeys(OptionsProvider provider) throws Exception { provider.setRequiredOpts(); @@ -280,11 +102,11 @@ public void testValidationExceptionForKeys(OptionsProvider provider }); } - @Test(dataProvider = "searchFacetOptions") + @Test(dataProvider = "searchFacetOptions", dataProviderClass = PaginationTestHelpers.class) public void testValidationExceptionForFacetedSearch(String optionName, Object optionValue) throws Exception { OptionsProvider provider = - SEARCH_OPTS_WRAPPER.newProvider(); + OptionsWrapper.POST_SEARCH.newProvider(); provider.setRequiredOpts(); provider.set(optionName, optionValue); Assert.assertThrows("There should be a validation exception", IllegalArgumentException.class, diff --git a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/PaginationTestHelpers.java b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/PaginationTestHelpers.java index f0d104d1e..b4af37307 100644 --- a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/PaginationTestHelpers.java +++ b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/PaginationTestHelpers.java @@ -14,35 +14,114 @@ package com.ibm.cloud.cloudant.features.pagination; +import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; +import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.testng.annotations.DataProvider; import com.ibm.cloud.cloudant.features.MockCloudant; import com.ibm.cloud.cloudant.features.MockCloudant.MockInstruction; import com.ibm.cloud.cloudant.features.MockCloudant.QueuedSupplier; +import com.ibm.cloud.cloudant.v1.model.AllDocsResult; +import com.ibm.cloud.cloudant.v1.model.DocsResultRow; +import com.ibm.cloud.cloudant.v1.model.Document; +import com.ibm.cloud.cloudant.v1.model.FindResult; +import com.ibm.cloud.cloudant.v1.model.PostAllDocsOptions; +import com.ibm.cloud.cloudant.v1.model.PostDesignDocsOptions; import com.ibm.cloud.cloudant.v1.model.PostFindOptions; +import com.ibm.cloud.cloudant.v1.model.PostPartitionAllDocsOptions; +import com.ibm.cloud.cloudant.v1.model.PostPartitionFindOptions; +import com.ibm.cloud.cloudant.v1.model.PostPartitionSearchOptions; +import com.ibm.cloud.cloudant.v1.model.PostPartitionViewOptions; +import com.ibm.cloud.cloudant.v1.model.PostSearchOptions; import com.ibm.cloud.cloudant.v1.model.PostViewOptions; +import com.ibm.cloud.cloudant.v1.model.SearchResult; +import com.ibm.cloud.cloudant.v1.model.SearchResultRow; import com.ibm.cloud.cloudant.v1.model.ViewResult; import com.ibm.cloud.cloudant.v1.model.ViewResultRow; import com.ibm.cloud.sdk.core.http.ServiceCall; public class PaginationTestHelpers { - static class MockPagerClient extends MockCloudant { + static class MockPagerCloudant extends MockCloudant { - MockPagerClient(Supplier> instructionSupplier) { + MockPagerCloudant(Supplier> instructionSupplier) { super(instructionSupplier); } - ServiceCall testCall() { + @Override + public com.ibm.cloud.sdk.core.http.ServiceCall postAllDocs( + PostAllDocsOptions postAllDocsOptions) { + return (ServiceCall) this.testCall(); + } + + @Override + public com.ibm.cloud.sdk.core.http.ServiceCall postDesignDocs( + PostDesignDocsOptions postDesignDocsOptions) { + return (ServiceCall) this.testCall(); + } + + @Override + public com.ibm.cloud.sdk.core.http.ServiceCall postFind( + PostFindOptions postFindOptions) { + return (ServiceCall) this.testCall(); + } + + @Override + public com.ibm.cloud.sdk.core.http.ServiceCall postPartitionAllDocs( + PostPartitionAllDocsOptions postPartitionAllDocsOptions) { + return (ServiceCall) this.testCall(); + } + + @Override + public com.ibm.cloud.sdk.core.http.ServiceCall postPartitionFind( + PostPartitionFindOptions postPartitionFindOptions) { + return (ServiceCall) this.testCall(); + } + + @Override + public com.ibm.cloud.sdk.core.http.ServiceCall postPartitionSearch( + PostPartitionSearchOptions postPartitionSearchOptions) { + return (ServiceCall) this.testCall(); + } + + @Override + public com.ibm.cloud.sdk.core.http.ServiceCall postPartitionView( + PostPartitionViewOptions postPartitionViewOptions) { + return (ServiceCall) this.testCall(); + } + + @Override + public com.ibm.cloud.sdk.core.http.ServiceCall postSearch( + PostSearchOptions postSearchOptions) { + return (ServiceCall) this.testCall(); + } + + @Override + public com.ibm.cloud.sdk.core.http.ServiceCall postView( + PostViewOptions postViewOptions) { + return (ServiceCall) this.testCall(); + } + + ServiceCall testCall() { return new MockServiceCall(mocks.get()); } } + static class MockPagerClient extends MockPagerCloudant { + MockPagerClient(Supplier> instructionSupplier) { + super(instructionSupplier); + } + } + static class TestResult { private List rows; @@ -55,15 +134,22 @@ List getRows() { } } - private static class PageSupplierFactory { + static class PageSupplierFactory { - Function, R> itemsToPageResultFn; - Function integerRowWrapFn; + final boolean plusOnePaging; + final Function, R> itemsToPageResultFn; + final Function integerRowWrapFn; PageSupplierFactory(Function, R> itemsToPageResultFn, Function integerRowWrapFn) { + this(itemsToPageResultFn, integerRowWrapFn, false); + } + + PageSupplierFactory(Function, R> itemsToPageResultFn, + Function integerRowWrapFn, boolean plusOnePaging) { this.itemsToPageResultFn = itemsToPageResultFn; this.integerRowWrapFn = integerRowWrapFn; + this.plusOnePaging = plusOnePaging; } private List> getPages(int total, int pageSize) { @@ -82,13 +168,14 @@ private List> getPages(int total, int pageSize) { } PageSupplier newPageSupplier(int total, int pageSize) { - return new PageSupplier(this.itemsToPageResultFn, getPages(total, pageSize)); - } - - PageSupplier newExtraRowPageSupplier(int total, int pageSize) { List> pages = getPages(total, pageSize); - List> responsePages = repageForKeyBased(pages); - return new PageSupplier(this.itemsToPageResultFn, pages, responsePages); + if (this.plusOnePaging) { + List> responsePages = repageForKeyBased(pages); + return new PageSupplier(this.itemsToPageResultFn, pages, responsePages); + } else { + return new PageSupplier(this.itemsToPageResultFn, pages); + } + } private List> repageForKeyBased(List> pages) { @@ -129,32 +216,19 @@ private PageSupplier(Function, R> itemsToPageFn, List> pages, } } - private static class TestViewResultRow extends ViewResultRow { - private TestViewResultRow(Integer i) { - this.id = "testdoc" + String.valueOf(i); - this.key = i; - } - } - - private static class TestViewResult extends ViewResult { - private TestViewResult(List rows) { - this.rows = rows; - } - } - static PageSupplier newBasePageSupplier(int total, int pageSize) { return new PageSupplierFactory<>(TestResult::new, Function.identity()).newPageSupplier(total, pageSize); } static PageSupplier newKeyPageSupplier(int total, int pageSize) { - return new PageSupplierFactory(TestResult::new, Function.identity()) - .newExtraRowPageSupplier(total, pageSize); + return new PageSupplierFactory(TestResult::new, Function.identity(), true) + .newPageSupplier(total, pageSize); } static PageSupplier newViewPageSupplier(int total, int pageSize) { return new PageSupplierFactory(TestViewResult::new, - TestViewResultRow::new).newExtraRowPageSupplier(total, pageSize); + TestViewResultRow::new, true).newPageSupplier(total, pageSize); } static PageSupplier newPageSupplierFromList(List> pages) { @@ -178,4 +252,243 @@ static PostFindOptions.Builder getRequiredTestFindOptionsBuilder() { .selector(Collections.singletonMap("testField", "testValue")); } + static final class OptionsWrapper { + + static final OptionsWrapper POST_ALL_DOCS = + new OptionsWrapper<>(OptionsHandler.POST_ALL_DOCS, PostAllDocsOptions.Builder::new); + static final OptionsWrapper POST_DESIGN_DOCS = + new OptionsWrapper<>(OptionsHandler.POST_DESIGN_DOCS, PostDesignDocsOptions.Builder::new); + static final OptionsWrapper POST_FIND = + new OptionsWrapper<>(OptionsHandler.POST_FIND, PostFindOptions.Builder::new); + static final OptionsWrapper POST_PARTITION_ALL_DOCS = + new OptionsWrapper<>(OptionsHandler.POST_PARTITION_ALL_DOCS, + PostPartitionAllDocsOptions.Builder::new); + static final OptionsWrapper POST_PARTITION_FIND = + new OptionsWrapper<>(OptionsHandler.POST_PARTITION_FIND, + PostPartitionFindOptions.Builder::new); + static final OptionsWrapper POST_PARTITION_SEARCH = + new OptionsWrapper<>(OptionsHandler.POST_PARTITION_SEARCH, + PostPartitionSearchOptions.Builder::new); + static final OptionsWrapper POST_PARTITION_VIEW = + new OptionsWrapper<>(OptionsHandler.POST_PARTITION_VIEW, + PostPartitionViewOptions.Builder::new); + static final OptionsWrapper POST_SEARCH = + new OptionsWrapper<>(OptionsHandler.POST_SEARCH, PostSearchOptions.Builder::new); + static final OptionsWrapper POST_VIEW = + new OptionsWrapper<>(OptionsHandler.POST_VIEW, PostViewOptions.Builder::new); + + private final OptionsHandler handler; + private final Supplier builderSupplier; + + OptionsWrapper(OptionsHandler handler, Supplier builderSupplier) { + this.handler = handler; + this.builderSupplier = builderSupplier; + } + + OptionsProvider newProvider() { + return new OptionsProvider(this.handler, this.builderSupplier.get()); + } + + } + + static final class OptionsProvider { + + final OptionsHandler handler; + private final B builder; + + OptionsProvider(OptionsHandler handler, B builder) { + this.handler = handler; + this.builder = builder; + } + + void setRequiredOpts() throws Exception { + // database is always required + this.set("db", "testdb"); + + // For partitioned operations we need + // partitionKey + if (OptionsHandler.POST_PARTITION_ALL_DOCS.equals(handler) + || OptionsHandler.POST_PARTITION_FIND.equals(handler) + || OptionsHandler.POST_PARTITION_SEARCH.equals(handler) + || OptionsHandler.POST_PARTITION_VIEW.equals(handler)) { + this.set("partitionKey", "testpart"); + } + + // For find operations we need + // selector + if (OptionsHandler.POST_FIND.equals(handler) + || OptionsHandler.POST_PARTITION_FIND.equals(handler)) { + Map selector = Collections.emptyMap(); + this.set("selector", selector); + } + + // For search operations we need + // ddoc + // index + // query + if (OptionsHandler.POST_PARTITION_SEARCH.equals(handler) + || OptionsHandler.POST_SEARCH.equals(handler)) { + this.set("ddoc", "testddoc"); + this.set("index", "testsearchindex"); + this.set("query", "*:*"); + } + + // For view operations we need + // ddoc + // view + if (OptionsHandler.POST_PARTITION_VIEW.equals(handler) + || OptionsHandler.POST_VIEW.equals(handler)) { + this.set("ddoc", "testddoc"); + this.set("view", "testview"); + } + } + + void set(String name, Object... values) throws Exception { + List> argTypes = Arrays.asList(values).stream().map(a -> { + Class c = a.getClass(); + if (List.class.isAssignableFrom(c)) { + c = List.class; + } + if (Long.class.equals(c)) { + c = long.class; + } + if (Map.class.isAssignableFrom(c)) { + c = Map.class; + } + return c; + }).collect(Collectors.toList()); + Class[] argTypesArray = argTypes.toArray(new Class[argTypes.size()]); + Method method = this.builder.getClass().getMethod(name, argTypesArray); + method.invoke(this.builder, values); + } + + O build() { + return this.handler.optionsFromBuilder(this.builder); + } + + } + + List> allDocsOptions = List.of(OptionsWrapper.POST_ALL_DOCS, + OptionsWrapper.POST_DESIGN_DOCS, OptionsWrapper.POST_PARTITION_ALL_DOCS); + + List> viewOptions = + List.of(OptionsWrapper.POST_PARTITION_VIEW, OptionsWrapper.POST_VIEW); + + List> viewLikeOptions = + Stream.of(allDocsOptions, viewOptions).flatMap(List::stream).collect(Collectors.toList()); + + List> findOptions = + List.of(OptionsWrapper.POST_FIND, OptionsWrapper.POST_PARTITION_FIND); + + List> searchOptions = + List.of(OptionsWrapper.POST_PARTITION_SEARCH, OptionsWrapper.POST_SEARCH); + + List> allOptions = Stream.of(findOptions, searchOptions, viewLikeOptions) + .flatMap(List::stream).collect(Collectors.toList()); + + public Iterator getIteratorFor(List> options) { + return options.stream().map(w -> { + return new Object[] {w.newProvider()}; + }).iterator(); + } + + @DataProvider(name = "allDocsOptions") + public Iterator allDocsOptions() { + return getIteratorFor(allDocsOptions); + } + + @DataProvider(name = "allOptions") + public Iterator allOptions() { + return getIteratorFor(allOptions); + } + + @DataProvider(name = "findOptions") + public Iterator findOptions() { + return getIteratorFor(findOptions); + } + + @DataProvider(name = "searchOptions") + public Iterator searchOptions() { + return getIteratorFor(searchOptions); + } + + @DataProvider(name = "searchFacetOptions") + public Iterator facets() { + List options = new ArrayList<>(5); + options.add(new Object[] {"counts", Collections.singletonList("aTestFieldToCount")}); + options.add(new Object[] {"groupField", "testField"}); + options.add(new Object[] {"groupLimit", 6L}); + options.add(new Object[] {"groupSort", Collections.singletonList("aTestFieldToGroupSort")}); + options.add(new Object[] {"ranges", Collections.singletonMap("aTestFieldForRanges", + Map.of("low", "[0 TO 5}", "high", "[5 TO 10]"))}); + return options.iterator(); + } + + @DataProvider(name = "viewLikeOptions") + public Iterator viewLikeOptions() { + return getIteratorFor(viewLikeOptions); + } + + @DataProvider(name = "viewOptions") + public Iterator viewOptions() { + return getIteratorFor(viewLikeOptions); + } + + static class TestDesignDocsResultRow extends DocsResultRow { + TestDesignDocsResultRow(Integer i) { + this.id = "_design/testdoc" + String.valueOf(i); + this.key = this.id; + } + } + + static class TestDocsResultRow extends DocsResultRow { + TestDocsResultRow(Integer i) { + this.id = "testdoc" + String.valueOf(i); + this.key = this.id; + } + } + + static class TestAllDocsResult extends AllDocsResult { + TestAllDocsResult(List rows) { + this.rows = rows; + } + } + + static class TestFindDocument extends Document { + TestFindDocument(Integer i) { + this.id = "testdoc" + String.valueOf(i); + } + } + + static class TestFindResult extends FindResult { + TestFindResult(List rows) { + this.docs = rows; + } + } + + static class TestSearchResultRow extends SearchResultRow { + TestSearchResultRow(Integer i) { + this.id = "testdoc" + String.valueOf(i); + } + } + + static class TestSearchResult extends SearchResult { + TestSearchResult(List rows) { + this.rows = rows; + } + } + + static class TestViewResultRow extends ViewResultRow { + TestViewResultRow(Integer i) { + this.id = "testdoc" + String.valueOf(i); + this.key = i; + } + } + + static class TestViewResult extends ViewResult { + TestViewResult(List rows) { + this.rows = rows; + } + } + } From ea7434f24fd3a133b8b99cf5e85658538e2f258b Mon Sep 17 00:00:00 2001 From: Rich Ellis Date: Mon, 12 May 2025 12:15:21 +0100 Subject: [PATCH 35/43] test: fix method sigs to make tests run --- .../features/pagination/OptionsValidationTest.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/OptionsValidationTest.java b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/OptionsValidationTest.java index b1979b113..8484721f2 100644 --- a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/OptionsValidationTest.java +++ b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/OptionsValidationTest.java @@ -23,7 +23,7 @@ public class OptionsValidationTest { @Test(dataProvider = "allOptions", dataProviderClass = PaginationTestHelpers.class) - public void testOptionsValidationLimitLessThanMin(OptionsProvider provider) + public void testOptionsValidationLimitLessThanMin(OptionsProvider provider) throws Exception { provider.setRequiredOpts(); provider.set("limit", OptionsHandler.MIN_LIMIT - 1); @@ -34,7 +34,7 @@ public void testOptionsValidationLimitLessThanMin(OptionsProvider p } @Test(dataProvider = "allOptions", dataProviderClass = PaginationTestHelpers.class) - public void testOptionsValidationLimitEqualToMin(OptionsProvider provider) + public void testOptionsValidationLimitEqualToMin(OptionsProvider provider) throws Exception { provider.setRequiredOpts(); provider.set("limit", OptionsHandler.MIN_LIMIT); @@ -46,7 +46,7 @@ public void testOptionsValidationLimitEqualToMin(OptionsProvider pr } @Test(dataProvider = "allOptions", dataProviderClass = PaginationTestHelpers.class) - public void testOptionsValidationLimitLessThanMax(OptionsProvider provider) + public void testOptionsValidationLimitLessThanMax(OptionsProvider provider) throws Exception { provider.setRequiredOpts(); provider.set("limit", OptionsHandler.MAX_LIMIT - 1); @@ -58,7 +58,7 @@ public void testOptionsValidationLimitLessThanMax(OptionsProvider p } @Test(dataProvider = "allOptions", dataProviderClass = PaginationTestHelpers.class) - public void testOptionsValidationLimitEqualToMax(OptionsProvider provider) + public void testOptionsValidationLimitEqualToMax(OptionsProvider provider) throws Exception { provider.setRequiredOpts(); provider.set("limit", OptionsHandler.MAX_LIMIT); @@ -70,7 +70,7 @@ public void testOptionsValidationLimitEqualToMax(OptionsProvider pr } @Test(dataProvider = "allOptions", dataProviderClass = PaginationTestHelpers.class) - public void testOptionsValidationLimitGreaterThanMax(OptionsProvider provider) + public void testOptionsValidationLimitGreaterThanMax(OptionsProvider provider) throws Exception { provider.setRequiredOpts(); provider.set("limit", OptionsHandler.MAX_LIMIT + 1); @@ -81,7 +81,7 @@ public void testOptionsValidationLimitGreaterThanMax(OptionsProvider void testOptionsValidationLimitUnset(OptionsProvider provider) + public void testOptionsValidationLimitUnset(OptionsProvider provider) throws Exception { provider.setRequiredOpts(); try { @@ -92,7 +92,7 @@ public void testOptionsValidationLimitUnset(OptionsProvider provide } @Test(dataProvider = "viewLikeOptions", dataProviderClass = PaginationTestHelpers.class) - public void testValidationExceptionForKeys(OptionsProvider provider) + public void testValidationExceptionForKeys(OptionsProvider provider) throws Exception { provider.setRequiredOpts(); provider.set("keys", List.of("key1", "key2")); From bae814e10186cf1402e6be12e7c31e15506026e4 Mon Sep 17 00:00:00 2001 From: Rich Ellis Date: Tue, 13 May 2025 16:04:27 +0100 Subject: [PATCH 36/43] test: add tests for all docs pagination --- .../features/pagination/AllDocsTest.java | 41 ++++ .../pagination/PaginationOperationTest.java | 179 ++++++++++++++++++ 2 files changed, 220 insertions(+) create mode 100644 modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/AllDocsTest.java create mode 100644 modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/PaginationOperationTest.java diff --git a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/AllDocsTest.java b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/AllDocsTest.java new file mode 100644 index 000000000..7269ae538 --- /dev/null +++ b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/AllDocsTest.java @@ -0,0 +1,41 @@ +/** + * © Copyright IBM Corporation 2025. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.ibm.cloud.cloudant.features.pagination; + +import com.ibm.cloud.cloudant.features.MockCloudant; +import com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.OptionsWrapper; +import com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.PageSupplierFactory; +import com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.TestAllDocsResult; +import com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.TestDocsResultRow; +import com.ibm.cloud.cloudant.v1.model.AllDocsResult; +import com.ibm.cloud.cloudant.v1.model.DocsResultRow; +import com.ibm.cloud.cloudant.v1.model.PostAllDocsOptions; + +public class AllDocsTest extends + PaginationOperationTest { + + AllDocsTest() { + super(new PageSupplierFactory(TestAllDocsResult::new, TestDocsResultRow::new, + true), OptionsWrapper.POST_ALL_DOCS.newProvider(), true); + } + + // New Pagination + @Override + protected Pagination makeNewPagination( + MockCloudant cloudant, PostAllDocsOptions options) { + return Pagination.newPagination(cloudant, options); + } + +} diff --git a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/PaginationOperationTest.java b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/PaginationOperationTest.java new file mode 100644 index 000000000..2449fa5ba --- /dev/null +++ b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/PaginationOperationTest.java @@ -0,0 +1,179 @@ +/** + * © Copyright IBM Corporation 2025. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.ibm.cloud.cloudant.features.pagination; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.testng.Assert; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; +import com.ibm.cloud.cloudant.features.MockCloudant; +import com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.MockPagerCloudant; +import com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.OptionsProvider; +import com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.PageSupplierFactory; + +public abstract class PaginationOperationTest { + + protected final PageSupplierFactory pageSupplierFactory; + protected final OptionsProvider provider; + protected final boolean plusOnePaging; + + PaginationOperationTest(PageSupplierFactory pageSupplierFactory, OptionsProvider provider, boolean plusOnePaging) { + this.pageSupplierFactory = pageSupplierFactory; + this.provider = provider; + this.plusOnePaging = plusOnePaging; + } + + @DataProvider(name = "pageSets") + Object[][] getPageSets() { + int pageSize = 10; + return new Object[][] {getTestPageParams(0, pageSize), // Empty page + getTestPageParams(1, pageSize), // Partial page + getTestPageParams(pageSize - 1, pageSize), // One less than a whole page + getTestPageParams(pageSize, pageSize), // Exactly one page + getTestPageParams(pageSize + 1, pageSize), // One more than a whole page + getTestPageParams(3 * pageSize, pageSize), // Multiple pages, exact + getTestPageParams(3 * pageSize + 1, pageSize), // Multiple pages, plus one + getTestPageParams(4 * pageSize - 1, pageSize) // Multiple pages, partial finish + }; + } + + Object[] getTestPageParams(int total, int pageSize) { + int fullPages = (total / pageSize); + int partialPages = total % pageSize == 0 ? 0 : 1; + int expectedPages = fullPages + partialPages; + // Need at least 1 empty page to know there are no more results + // if not ending on a partial page, except if the first page or + // using n+1 paging (because an exact user page is a partial real page). + if (partialPages == 0 && (!plusOnePaging || expectedPages == 0)) { + expectedPages += 1; // We will get at least 1 empty page + } + return new Object[] {total, pageSize, expectedPages}; + } + + protected abstract Pagination makeNewPagination(MockCloudant c, O options); + + // Check validation is wired + @Test + public void testValidationEnabled() throws Exception { + this.provider.setRequiredOpts(); + this.provider.set("limit", OptionsHandler.MIN_LIMIT - 1); + Assert.assertThrows("There should be a validation exception", IllegalArgumentException.class, + () -> { + makeNewPagination(new MockPagerCloudant(pageSupplierFactory.newPageSupplier(0, 0)), + provider.build()); + }); + } + + // Check Pager + @Test(dataProvider = "pageSets") + public void testPager(int totalItems, int pageSize, int expectedPages) throws Exception { + provider.setRequiredOpts(); + provider.set("limit", Integer.valueOf(pageSize).longValue()); + Pagination pagination = makeNewPagination( + new MockPagerCloudant(this.pageSupplierFactory.newPageSupplier(totalItems, pageSize)), + provider.build()); + Pager pager = pagination.pager(); + int pageCount = 0; + int items = 0; + Set uniqueItems = new HashSet<>(totalItems); + while (pager.hasNext()) { + List page = pager.getNext(); + pageCount++; + items += page.size(); + uniqueItems.addAll(page); + } + Assert.assertEquals(pageCount, expectedPages, "There should be the expected number of pages."); + Assert.assertEquals(items, totalItems, "There should be the expected number of items."); + Assert.assertEquals(uniqueItems.size(), totalItems, "The items should be unique."); + } + + // Check PageStream + @Test(dataProvider = "pageSets") + public void testPageStream(int totalItems, int pageSize, int expectedPages) throws Exception { + provider.setRequiredOpts(); + provider.set("limit", Integer.valueOf(pageSize).longValue()); + Pagination pagination = makeNewPagination( + new MockPagerCloudant(this.pageSupplierFactory.newPageSupplier(totalItems, pageSize)), + provider.build()); + Assert.assertEquals(pagination.pageStream().count(), expectedPages, + "There should be the expected number of pages."); + // Reset the mocks for another stream + pagination = makeNewPagination( + new MockPagerCloudant(this.pageSupplierFactory.newPageSupplier(totalItems, pageSize)), + provider.build()); + Assert.assertEquals(pagination.pageStream().flatMap(List::stream).distinct().count(), + totalItems, "There should be the expected number of distinct items."); + } + + // Check Pages + @Test(dataProvider = "pageSets") + public void testPages(int totalItems, int pageSize, int expectedPages) throws Exception { + provider.setRequiredOpts(); + provider.set("limit", Integer.valueOf(pageSize).longValue()); + Pagination pagination = makeNewPagination( + new MockPagerCloudant(this.pageSupplierFactory.newPageSupplier(totalItems, pageSize)), + provider.build()); + int pageCount = 0; + int items = 0; + Set uniqueItems = new HashSet<>(totalItems); + for (List page : pagination.pages()) { + pageCount++; + items += page.size(); + uniqueItems.addAll(page); + } + Assert.assertEquals(pageCount, expectedPages, "There should be the expected number of pages."); + Assert.assertEquals(items, totalItems, "There should be the expected number of items."); + Assert.assertEquals(uniqueItems.size(), totalItems, "The items should be unique."); + } + + // Check RowStream + @Test(dataProvider = "pageSets") + public void testRowStream(int totalItems, int pageSize, int expectedPages) throws Exception { + provider.setRequiredOpts(); + provider.set("limit", Integer.valueOf(pageSize).longValue()); + Pagination pagination = makeNewPagination( + new MockPagerCloudant(this.pageSupplierFactory.newPageSupplier(totalItems, pageSize)), + provider.build()); + Assert.assertEquals(pagination.rowStream().count(), totalItems, + "There should be the expected number of items."); + // Reset mocks for another stream + pagination = makeNewPagination( + new MockPagerCloudant(this.pageSupplierFactory.newPageSupplier(totalItems, pageSize)), + provider.build()); + Assert.assertEquals(pagination.rowStream().distinct().count(), totalItems, + "There should be the expected number of distinct items."); + } + + // Check Rows + @Test(dataProvider = "pageSets") + public void testRows(int totalItems, int pageSize, int expectedPages) throws Exception { + provider.setRequiredOpts(); + provider.set("limit", Integer.valueOf(pageSize).longValue()); + Pagination pagination = makeNewPagination( + new MockPagerCloudant(this.pageSupplierFactory.newPageSupplier(totalItems, pageSize)), + provider.build()); + int items = 0; + Set uniqueItems = new HashSet<>(totalItems); + for (I row : pagination.rows()) { + items++; + uniqueItems.add(row); + } + Assert.assertEquals(items, totalItems, "There should be the expected number of items."); + Assert.assertEquals(uniqueItems.size(), totalItems, "The items should be unique."); + } + +} From e074f5766f5cb737cd2ae2ed4fa61cc0f0c73f92 Mon Sep 17 00:00:00 2001 From: Rich Ellis Date: Tue, 13 May 2025 17:38:25 +0100 Subject: [PATCH 37/43] test: fix mock document --- .../cloudant/features/pagination/PaginationTestHelpers.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/PaginationTestHelpers.java b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/PaginationTestHelpers.java index b4af37307..fee039864 100644 --- a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/PaginationTestHelpers.java +++ b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/PaginationTestHelpers.java @@ -455,7 +455,13 @@ static class TestAllDocsResult extends AllDocsResult { } static class TestFindDocument extends Document { + + TestFindDocument() { + super(); + } + TestFindDocument(Integer i) { + super(); this.id = "testdoc" + String.valueOf(i); } } From 1379d605bd31a0fadeee59a6bae744bfb2476b5a Mon Sep 17 00:00:00 2001 From: Rich Ellis Date: Tue, 13 May 2025 17:38:56 +0100 Subject: [PATCH 38/43] test: add remaining pagination operation tests --- .../features/pagination/DesignDocsTest.java | 41 +++++++++++++++++++ .../features/pagination/FindTest.java | 41 +++++++++++++++++++ .../pagination/PartitionAllDocsTest.java | 41 +++++++++++++++++++ .../pagination/PartitionFindTest.java | 41 +++++++++++++++++++ .../pagination/PartitionSearchTest.java | 41 +++++++++++++++++++ .../pagination/PartitionViewTest.java | 41 +++++++++++++++++++ .../features/pagination/SearchTest.java | 41 +++++++++++++++++++ .../features/pagination/ViewTest.java | 41 +++++++++++++++++++ 8 files changed, 328 insertions(+) create mode 100644 modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/DesignDocsTest.java create mode 100644 modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/FindTest.java create mode 100644 modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/PartitionAllDocsTest.java create mode 100644 modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/PartitionFindTest.java create mode 100644 modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/PartitionSearchTest.java create mode 100644 modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/PartitionViewTest.java create mode 100644 modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/SearchTest.java create mode 100644 modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/ViewTest.java diff --git a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/DesignDocsTest.java b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/DesignDocsTest.java new file mode 100644 index 000000000..94fda34ea --- /dev/null +++ b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/DesignDocsTest.java @@ -0,0 +1,41 @@ +/** + * © Copyright IBM Corporation 2025. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.ibm.cloud.cloudant.features.pagination; + +import com.ibm.cloud.cloudant.features.MockCloudant; +import com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.OptionsWrapper; +import com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.PageSupplierFactory; +import com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.TestAllDocsResult; +import com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.TestDocsResultRow; +import com.ibm.cloud.cloudant.v1.model.AllDocsResult; +import com.ibm.cloud.cloudant.v1.model.DocsResultRow; +import com.ibm.cloud.cloudant.v1.model.PostDesignDocsOptions; + +public class DesignDocsTest extends + PaginationOperationTest { + + DesignDocsTest() { + super(new PageSupplierFactory(TestAllDocsResult::new, TestDocsResultRow::new, + true), OptionsWrapper.POST_DESIGN_DOCS.newProvider(), true); + } + + // New Pagination + @Override + protected Pagination makeNewPagination( + MockCloudant cloudant, PostDesignDocsOptions options) { + return Pagination.newPagination(cloudant, options); + } + +} diff --git a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/FindTest.java b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/FindTest.java new file mode 100644 index 000000000..4a309ae0e --- /dev/null +++ b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/FindTest.java @@ -0,0 +1,41 @@ +/** + * © Copyright IBM Corporation 2025. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.ibm.cloud.cloudant.features.pagination; + +import com.ibm.cloud.cloudant.features.MockCloudant; +import com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.OptionsWrapper; +import com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.PageSupplierFactory; +import com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.TestFindDocument; +import com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.TestFindResult; +import com.ibm.cloud.cloudant.v1.model.Document; +import com.ibm.cloud.cloudant.v1.model.FindResult; +import com.ibm.cloud.cloudant.v1.model.PostFindOptions; + +public class FindTest extends + PaginationOperationTest { + + FindTest() { + super(new PageSupplierFactory(TestFindResult::new, TestFindDocument::new, + false), OptionsWrapper.POST_FIND.newProvider(), false); + } + + // New Pagination + @Override + protected Pagination makeNewPagination( + MockCloudant cloudant, PostFindOptions options) { + return Pagination.newPagination(cloudant, options); + } + +} diff --git a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/PartitionAllDocsTest.java b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/PartitionAllDocsTest.java new file mode 100644 index 000000000..386c10fa2 --- /dev/null +++ b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/PartitionAllDocsTest.java @@ -0,0 +1,41 @@ +/** + * © Copyright IBM Corporation 2025. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.ibm.cloud.cloudant.features.pagination; + +import com.ibm.cloud.cloudant.features.MockCloudant; +import com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.OptionsWrapper; +import com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.PageSupplierFactory; +import com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.TestAllDocsResult; +import com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.TestDocsResultRow; +import com.ibm.cloud.cloudant.v1.model.AllDocsResult; +import com.ibm.cloud.cloudant.v1.model.DocsResultRow; +import com.ibm.cloud.cloudant.v1.model.PostPartitionAllDocsOptions; + +public class PartitionAllDocsTest extends + PaginationOperationTest { + + PartitionAllDocsTest() { + super(new PageSupplierFactory(TestAllDocsResult::new, TestDocsResultRow::new, + true), OptionsWrapper.POST_PARTITION_ALL_DOCS.newProvider(), true); + } + + // New Pagination + @Override + protected Pagination makeNewPagination( + MockCloudant cloudant, PostPartitionAllDocsOptions options) { + return Pagination.newPagination(cloudant, options); + } + +} diff --git a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/PartitionFindTest.java b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/PartitionFindTest.java new file mode 100644 index 000000000..3b59c5371 --- /dev/null +++ b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/PartitionFindTest.java @@ -0,0 +1,41 @@ +/** + * © Copyright IBM Corporation 2025. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.ibm.cloud.cloudant.features.pagination; + +import com.ibm.cloud.cloudant.features.MockCloudant; +import com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.OptionsWrapper; +import com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.PageSupplierFactory; +import com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.TestFindDocument; +import com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.TestFindResult; +import com.ibm.cloud.cloudant.v1.model.Document; +import com.ibm.cloud.cloudant.v1.model.FindResult; +import com.ibm.cloud.cloudant.v1.model.PostPartitionFindOptions; + +public class PartitionFindTest extends + PaginationOperationTest { + + PartitionFindTest() { + super(new PageSupplierFactory(TestFindResult::new, TestFindDocument::new, + false), OptionsWrapper.POST_PARTITION_FIND.newProvider(), false); + } + + // New Pagination + @Override + protected Pagination makeNewPagination( + MockCloudant cloudant, PostPartitionFindOptions options) { + return Pagination.newPagination(cloudant, options); + } + +} diff --git a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/PartitionSearchTest.java b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/PartitionSearchTest.java new file mode 100644 index 000000000..aef53605e --- /dev/null +++ b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/PartitionSearchTest.java @@ -0,0 +1,41 @@ +/** + * © Copyright IBM Corporation 2025. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.ibm.cloud.cloudant.features.pagination; + +import com.ibm.cloud.cloudant.features.MockCloudant; +import com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.OptionsWrapper; +import com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.PageSupplierFactory; +import com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.TestSearchResult; +import com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.TestSearchResultRow; +import com.ibm.cloud.cloudant.v1.model.PostPartitionSearchOptions; +import com.ibm.cloud.cloudant.v1.model.SearchResult; +import com.ibm.cloud.cloudant.v1.model.SearchResultRow; + +public class PartitionSearchTest extends + PaginationOperationTest { + + PartitionSearchTest() { + super(new PageSupplierFactory(TestSearchResult::new, TestSearchResultRow::new, + false), OptionsWrapper.POST_PARTITION_SEARCH.newProvider(), false); + } + + // New Pagination + @Override + protected Pagination makeNewPagination( + MockCloudant cloudant, PostPartitionSearchOptions options) { + return Pagination.newPagination(cloudant, options); + } + +} diff --git a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/PartitionViewTest.java b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/PartitionViewTest.java new file mode 100644 index 000000000..d00449e8d --- /dev/null +++ b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/PartitionViewTest.java @@ -0,0 +1,41 @@ +/** + * © Copyright IBM Corporation 2025. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.ibm.cloud.cloudant.features.pagination; + +import com.ibm.cloud.cloudant.features.MockCloudant; +import com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.OptionsWrapper; +import com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.PageSupplierFactory; +import com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.TestViewResult; +import com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.TestViewResultRow; +import com.ibm.cloud.cloudant.v1.model.PostPartitionViewOptions; +import com.ibm.cloud.cloudant.v1.model.ViewResult; +import com.ibm.cloud.cloudant.v1.model.ViewResultRow; + +public class PartitionViewTest extends + PaginationOperationTest { + + PartitionViewTest() { + super(new PageSupplierFactory(TestViewResult::new, TestViewResultRow::new, + true), OptionsWrapper.POST_PARTITION_VIEW.newProvider(), true); + } + + // New Pagination + @Override + protected Pagination makeNewPagination( + MockCloudant cloudant, PostPartitionViewOptions options) { + return Pagination.newPagination(cloudant, options); + } + +} diff --git a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/SearchTest.java b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/SearchTest.java new file mode 100644 index 000000000..c45df7bfc --- /dev/null +++ b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/SearchTest.java @@ -0,0 +1,41 @@ +/** + * © Copyright IBM Corporation 2025. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.ibm.cloud.cloudant.features.pagination; + +import com.ibm.cloud.cloudant.features.MockCloudant; +import com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.OptionsWrapper; +import com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.PageSupplierFactory; +import com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.TestSearchResult; +import com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.TestSearchResultRow; +import com.ibm.cloud.cloudant.v1.model.PostSearchOptions; +import com.ibm.cloud.cloudant.v1.model.SearchResult; +import com.ibm.cloud.cloudant.v1.model.SearchResultRow; + +public class SearchTest extends + PaginationOperationTest { + + SearchTest() { + super(new PageSupplierFactory(TestSearchResult::new, TestSearchResultRow::new, + false), OptionsWrapper.POST_SEARCH.newProvider(), false); + } + + // New Pagination + @Override + protected Pagination makeNewPagination( + MockCloudant cloudant, PostSearchOptions options) { + return Pagination.newPagination(cloudant, options); + } + +} diff --git a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/ViewTest.java b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/ViewTest.java new file mode 100644 index 000000000..a9c078a36 --- /dev/null +++ b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/ViewTest.java @@ -0,0 +1,41 @@ +/** + * © Copyright IBM Corporation 2025. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.ibm.cloud.cloudant.features.pagination; + +import com.ibm.cloud.cloudant.features.MockCloudant; +import com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.OptionsWrapper; +import com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.PageSupplierFactory; +import com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.TestViewResult; +import com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.TestViewResultRow; +import com.ibm.cloud.cloudant.v1.model.PostViewOptions; +import com.ibm.cloud.cloudant.v1.model.ViewResult; +import com.ibm.cloud.cloudant.v1.model.ViewResultRow; + +public class ViewTest extends + PaginationOperationTest { + + ViewTest() { + super(new PageSupplierFactory(TestViewResult::new, TestViewResultRow::new, + true), OptionsWrapper.POST_VIEW.newProvider(), true); + } + + // New Pagination + @Override + protected Pagination makeNewPagination( + MockCloudant cloudant, PostViewOptions options) { + return Pagination.newPagination(cloudant, options); + } + +} From 7749837ffaf0f1cf0e32a7261d9041cd88123329 Mon Sep 17 00:00:00 2001 From: Rich Ellis Date: Fri, 16 May 2025 12:54:14 +0100 Subject: [PATCH 39/43] test: reduce duplication --- .../pagination/PaginationOperationTest.java | 264 +++++++++++------- 1 file changed, 162 insertions(+), 102 deletions(-) diff --git a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/PaginationOperationTest.java b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/PaginationOperationTest.java index 2449fa5ba..e2098a1c7 100644 --- a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/PaginationOperationTest.java +++ b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/PaginationOperationTest.java @@ -14,13 +14,20 @@ package com.ibm.cloud.cloudant.features.pagination; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.Function; +import java.util.function.Supplier; import org.testng.Assert; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import com.ibm.cloud.cloudant.features.MockCloudant; +import com.ibm.cloud.cloudant.features.MockCloudant.MockInstruction; import com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.MockPagerCloudant; import com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.OptionsProvider; import com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.PageSupplierFactory; @@ -31,149 +38,202 @@ public abstract class PaginationOperationTest { protected final OptionsProvider provider; protected final boolean plusOnePaging; - PaginationOperationTest(PageSupplierFactory pageSupplierFactory, OptionsProvider provider, boolean plusOnePaging) { + private Function, PagingResult> pagerFn = p -> { + PagingResult result = new PagingResult<>(); + Pager pager = p.pager(); + while (pager.hasNext()) { + List page = pager.getNext(); + result.handlePage(page); + } + return result; + }; + + private Function, PagingResult> pageStreamFn = p -> { + PagingResult result = new PagingResult<>(); + p.pageStream().forEach(result::handlePage); + return result; + }; + + private Function, PagingResult> pagesFn = p -> { + PagingResult result = new PagingResult<>(); + for (List page : p.pages()) { + result.handlePage(page); + } + return result; + }; + + private Function, PagingResult> rowStreamFn = p -> { + PagingResult result = new PagingResult<>(); + p.rowStream().forEach(result::handleItem); + return result; + }; + + private Function, PagingResult> rowsFn = p -> { + PagingResult result = new PagingResult<>(); + for (I row : p.rows()) { + result.handleItem(row); + } + return result; + }; + + PaginationOperationTest(PageSupplierFactory pageSupplierFactory, + OptionsProvider provider, boolean plusOnePaging) { this.pageSupplierFactory = pageSupplierFactory; this.provider = provider; this.plusOnePaging = plusOnePaging; } + private static final class TestCase { + final int totalItems; + final int pageSize; + final int expectedPages; + + TestCase(int totalItems, int pageSize, boolean plusOnePaging) { + this.totalItems = totalItems; + this.pageSize = pageSize; + this.expectedPages = (totalItems == 0) ? 1 : getExpectedPages(plusOnePaging); + } + + private int getExpectedPages(boolean plusOnePaging) { + int fullPages = (totalItems / pageSize); + int partialPages = totalItems % pageSize == 0 ? 0 : 1; + int expectedPages = fullPages + partialPages; + // Need at least 1 empty page to know there are no more results + // if not ending on a partial page, except if the first page or + // using n+1 paging (because an exact user page is a partial real page). + if (partialPages == 0 && (!plusOnePaging || expectedPages == 0)) { + expectedPages += 1; // We will get at least 1 empty page + } + return expectedPages; + } + } + + private final List, TestCase>> pageAssertions = + List.of(PagingResult::assertPageCount, PagingResult::assertItemCount, + PagingResult::assertUniqueItemCount); + private final List, TestCase>> itemAssertions = + pageAssertions.subList(1, pageAssertions.size()); + + + private static final class PagingResult { + + boolean assertable = false; + Integer pageCount = 0; + List items = new ArrayList<>(); + + private void setAssertable() { + if (!this.assertable) { + this.assertable = true; + } + } + + private void handleItem(I item) { + setAssertable(); + items.add(item); + } + + private void handlePage(Collection page) { + setAssertable(); + pageCount++; + items.addAll(page); + } + + private void assertItemCount(TestCase t) { + Assert.assertEquals(items.size(), t.totalItems, + "There should be the expected number of items."); + } + + private void assertPageCount(TestCase t) { + Assert.assertTrue(this.assertable, "PagingResult handled no pages."); + Assert.assertEquals(pageCount, t.expectedPages, + "There should be the expected number of pages."); + } + + private void assertUniqueItemCount(TestCase t) { + Set uniqueItems = new HashSet<>(); + uniqueItems.addAll(items); + Assert.assertEquals(uniqueItems.size(), t.totalItems, "The items should be unique."); + } + + } + @DataProvider(name = "pageSets") Object[][] getPageSets() { int pageSize = 10; - return new Object[][] {getTestPageParams(0, pageSize), // Empty page - getTestPageParams(1, pageSize), // Partial page - getTestPageParams(pageSize - 1, pageSize), // One less than a whole page - getTestPageParams(pageSize, pageSize), // Exactly one page - getTestPageParams(pageSize + 1, pageSize), // One more than a whole page - getTestPageParams(3 * pageSize, pageSize), // Multiple pages, exact - getTestPageParams(3 * pageSize + 1, pageSize), // Multiple pages, plus one - getTestPageParams(4 * pageSize - 1, pageSize) // Multiple pages, partial finish + return new Object[][] {makeTestCase(0, pageSize), // Empty page + makeTestCase(1, pageSize), // Partial page + makeTestCase(pageSize - 1, pageSize), // One less than a whole page + makeTestCase(pageSize, pageSize), // Exactly one page + makeTestCase(pageSize + 1, pageSize), // One more than a whole page + makeTestCase(3 * pageSize, pageSize), // Multiple pages, exact + makeTestCase(3 * pageSize + 1, pageSize), // Multiple pages, plus one + makeTestCase(4 * pageSize - 1, pageSize) // Multiple pages, partial finish }; } - Object[] getTestPageParams(int total, int pageSize) { - int fullPages = (total / pageSize); - int partialPages = total % pageSize == 0 ? 0 : 1; - int expectedPages = fullPages + partialPages; - // Need at least 1 empty page to know there are no more results - // if not ending on a partial page, except if the first page or - // using n+1 paging (because an exact user page is a partial real page). - if (partialPages == 0 && (!plusOnePaging || expectedPages == 0)) { - expectedPages += 1; // We will get at least 1 empty page - } - return new Object[] {total, pageSize, expectedPages}; + Object[] makeTestCase(int total, int pageSize) { + return new Object[] {new TestCase(total, pageSize, plusOnePaging)}; } protected abstract Pagination makeNewPagination(MockCloudant c, O options); + private Pagination makeTestPagination(int pageSize, Supplier> supplier) + throws Exception { + this.provider.setRequiredOpts(); + this.provider.set("limit", Integer.valueOf(pageSize).longValue()); + return makeNewPagination(new MockPagerCloudant(supplier), provider.build()); + } + + private void runPaginationTest(TestCase t, + Function, PagingResult> pagingFunction, + List, TestCase>> assertions) throws Exception { + Pagination pagination = makeTestPagination(t.pageSize, + this.pageSupplierFactory.newPageSupplier(t.totalItems, t.pageSize)); + PagingResult r = pagingFunction.apply(pagination); + for (BiConsumer, TestCase> assertion : assertions) { + assertion.accept(r, t); + } + } + // Check validation is wired @Test public void testValidationEnabled() throws Exception { - this.provider.setRequiredOpts(); - this.provider.set("limit", OptionsHandler.MIN_LIMIT - 1); Assert.assertThrows("There should be a validation exception", IllegalArgumentException.class, () -> { - makeNewPagination(new MockPagerCloudant(pageSupplierFactory.newPageSupplier(0, 0)), - provider.build()); + runPaginationTest( + new TestCase(0, Long.valueOf(OptionsHandler.MIN_LIMIT - 1).intValue(), plusOnePaging), + p -> null, Collections.emptyList()); }); } // Check Pager @Test(dataProvider = "pageSets") - public void testPager(int totalItems, int pageSize, int expectedPages) throws Exception { - provider.setRequiredOpts(); - provider.set("limit", Integer.valueOf(pageSize).longValue()); - Pagination pagination = makeNewPagination( - new MockPagerCloudant(this.pageSupplierFactory.newPageSupplier(totalItems, pageSize)), - provider.build()); - Pager pager = pagination.pager(); - int pageCount = 0; - int items = 0; - Set uniqueItems = new HashSet<>(totalItems); - while (pager.hasNext()) { - List page = pager.getNext(); - pageCount++; - items += page.size(); - uniqueItems.addAll(page); - } - Assert.assertEquals(pageCount, expectedPages, "There should be the expected number of pages."); - Assert.assertEquals(items, totalItems, "There should be the expected number of items."); - Assert.assertEquals(uniqueItems.size(), totalItems, "The items should be unique."); + public void testPager(TestCase t) throws Exception { + runPaginationTest(t, pagerFn, pageAssertions); } // Check PageStream @Test(dataProvider = "pageSets") - public void testPageStream(int totalItems, int pageSize, int expectedPages) throws Exception { - provider.setRequiredOpts(); - provider.set("limit", Integer.valueOf(pageSize).longValue()); - Pagination pagination = makeNewPagination( - new MockPagerCloudant(this.pageSupplierFactory.newPageSupplier(totalItems, pageSize)), - provider.build()); - Assert.assertEquals(pagination.pageStream().count(), expectedPages, - "There should be the expected number of pages."); - // Reset the mocks for another stream - pagination = makeNewPagination( - new MockPagerCloudant(this.pageSupplierFactory.newPageSupplier(totalItems, pageSize)), - provider.build()); - Assert.assertEquals(pagination.pageStream().flatMap(List::stream).distinct().count(), - totalItems, "There should be the expected number of distinct items."); + public void testPageStream(TestCase t) throws Exception { + runPaginationTest(t, pageStreamFn, pageAssertions); } // Check Pages @Test(dataProvider = "pageSets") - public void testPages(int totalItems, int pageSize, int expectedPages) throws Exception { - provider.setRequiredOpts(); - provider.set("limit", Integer.valueOf(pageSize).longValue()); - Pagination pagination = makeNewPagination( - new MockPagerCloudant(this.pageSupplierFactory.newPageSupplier(totalItems, pageSize)), - provider.build()); - int pageCount = 0; - int items = 0; - Set uniqueItems = new HashSet<>(totalItems); - for (List page : pagination.pages()) { - pageCount++; - items += page.size(); - uniqueItems.addAll(page); - } - Assert.assertEquals(pageCount, expectedPages, "There should be the expected number of pages."); - Assert.assertEquals(items, totalItems, "There should be the expected number of items."); - Assert.assertEquals(uniqueItems.size(), totalItems, "The items should be unique."); + public void testPages(TestCase t) throws Exception { + runPaginationTest(t, pagesFn, pageAssertions); } // Check RowStream @Test(dataProvider = "pageSets") - public void testRowStream(int totalItems, int pageSize, int expectedPages) throws Exception { - provider.setRequiredOpts(); - provider.set("limit", Integer.valueOf(pageSize).longValue()); - Pagination pagination = makeNewPagination( - new MockPagerCloudant(this.pageSupplierFactory.newPageSupplier(totalItems, pageSize)), - provider.build()); - Assert.assertEquals(pagination.rowStream().count(), totalItems, - "There should be the expected number of items."); - // Reset mocks for another stream - pagination = makeNewPagination( - new MockPagerCloudant(this.pageSupplierFactory.newPageSupplier(totalItems, pageSize)), - provider.build()); - Assert.assertEquals(pagination.rowStream().distinct().count(), totalItems, - "There should be the expected number of distinct items."); + public void testRowStream(TestCase t) throws Exception { + runPaginationTest(t, rowStreamFn, itemAssertions); } // Check Rows @Test(dataProvider = "pageSets") - public void testRows(int totalItems, int pageSize, int expectedPages) throws Exception { - provider.setRequiredOpts(); - provider.set("limit", Integer.valueOf(pageSize).longValue()); - Pagination pagination = makeNewPagination( - new MockPagerCloudant(this.pageSupplierFactory.newPageSupplier(totalItems, pageSize)), - provider.build()); - int items = 0; - Set uniqueItems = new HashSet<>(totalItems); - for (I row : pagination.rows()) { - items++; - uniqueItems.add(row); - } - Assert.assertEquals(items, totalItems, "There should be the expected number of items."); - Assert.assertEquals(uniqueItems.size(), totalItems, "The items should be unique."); + public void testRows(TestCase t) throws Exception { + runPaginationTest(t, rowsFn, itemAssertions); } } From b7856a9c09d47c4a44c58c28398fb9c2fb80e74c Mon Sep 17 00:00:00 2001 From: Rich Ellis Date: Fri, 16 May 2025 13:39:55 +0100 Subject: [PATCH 40/43] test: move parameterized errors to MockCloudant --- .../cloudant/features/ChangesFollowerTest.java | 14 ++------------ .../cloud/cloudant/features/MockCloudant.java | 16 +++++++++++++--- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/ChangesFollowerTest.java b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/ChangesFollowerTest.java index 985f3ecc0..7ab1b72b7 100644 --- a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/ChangesFollowerTest.java +++ b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/ChangesFollowerTest.java @@ -37,16 +37,6 @@ public class ChangesFollowerTest { - Object[][] errorsAsTestObjectArray(Collection errors) { - Object[][] tests = new Object[errors.size()][]; - int index = 0; - for (MockError e : errors) { - tests[index] = new Object[]{e}; - index++; - } - return tests; - } - /** * Make a collection of mock instructions that alternate between * successful batches and errors. @@ -97,12 +87,12 @@ Supplier> getAlternatingBatchErrorThenPerpetualSu @DataProvider(name = "terminalErrors") Object[][] getTerminalErrors() { - return errorsAsTestObjectArray(MockError.getTerminalErrors()); + return MockError.errorsAsTestObjectArray(MockError.getTerminalErrors()); } @DataProvider(name = "transientErrors") Object[][] getTransientErrors() { - return errorsAsTestObjectArray(MockError.getTransientErrors()); + return MockError.errorsAsTestObjectArray(MockError.getTransientErrors()); } @DataProvider(name = "invalidTimeoutClients") diff --git a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/MockCloudant.java b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/MockCloudant.java index c559f118e..46e908d16 100644 --- a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/MockCloudant.java +++ b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/MockCloudant.java @@ -78,7 +78,7 @@ public MockCloudant(Supplier> instructionSupplier) { /** * Mock errors */ - enum MockError { + public enum MockError { TERMINAL_400, TERMINAL_401, TERMINAL_403, @@ -138,7 +138,7 @@ private okhttp3.Response makeResponse(okhttp3.Response.Builder rb, int code, Str return rb.build(); } - Class getExceptionClass() { + public Class getExceptionClass() { return getException().getClass(); } @@ -175,6 +175,16 @@ RuntimeException getException() { void throwException() throws RuntimeException { throw getException(); } + + public static Object[][] errorsAsTestObjectArray(Collection errors) { + Object[][] tests = new Object[errors.size()][]; + int index = 0; + for (MockError e : errors) { + tests[index] = new Object[]{e}; + index++; + } + return tests; + } } /** @@ -252,7 +262,7 @@ public static class QueuedSupplier implements Supplier> { protected final Collection> instructions; protected final Queue> q; - protected QueuedSupplier(Collection> instructions) { + public QueuedSupplier(Collection> instructions) { this.instructions = instructions; this.q = new ArrayDeque<>(instructions); } From 066ebd6214464110779759afb84fdc6a51a9a28c Mon Sep 17 00:00:00 2001 From: Rich Ellis Date: Fri, 16 May 2025 15:30:41 +0100 Subject: [PATCH 41/43] test: add pagination error cases --- .../pagination/PaginationOperationTest.java | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/PaginationOperationTest.java b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/PaginationOperationTest.java index e2098a1c7..0e9e13f90 100644 --- a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/PaginationOperationTest.java +++ b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/PaginationOperationTest.java @@ -15,9 +15,11 @@ package com.ibm.cloud.cloudant.features.pagination; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.function.BiConsumer; @@ -27,9 +29,11 @@ import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import com.ibm.cloud.cloudant.features.MockCloudant; +import com.ibm.cloud.cloudant.features.MockCloudant.MockError; import com.ibm.cloud.cloudant.features.MockCloudant.MockInstruction; import com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.MockPagerCloudant; import com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.OptionsProvider; +import com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.PageSupplier; import com.ibm.cloud.cloudant.features.pagination.PaginationTestHelpers.PageSupplierFactory; public abstract class PaginationOperationTest { @@ -171,6 +175,13 @@ Object[][] getPageSets() { }; } + @DataProvider(name = "errorSuppliers") + Iterator getErrorSuppliers() { + return Arrays.stream(MockError.values()).flatMap(mockError -> { + return List.of(new Object[] {mockError, true}, new Object[] {mockError, false}).stream(); + }).iterator(); + } + Object[] makeTestCase(int total, int pageSize) { return new Object[] {new TestCase(total, pageSize, plusOnePaging)}; } @@ -195,6 +206,22 @@ private void runPaginationTest(TestCase t, } } + private void runPaginationErrorTest(Function, PagingResult> pagingFunction, + MockError error, boolean errorOnFirstPage) throws Exception { + int pageSize = Long.valueOf(OptionsHandler.MAX_LIMIT).intValue(); + List> instructions = new ArrayList<>(); + if (!errorOnFirstPage) { + PageSupplier supplier = + this.pageSupplierFactory.newPageSupplier(2 * pageSize, pageSize); + instructions.add(supplier.get()); // Add a successful page + } + instructions.add(new MockInstruction<>(error)); // Add the error + Pagination pagination = + makeTestPagination(pageSize, new MockCloudant.QueuedSupplier(instructions)); + Assert.assertThrows("The correct exception should be received.", error.getExceptionClass(), + () -> pagingFunction.apply(pagination)); + } + // Check validation is wired @Test public void testValidationEnabled() throws Exception { @@ -236,4 +263,35 @@ public void testRows(TestCase t) throws Exception { runPaginationTest(t, rowsFn, itemAssertions); } + // Check Pager errors + @Test(dataProvider = "errorSuppliers") + public void testPagerErrors(MockError error, boolean errorOnFirstPage) throws Exception { + runPaginationErrorTest(pagerFn, error, errorOnFirstPage); + } + + // Check PageStream errors + @Test(dataProvider = "errorSuppliers") + public void testPageStreamErrors(MockError error, boolean errorOnFirstPage) throws Exception { + runPaginationErrorTest(pageStreamFn, error, errorOnFirstPage); + } + + // Check Pages errors + @Test(dataProvider = "errorSuppliers") + public void testPagesErrors(MockError error, boolean errorOnFirstPage) throws Exception { + runPaginationErrorTest(pagesFn, error, errorOnFirstPage); + } + + // Check RowStream errors + @Test(dataProvider = "errorSuppliers") + public void testRowStreamErrors(MockError error, boolean errorOnFirstPage) throws Exception { + runPaginationErrorTest(rowStreamFn, error, errorOnFirstPage); + } + + // Check Rows errors + @Test(dataProvider = "errorSuppliers") + public void testRowsErrors(MockError error, boolean errorOnFirstPage) throws Exception { + runPaginationErrorTest(rowsFn, error, errorOnFirstPage); + } + + } From 3e0244407bc40ffd19d98b48cdf2038b383ba236 Mon Sep 17 00:00:00 2001 From: Rich Ellis Date: Fri, 23 May 2025 12:11:23 +0100 Subject: [PATCH 42/43] test: add count assertions on error tests --- .../pagination/PaginationOperationTest.java | 83 +++++++++++++++---- 1 file changed, 69 insertions(+), 14 deletions(-) diff --git a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/PaginationOperationTest.java b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/PaginationOperationTest.java index 0e9e13f90..162b3911d 100644 --- a/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/PaginationOperationTest.java +++ b/modules/cloudant/src/test/java/com/ibm/cloud/cloudant/features/pagination/PaginationOperationTest.java @@ -25,6 +25,7 @@ import java.util.function.BiConsumer; import java.util.function.Function; import java.util.function.Supplier; +import java.util.stream.Stream; import org.testng.Assert; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; @@ -46,36 +47,56 @@ public abstract class PaginationOperationTest { PagingResult result = new PagingResult<>(); Pager pager = p.pager(); while (pager.hasNext()) { - List page = pager.getNext(); - result.handlePage(page); + try { + List page = pager.getNext(); + result.handlePage(page); + } catch (Exception e) { + result.handleException(e); + } } return result; }; private Function, PagingResult> pageStreamFn = p -> { PagingResult result = new PagingResult<>(); - p.pageStream().forEach(result::handlePage); + try { + p.pageStream().forEach(result::handlePage); + } catch (Exception e) { + result.handleException(e); + } return result; }; private Function, PagingResult> pagesFn = p -> { PagingResult result = new PagingResult<>(); - for (List page : p.pages()) { - result.handlePage(page); + try { + for (List page : p.pages()) { + result.handlePage(page); + } + } catch (Exception e) { + result.handleException(e); } return result; }; private Function, PagingResult> rowStreamFn = p -> { PagingResult result = new PagingResult<>(); - p.rowStream().forEach(result::handleItem); + try { + p.rowStream().forEach(result::handleItem); + } catch (Exception e) { + result.handleException(e); + } return result; }; private Function, PagingResult> rowsFn = p -> { PagingResult result = new PagingResult<>(); - for (I row : p.rows()) { - result.handleItem(row); + try { + for (I row : p.rows()) { + result.handleItem(row); + } + } catch (Exception e) { + result.handleException(e); } return result; }; @@ -124,6 +145,7 @@ private static final class PagingResult { boolean assertable = false; Integer pageCount = 0; List items = new ArrayList<>(); + List errors = new ArrayList<>(); private void setAssertable() { if (!this.assertable) { @@ -131,6 +153,11 @@ private void setAssertable() { } } + private void handleException(Exception e) { + setAssertable(); + errors.add(e); + } + private void handleItem(I item) { setAssertable(); items.add(item); @@ -209,17 +236,45 @@ private void runPaginationTest(TestCase t, private void runPaginationErrorTest(Function, PagingResult> pagingFunction, MockError error, boolean errorOnFirstPage) throws Exception { int pageSize = Long.valueOf(OptionsHandler.MAX_LIMIT).intValue(); + int wholePages = 2; + int expectedItems = wholePages * pageSize; List> instructions = new ArrayList<>(); + PageSupplier supplier = this.pageSupplierFactory.newPageSupplier(expectedItems, pageSize); if (!errorOnFirstPage) { - PageSupplier supplier = - this.pageSupplierFactory.newPageSupplier(2 * pageSize, pageSize); instructions.add(supplier.get()); // Add a successful page } instructions.add(new MockInstruction<>(error)); // Add the error + // Add remaining instructions that might be needed + // For error on first page it is first page, second page, empty page (i.e. 3) + // For error on second page it is second page, empty page (i.e. 2) + int remainingInstructions = (errorOnFirstPage ? 1 : 0) + wholePages; + Stream.generate(supplier).limit(remainingInstructions).forEach(instructions::add); Pagination pagination = makeTestPagination(pageSize, new MockCloudant.QueuedSupplier(instructions)); - Assert.assertThrows("The correct exception should be received.", error.getExceptionClass(), - () -> pagingFunction.apply(pagination)); + PagingResult r = pagingFunction.apply(pagination); + // Assert that there was a single exception + Assert.assertEquals(r.errors.size(), 1, "There should be a single exception."); + Assert.assertEquals(r.errors.get(0).getClass(), error.getExceptionClass(), + "The correct exception should be received."); + TestCase t; + if (pagerFn.equals(pagingFunction)) { + // If using pager then expect all pages/items + t = new TestCase(expectedItems, pageSize, this.plusOnePaging); + r.assertPageCount(new TestCase(expectedItems, pageSize, this.plusOnePaging)); + r.assertItemCount(new TestCase(expectedItems, pageSize, this.plusOnePaging)); + } else { + // Else (pages, rows, streams) expect only up to the error + int expectedPages = (errorOnFirstPage) ? 0 : 1; + expectedItems = expectedPages * pageSize; + if (!rowsFn.equals(pagingFunction) && !rowStreamFn.equals(pagingFunction)) { + // Only assert page count if we are handling pages + Assert.assertEquals(r.pageCount, expectedPages, + "There should be the correct number of pages."); + } + Assert.assertEquals(r.items.size(), expectedItems, + "There should be the correct number of items."); + } + } // Check validation is wired @@ -269,13 +324,13 @@ public void testPagerErrors(MockError error, boolean errorOnFirstPage) throws Ex runPaginationErrorTest(pagerFn, error, errorOnFirstPage); } - // Check PageStream errors + // Check PageStream errors @Test(dataProvider = "errorSuppliers") public void testPageStreamErrors(MockError error, boolean errorOnFirstPage) throws Exception { runPaginationErrorTest(pageStreamFn, error, errorOnFirstPage); } - // Check Pages errors + // Check Pages errors @Test(dataProvider = "errorSuppliers") public void testPagesErrors(MockError error, boolean errorOnFirstPage) throws Exception { runPaginationErrorTest(pagesFn, error, errorOnFirstPage); From 06a8b4e6a5bc4b166be9b0aac4d15f2492ab02a5 Mon Sep 17 00:00:00 2001 From: Rich Ellis Date: Thu, 12 Jun 2025 12:40:29 +0100 Subject: [PATCH 43/43] docs: pagination examples --- .../pagination/AllDocsPagination.java | 95 ++++++++++++++++++ .../pagination/DesignDocsPagination.java | 94 ++++++++++++++++++ .../features/pagination/FindPagination.java | 98 +++++++++++++++++++ .../PartitionAllDocsPagination.java | 95 ++++++++++++++++++ .../pagination/PartitionFindPagination.java | 98 +++++++++++++++++++ .../pagination/PartitionSearchPagination.java | 98 +++++++++++++++++++ .../pagination/PartitionViewPagination.java | 97 ++++++++++++++++++ .../features/pagination/SearchPagination.java | 97 ++++++++++++++++++ .../features/pagination/ViewPagination.java | 96 ++++++++++++++++++ 9 files changed, 868 insertions(+) create mode 100644 modules/examples/src/main/java/features/pagination/AllDocsPagination.java create mode 100644 modules/examples/src/main/java/features/pagination/DesignDocsPagination.java create mode 100644 modules/examples/src/main/java/features/pagination/FindPagination.java create mode 100644 modules/examples/src/main/java/features/pagination/PartitionAllDocsPagination.java create mode 100644 modules/examples/src/main/java/features/pagination/PartitionFindPagination.java create mode 100644 modules/examples/src/main/java/features/pagination/PartitionSearchPagination.java create mode 100644 modules/examples/src/main/java/features/pagination/PartitionViewPagination.java create mode 100644 modules/examples/src/main/java/features/pagination/SearchPagination.java create mode 100644 modules/examples/src/main/java/features/pagination/ViewPagination.java diff --git a/modules/examples/src/main/java/features/pagination/AllDocsPagination.java b/modules/examples/src/main/java/features/pagination/AllDocsPagination.java new file mode 100644 index 000000000..eb928b5e6 --- /dev/null +++ b/modules/examples/src/main/java/features/pagination/AllDocsPagination.java @@ -0,0 +1,95 @@ +/** + * © Copyright IBM Corporation 2025. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package features.pagination; + +import java.util.List; +import com.ibm.cloud.cloudant.v1.Cloudant; +import com.ibm.cloud.cloudant.v1.model.DocsResultRow; +import com.ibm.cloud.cloudant.v1.model.PostAllDocsOptions; +import com.ibm.cloud.cloudant.features.pagination.Pager; +import com.ibm.cloud.cloudant.features.pagination.Pagination; + +public class AllDocsPagination { + + public static void main(String[] args) { + + // Initialize service + Cloudant service = Cloudant.newInstance(); + + // Setup options + PostAllDocsOptions options = new PostAllDocsOptions.Builder() + .db("orders") // example database name + .limit(50) // limit option sets the page size + .startKey("abc") // start from example doc ID abc + .build(); + + // Create pagination + Pagination pagination = Pagination.newPagination(service, options); + // pagination can be reused without side-effects as a factory for iterables, streams or pagers + // options are fixed at pagination creation time + + // Option: iterate pages + // Ideal for using an enhanced for loop with each page. + // The Iterable returned from pages() is reusable in that + // calling iterator() returns a new iterator each time. + // The returned iterators, however, are single use. + for (List page : pagination.pages()) { + // Do something with page + } + + // Option: stream pages + // Ideal for lazy functional processing of pages and total page limits + pagination.pageStream() // a new stream of the pages + .limit(20) // use Java stream limit to get only the first 20 pages (different from 50 limit used for page size) + .forEach(page -> { // stream operations e.g. terminal forEach + // Do something with page + }); + + // Option: iterate rows + // Ideal for using an enhanced for loop with each row. + // The Iterable returned from rows() is reusable in that + // calling iterator() returns a new iterator each time. + // The returned iterators, however, are single use. + for (DocsResultRow row : pagination.rows()) { + // Do something with row + } + + // Option: stream rows + // Ideal for lazy functional processing of rows and total row limits + pagination.rowStream() // a new stream of the result rows + .limit(1000) // use Java stream limit to cap at 1000 rows (20 page requests of 50 rows each in this example) + .forEach(row -> { // stream operations e.g. terminal forEach + // Do something with row + }); + + // Option: use pager next page + // For retrieving one page at a time with a method call. + Pager pagePager = pagination.pager(); + if (pagePager.hasNext()) { + List page = pagePager.getNext(); + // Do something with page + } + + // Option: use pager all results + // For retrieving all result rows in a single list + // Note: all result rows may be very large! + // Preferably use streams/iterables instead of getAll for memory efficiency with large result sets. + Pager allPager = pagination.pager(); + List allRows = allPager.getAll(); + for (DocsResultRow row : allRows) { + // Do something with row + } + + } + +} diff --git a/modules/examples/src/main/java/features/pagination/DesignDocsPagination.java b/modules/examples/src/main/java/features/pagination/DesignDocsPagination.java new file mode 100644 index 000000000..b8e79fa88 --- /dev/null +++ b/modules/examples/src/main/java/features/pagination/DesignDocsPagination.java @@ -0,0 +1,94 @@ +/** + * © Copyright IBM Corporation 2025. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package features.pagination; + +import java.util.List; +import com.ibm.cloud.cloudant.v1.Cloudant; +import com.ibm.cloud.cloudant.v1.model.DocsResultRow; +import com.ibm.cloud.cloudant.v1.model.PostDesignDocsOptions; +import com.ibm.cloud.cloudant.features.pagination.Pager; +import com.ibm.cloud.cloudant.features.pagination.Pagination; + +public class DesignDocsPagination { + + public static void main(String[] args) { + + // Initialize service + Cloudant service = Cloudant.newInstance(); + + // Setup options + PostDesignDocsOptions options = new PostDesignDocsOptions.Builder() + .db("shoppers") // example database name + .limit(50) // limit option sets the page size + .build(); + + // Create pagination + Pagination pagination = Pagination.newPagination(service, options); + // pagination can be reused without side-effects as a factory for iterables, streams or pagers + // options are fixed at pagination creation time + + // Option: iterate pages + // Ideal for using an enhanced for loop with each page. + // The Iterable returned from pages() is reusable in that + // calling iterator() returns a new iterator each time. + // The returned iterators, however, are single use. + for (List page : pagination.pages()) { + // Do something with page + } + + // Option: stream pages + // Ideal for lazy functional processing of pages and total page limits + pagination.pageStream() // a new stream of the pages + .limit(20) // use Java stream limit to get only the first 20 pages (different from 50 limit used for page size) + .forEach(page -> { // stream operations e.g. terminal forEach + // Do something with page + }); + + // Option: iterate rows + // Ideal for using an enhanced for loop with each row. + // The Iterable returned from rows() is reusable in that + // calling iterator() returns a new iterator each time. + // The returned iterators, however, are single use. + for (DocsResultRow row : pagination.rows()) { + // Do something with row + } + + // Option: stream rows + // Ideal for lazy functional processing of rows and total row limits + pagination.rowStream() // a new stream of the rows + .limit(1000) // use Java stream limit to cap at 1000 rows (20 page requests of 50 rows each in this example) + .forEach(row -> { // stream operations e.g. terminal forEach + // Do something with row + }); + + // Option: use pager next page + // For retrieving one page at a time with a method call. + Pager pagePager = pagination.pager(); + if (pagePager.hasNext()) { + List page = pagePager.getNext(); + // Do something with page + } + + // Option: use pager all results + // For retrieving all result rows in a single list + // Note: all result rows may be very large! + // Preferably use streams/iterables instead of getAll for memory efficiency with large result sets. + Pager allPager = pagination.pager(); + List allRows = allPager.getAll(); + for (DocsResultRow row : allRows) { + // Do something with row + } + + } + +} diff --git a/modules/examples/src/main/java/features/pagination/FindPagination.java b/modules/examples/src/main/java/features/pagination/FindPagination.java new file mode 100644 index 000000000..9e1e5e863 --- /dev/null +++ b/modules/examples/src/main/java/features/pagination/FindPagination.java @@ -0,0 +1,98 @@ +/** + * © Copyright IBM Corporation 2025. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package features.pagination; + +import java.util.Collections; +import java.util.List; +import com.ibm.cloud.cloudant.v1.Cloudant; +import com.ibm.cloud.cloudant.v1.model.Document; +import com.ibm.cloud.cloudant.v1.model.PostFindOptions; +import com.ibm.cloud.cloudant.features.pagination.Pager; +import com.ibm.cloud.cloudant.features.pagination.Pagination; + +public class FindPagination { + + public static void main(String[] args) { + + // Initialize service + Cloudant service = Cloudant.newInstance(); + + // Setup options + PostFindOptions options = new PostFindOptions.Builder() + .db("shoppers") // Database name + .limit(50) // limit option sets the page size + .fields(List.of("_id", "type", "name", "email")) // return these fields + .selector(Collections.singletonMap("email_verified", "true")) // select docs with verified emails + .sort(Collections.singletonList(Collections.singletonMap("email", "desc"))) // sort descending by email + .build(); + + // Create pagination + Pagination pagination = Pagination.newPagination(service, options); + // pagination can be reused without side-effects as a factory for iterables, streams or pagers + // options are fixed at pagination creation time + + // Option: iterate pages + // Ideal for using an enhanced for loop with each page. + // The Iterable returned from pages() is reusable in that + // calling iterator() returns a new iterator each time. + // The returned iterators, however, are single use. + for (List page : pagination.pages()) { + // Do something with page + } + + // Option: stream pages + // Ideal for lazy functional processing of pages and total page limits + pagination.pageStream() // a new stream of the pages + .limit(20) // use Java stream limit to get only the first 20 pages (different from 50 limit used for page size) + .forEach(page -> { // stream operations e.g. terminal forEach + // Do something with page + }); + + // Option: iterate rows + // Ideal for using an enhanced for loop with each row. + // The Iterable returned from rows() is reusable in that + // calling iterator() returns a new iterator each time. + // The returned iterators, however, are single use. + for (Document row : pagination.rows()) { + // Do something with row + } + + // Option: stream rows + // Ideal for lazy functional processing of rows and total row limits + pagination.rowStream() // a new stream of the rows + .limit(1000) // use Java stream limit to cap at 1000 rows (20 page requests of 50 rows each in this example) + .forEach(row -> { // stream operations e.g. terminal forEach + // Do something with row + }); + + // Option: use pager next page + // For retrieving one page at a time with a method call. + Pager pagePager = pagination.pager(); + if (pagePager.hasNext()) { + List page = pagePager.getNext(); + // Do something with page + } + + // Option: use pager all results + // For retrieving all result rows in a single list + // Note: all result rows may be very large! + // Preferably use streams/iterables instead of getAll for memory efficiency with large result sets. + Pager allPager = pagination.pager(); + List allRows = allPager.getAll(); + for (Document row : allRows) { + // Do something with row + } + + } + +} diff --git a/modules/examples/src/main/java/features/pagination/PartitionAllDocsPagination.java b/modules/examples/src/main/java/features/pagination/PartitionAllDocsPagination.java new file mode 100644 index 000000000..39059bcb6 --- /dev/null +++ b/modules/examples/src/main/java/features/pagination/PartitionAllDocsPagination.java @@ -0,0 +1,95 @@ +/** + * © Copyright IBM Corporation 2025. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package features.pagination; + +import java.util.List; +import com.ibm.cloud.cloudant.v1.Cloudant; +import com.ibm.cloud.cloudant.v1.model.DocsResultRow; +import com.ibm.cloud.cloudant.v1.model.PostPartitionAllDocsOptions; +import com.ibm.cloud.cloudant.features.pagination.Pager; +import com.ibm.cloud.cloudant.features.pagination.Pagination; + +public class PartitionAllDocsPagination { + + public static void main(String[] args) { + + // Initialize service + Cloudant service = Cloudant.newInstance(); + + // Setup options + PostPartitionAllDocsOptions options = new PostPartitionAllDocsOptions.Builder() + .db("events") // example database name + .limit(50) // limit option sets the page size + .partitionKey("ns1HJS13AMkK") // query only this partition + .build(); + + // Create pagination + Pagination pagination = Pagination.newPagination(service, options); + // pagination can be reused without side-effects as a factory for iterables, streams or pagers + // options are fixed at pagination creation time + + // Option: iterate pages + // Ideal for using an enhanced for loop with each page. + // The Iterable returned from pages() is reusable in that + // calling iterator() returns a new iterator each time. + // The returned iterators, however, are single use. + for (List page : pagination.pages()) { + // Do something with page + } + + // Option: stream pages + // Ideal for lazy functional processing of pages and total page limits + pagination.pageStream() // a new stream of the pages + .limit(20) // use Java stream limit to get only the first 20 pages (different from 50 limit used for page size) + .forEach(page -> { // stream operations e.g. terminal forEach + // Do something with page + }); + + // Option: iterate rows + // Ideal for using an enhanced for loop with each row. + // The Iterable returned from rows() is reusable in that + // calling iterator() returns a new iterator each time. + // The returned iterators, however, are single use. + for (DocsResultRow row : pagination.rows()) { + // Do something with row + } + + // Option: stream rows + // Ideal for lazy functional processing of rows and total row limits + pagination.rowStream() // a new stream of the rows + .limit(1000) // use Java stream limit to cap at 1000 rows (20 page requests of 50 rows each in this example) + .forEach(row -> { // stream operations e.g. terminal forEach + // Do something with row + }); + + // Option: use pager next page + // For retrieving one page at a time with a method call. + Pager pagePager = pagination.pager(); + if (pagePager.hasNext()) { + List page = pagePager.getNext(); + // Do something with page + } + + // Option: use pager all results + // For retrieving all result rows in a single list + // Note: all result rows may be very large! + // Preferably use streams/iterables instead of getAll for memory efficiency with large result sets. + Pager allPager = pagination.pager(); + List allRows = allPager.getAll(); + for (DocsResultRow row : allRows) { + // Do something with row + } + + } + +} diff --git a/modules/examples/src/main/java/features/pagination/PartitionFindPagination.java b/modules/examples/src/main/java/features/pagination/PartitionFindPagination.java new file mode 100644 index 000000000..5d2cfc890 --- /dev/null +++ b/modules/examples/src/main/java/features/pagination/PartitionFindPagination.java @@ -0,0 +1,98 @@ +/** + * © Copyright IBM Corporation 2025. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package features.pagination; + +import java.util.Collections; +import java.util.List; +import com.ibm.cloud.cloudant.v1.Cloudant; +import com.ibm.cloud.cloudant.v1.model.Document; +import com.ibm.cloud.cloudant.v1.model.PostPartitionFindOptions; +import com.ibm.cloud.cloudant.features.pagination.Pager; +import com.ibm.cloud.cloudant.features.pagination.Pagination; + +public class PartitionFindPagination { + + public static void main(String[] args) { + + // Initialize service + Cloudant service = Cloudant.newInstance(); + + // Setup options + PostPartitionFindOptions options = new PostPartitionFindOptions.Builder() + .db("events") // example database name + .limit(50) // limit option sets the page size + .partitionKey("ns1HJS13AMkK") // query only this partition + .fields(List.of("productId", "eventType", "date")) // return these fields + .selector(Collections.singletonMap("userId", "abc123")) // select documents with "userId" field equal to "abc123" + .build(); + + // Create pagination + Pagination pagination = Pagination.newPagination(service, options); + // pagination can be reused without side-effects as a factory for iterables, streams or pagers + // options are fixed at pagination creation time + + // Option: iterate pages + // Ideal for using an enhanced for loop with each page. + // The Iterable returned from pages() is reusable in that + // calling iterator() returns a new iterator each time. + // The returned iterators, however, are single use. + for (List page : pagination.pages()) { + // Do something with page + } + + // Option: stream pages + // Ideal for lazy functional processing of pages and total page limits + pagination.pageStream() // a new stream of the pages + .limit(20) // use Java stream limit to get only the first 20 pages (different from 50 limit used for page size) + .forEach(page -> { // stream operations e.g. terminal forEach + // Do something with page + }); + + // Option: iterate rows + // Ideal for using an enhanced for loop with each row. + // The Iterable returned from rows() is reusable in that + // calling iterator() returns a new iterator each time. + // The returned iterators, however, are single use. + for (Document row : pagination.rows()) { + // Do something with row + } + + // Option: stream rows + // Ideal for lazy functional processing of rows and total row limits + pagination.rowStream() // a new stream of the rows + .limit(1000) // use Java stream limit to cap at 1000 rows (20 page requests of 50 rows each in this example) + .forEach(row -> { // stream operations e.g. terminal forEach + // Do something with row + }); + + // Option: use pager next page + // For retrieving one page at a time with a method call. + Pager pagePager = pagination.pager(); + if (pagePager.hasNext()) { + List page = pagePager.getNext(); + // Do something with page + } + + // Option: use pager all results + // For retrieving all result rows in a single list + // Note: all result rows may be very large! + // Preferably use streams/iterables instead of getAll for memory efficiency with large result sets. + Pager allPager = pagination.pager(); + List allRows = allPager.getAll(); + for (Document row : allRows) { + // Do something with row + } + + } + +} diff --git a/modules/examples/src/main/java/features/pagination/PartitionSearchPagination.java b/modules/examples/src/main/java/features/pagination/PartitionSearchPagination.java new file mode 100644 index 000000000..83c040758 --- /dev/null +++ b/modules/examples/src/main/java/features/pagination/PartitionSearchPagination.java @@ -0,0 +1,98 @@ +/** + * © Copyright IBM Corporation 2025. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package features.pagination; + +import java.util.List; +import com.ibm.cloud.cloudant.v1.Cloudant; +import com.ibm.cloud.cloudant.v1.model.SearchResultRow; +import com.ibm.cloud.cloudant.v1.model.PostPartitionSearchOptions; +import com.ibm.cloud.cloudant.features.pagination.Pager; +import com.ibm.cloud.cloudant.features.pagination.Pagination; + +public class PartitionSearchPagination { + + public static void main(String[] args) { + + // Initialize service + Cloudant service = Cloudant.newInstance(); + + // Setup options + PostPartitionSearchOptions options = new PostPartitionSearchOptions.Builder() + .db("events") // example database name + .limit(50) // limit option sets the page size + .partitionKey("ns1HJS13AMkK") // query only this partition + .ddoc("checkout") // use the checkout design document + .index("findByDate") // search in this index + .query("date:[2019-01-01T12:00:00.000Z TO 2019-01-31T12:00:00.000Z]") // Lucene search query + .build(); + + // Create pagination + Pagination pagination = Pagination.newPagination(service, options); + // pagination can be reused without side-effects as a factory for iterables, streams or pagers + // options are fixed at pagination creation time + + // Option: iterate pages + // Ideal for using an enhanced for loop with each page. + // The Iterable returned from pages() is reusable in that + // calling iterator() returns a new iterator each time. + // The returned iterators, however, are single use. + for (List page : pagination.pages()) { + // Do something with page + } + + // Option: stream pages + // Ideal for lazy functional processing of pages and total page limits + pagination.pageStream() // a new stream of the pages + .limit(20) // use Java stream limit to get only the first 20 pages (different from 50 limit used for page size) + .forEach(page -> { // stream operations e.g. terminal forEach + // Do something with page + }); + + // Option: iterate rows + // Ideal for using an enhanced for loop with each row. + // The Iterable returned from rows() is reusable in that + // calling iterator() returns a new iterator each time. + // The returned iterators, however, are single use. + for (SearchResultRow row : pagination.rows()) { + // Do something with row + } + + // Option: stream rows + // Ideal for lazy functional processing of rows and total row limits + pagination.rowStream() // a new stream of the rows + .limit(1000) // use Java stream limit to cap at 1000 rows (20 page requests of 50 rows each in this example) + .forEach(row -> { // stream operations e.g. terminal forEach + // Do something with row + }); + + // Option: use pager next page + // For retrieving one page at a time with a method call. + Pager pagePager = pagination.pager(); + if (pagePager.hasNext()) { + List page = pagePager.getNext(); + // Do something with page + } + + // Option: use pager all results + // For retrieving all result rows in a single list + // Note: all result rows may be very large! + // Preferably use streams/iterables instead of getAll for memory efficiency with large result sets. + Pager allPager = pagination.pager(); + List allRows = allPager.getAll(); + for (SearchResultRow row : allRows) { + // Do something with row + } + + } + +} diff --git a/modules/examples/src/main/java/features/pagination/PartitionViewPagination.java b/modules/examples/src/main/java/features/pagination/PartitionViewPagination.java new file mode 100644 index 000000000..92a040f18 --- /dev/null +++ b/modules/examples/src/main/java/features/pagination/PartitionViewPagination.java @@ -0,0 +1,97 @@ +/** + * © Copyright IBM Corporation 2025. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package features.pagination; + +import java.util.List; +import com.ibm.cloud.cloudant.v1.Cloudant; +import com.ibm.cloud.cloudant.v1.model.ViewResultRow; +import com.ibm.cloud.cloudant.v1.model.PostPartitionViewOptions; +import com.ibm.cloud.cloudant.features.pagination.Pager; +import com.ibm.cloud.cloudant.features.pagination.Pagination; + +public class PartitionViewPagination { + + public static void main(String[] args) { + + // Initialize service + Cloudant service = Cloudant.newInstance(); + + // Setup options + PostPartitionViewOptions options = new PostPartitionViewOptions.Builder() + .db("events") // example database name + .limit(50) // limit option sets the page size + .partitionKey("ns1HJS13AMkK") // query only this partition + .ddoc("checkout") // use the checkout design document + .view("byProductId") // the view to use + .build(); + + // Create pagination + Pagination pagination = Pagination.newPagination(service, options); + // pagination can be reused without side-effects as a factory for iterables, streams or pagers + // options are fixed at pagination creation time + + // Option: iterate pages + // Ideal for using an enhanced for loop with each page. + // The Iterable returned from pages() is reusable in that + // calling iterator() returns a new iterator each time. + // The returned iterators, however, are single use. + for (List page : pagination.pages()) { + // Do something with page + } + + // Option: stream pages + // Ideal for lazy functional processing of pages and total page limits + pagination.pageStream() // a new stream of the pages + .limit(20) // use Java stream limit to get only the first 20 pages (different from 50 limit used for page size) + .forEach(page -> { // stream operations e.g. terminal forEach + // Do something with page + }); + + // Option: iterate rows + // Ideal for using an enhanced for loop with each row. + // The Iterable returned from rows() is reusable in that + // calling iterator() returns a new iterator each time. + // The returned iterators, however, are single use. + for (ViewResultRow row : pagination.rows()) { + // Do something with row + } + + // Option: stream rows + // Ideal for lazy functional processing of rows and total row limits + pagination.rowStream() // a new stream of the rows + .limit(1000) // use Java stream limit to cap at 1000 rows (20 page requests of 50 rows each in this example) + .forEach(row -> { // stream operations e.g. terminal forEach + // Do something with row + }); + + // Option: use pager next page + // For retrieving one page at a time with a method call. + Pager pagePager = pagination.pager(); + if (pagePager.hasNext()) { + List page = pagePager.getNext(); + // Do something with page + } + + // Option: use pager all results + // For retrieving all result rows in a single list + // Note: all result rows may be very large! + // Preferably use streams/iterables instead of getAll for memory efficiency with large result sets. + Pager allPager = pagination.pager(); + List allRows = allPager.getAll(); + for (ViewResultRow row : allRows) { + // Do something with row + } + + } + +} diff --git a/modules/examples/src/main/java/features/pagination/SearchPagination.java b/modules/examples/src/main/java/features/pagination/SearchPagination.java new file mode 100644 index 000000000..d905984b7 --- /dev/null +++ b/modules/examples/src/main/java/features/pagination/SearchPagination.java @@ -0,0 +1,97 @@ +/** + * © Copyright IBM Corporation 2025. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package features.pagination; + +import java.util.List; +import com.ibm.cloud.cloudant.v1.Cloudant; +import com.ibm.cloud.cloudant.v1.model.SearchResultRow; +import com.ibm.cloud.cloudant.v1.model.PostSearchOptions; +import com.ibm.cloud.cloudant.features.pagination.Pager; +import com.ibm.cloud.cloudant.features.pagination.Pagination; + +public class SearchPagination { + + public static void main(String[] args) { + + // Initialize service + Cloudant service = Cloudant.newInstance(); + + // Setup options + PostSearchOptions options = new PostSearchOptions.Builder() + .db("shoppers") // example database name + .limit(50) // limit option sets the page size + .ddoc("allUsers") // use the allUsers design document + .index("activeUsers") // search in this index + .query("name:Jane* AND active:True") // Lucene search query + .build(); + + // Create pagination + Pagination pagination = Pagination.newPagination(service, options); + // pagination can be reused without side-effects as a factory for iterables, streams or pagers + // options are fixed at pagination creation time + + // Option: iterate pages + // Ideal for using an enhanced for loop with each page. + // The Iterable returned from pages() is reusable in that + // calling iterator() returns a new iterator each time. + // The returned iterators, however, are single use. + for (List page : pagination.pages()) { + // Do something with page + } + + // Option: stream pages + // Ideal for lazy functional processing of pages and total page limits + pagination.pageStream() // a new stream of the pages + .limit(20) // use Java stream limit to get only the first 20 pages (different from 50 limit used for page size) + .forEach(page -> { // stream operations e.g. terminal forEach + // Do something with page + }); + + // Option: iterate rows + // Ideal for using an enhanced for loop with each row. + // The Iterable returned from rows() is reusable in that + // calling iterator() returns a new iterator each time. + // The returned iterators, however, are single use. + for (SearchResultRow row : pagination.rows()) { + // Do something with row + } + + // Option: stream rows + // Ideal for lazy functional processing of rows and total row limits + pagination.rowStream() // a new stream of the rows + .limit(1000) // use Java stream limit to cap at 1000 rows (20 page requests of 50 rows each in this example) + .forEach(row -> { // stream operations e.g. terminal forEach + // Do something with row + }); + + // Option: use pager next page + // For retrieving one page at a time with a method call. + Pager pagePager = pagination.pager(); + if (pagePager.hasNext()) { + List page = pagePager.getNext(); + // Do something with page + } + + // Option: use pager all results + // For retrieving all result rows in a single list + // Note: all result rows may be very large! + // Preferably use streams/iterables instead of getAll for memory efficiency with large result sets. + Pager allPager = pagination.pager(); + List allRows = allPager.getAll(); + for (SearchResultRow row : allRows) { + // Do something with row + } + + } + +} diff --git a/modules/examples/src/main/java/features/pagination/ViewPagination.java b/modules/examples/src/main/java/features/pagination/ViewPagination.java new file mode 100644 index 000000000..e231d657d --- /dev/null +++ b/modules/examples/src/main/java/features/pagination/ViewPagination.java @@ -0,0 +1,96 @@ +/** + * © Copyright IBM Corporation 2025. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package features.pagination; + +import java.util.List; +import com.ibm.cloud.cloudant.v1.Cloudant; +import com.ibm.cloud.cloudant.v1.model.ViewResultRow; +import com.ibm.cloud.cloudant.v1.model.PostViewOptions; +import com.ibm.cloud.cloudant.features.pagination.Pager; +import com.ibm.cloud.cloudant.features.pagination.Pagination; + +public class ViewPagination { + + public static void main(String[] args) { + + // Initialize service + Cloudant service = Cloudant.newInstance(); + + // Setup options + PostViewOptions options = new PostViewOptions.Builder() + .db("shoppers") // example database name + .limit(50) // limit option sets the page size + .ddoc("allUsers") // use the allUsers design document + .view("getVerifiedEmails") // the view to use + .build(); + + // Create pagination + Pagination pagination = Pagination.newPagination(service, options); + // pagination can be reused without side-effects as a factory for iterables, streams or pagers + // options are fixed at pagination creation time + + // Option: iterate pages + // Ideal for using an enhanced for loop with each page. + // The Iterable returned from pages() is reusable in that + // calling iterator() returns a new iterator each time. + // The returned iterators, however, are single use. + for (List page : pagination.pages()) { + // Do something with page + } + + // Option: stream pages + // Ideal for lazy functional processing of pages and total page limits + pagination.pageStream() // a new stream of the pages + .limit(20) // use Java stream limit to get only the first 20 pages (different from 50 limit used for page size) + .forEach(page -> { // stream operations e.g. terminal forEach + // Do something with page + }); + + // Option: iterate rows + // Ideal for using an enhanced for loop with each row. + // The Iterable returned from rows() is reusable in that + // calling iterator() returns a new iterator each time. + // The returned iterators, however, are single use. + for (ViewResultRow row : pagination.rows()) { + // Do something with row + } + + // Option: stream rows + // Ideal for lazy functional processing of rows and total row limits + pagination.rowStream() // a new stream of the rows + .limit(1000) // use Java stream limit to cap at 1000 rows (20 page requests of 50 rows each in this example) + .forEach(row -> { // stream operations e.g. terminal forEach + // Do something with row + }); + + // Option: use pager next page + // For retrieving one page at a time with a method call. + Pager pagePager = pagination.pager(); + if (pagePager.hasNext()) { + List page = pagePager.getNext(); + // Do something with page + } + + // Option: use pager all results + // For retrieving all result rows in a single list + // Note: all result rows may be very large! + // Preferably use streams/iterables instead of getAll for memory efficiency with large result sets. + Pager allPager = pagination.pager(); + List allRows = allPager.getAll(); + for (ViewResultRow row : allRows) { + // Do something with row + } + + } + +}