diff --git a/data-model/src/main/java/io/micronaut/data/annotation/RepositoryConfiguration.java b/data-model/src/main/java/io/micronaut/data/annotation/RepositoryConfiguration.java index 24bcb44b8d9..268a146319c 100644 --- a/data-model/src/main/java/io/micronaut/data/annotation/RepositoryConfiguration.java +++ b/data-model/src/main/java/io/micronaut/data/annotation/RepositoryConfiguration.java @@ -15,6 +15,7 @@ */ package io.micronaut.data.annotation; +import io.micronaut.data.model.CursoredPage; import io.micronaut.data.model.Page; import io.micronaut.data.model.Pageable; import io.micronaut.data.model.Slice; @@ -59,7 +60,8 @@ TypeRole[] typeRoles() default { @TypeRole(role = TypeRole.PAGEABLE, type = Pageable.class), @TypeRole(role = TypeRole.SORT, type = Sort.class), @TypeRole(role = TypeRole.SLICE, type = Slice.class), - @TypeRole(role = TypeRole.PAGE, type = Page.class) + @TypeRole(role = TypeRole.PAGE, type = Page.class), + @TypeRole(role = TypeRole.CURSORED_PAGE, type = CursoredPage.class) }; /** diff --git a/data-model/src/main/java/io/micronaut/data/annotation/TypeRole.java b/data-model/src/main/java/io/micronaut/data/annotation/TypeRole.java index efaa389a871..810510209a0 100644 --- a/data-model/src/main/java/io/micronaut/data/annotation/TypeRole.java +++ b/data-model/src/main/java/io/micronaut/data/annotation/TypeRole.java @@ -75,6 +75,11 @@ */ String PAGE = "page"; + /** + * The parameter that is used to represent a {@link io.micronaut.data.model.CursoredPage}. + */ + String CURSORED_PAGE = "cursoredPage"; + /** * The name of the role. * @return The role name diff --git a/data-model/src/main/java/io/micronaut/data/intercept/FindCursoredPageInterceptor.java b/data-model/src/main/java/io/micronaut/data/intercept/FindCursoredPageInterceptor.java new file mode 100644 index 00000000000..3f287398ab0 --- /dev/null +++ b/data-model/src/main/java/io/micronaut/data/intercept/FindCursoredPageInterceptor.java @@ -0,0 +1,27 @@ +/* + * Copyright 2017-2020 original authors + * + * 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 + * + * https://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 io.micronaut.data.intercept; + +/** + * An interceptor that handles a return type of {@link io.micronaut.data.model.CursoredPage}. + * + * @author Andriy Dmytruk + * @param The declaring type + * @param The return type + * @since 4.8.0 + */ +public interface FindCursoredPageInterceptor extends DataInterceptor { +} diff --git a/data-model/src/main/java/io/micronaut/data/model/CursoredPage.java b/data-model/src/main/java/io/micronaut/data/model/CursoredPage.java new file mode 100644 index 00000000000..5d2b63df38c --- /dev/null +++ b/data-model/src/main/java/io/micronaut/data/model/CursoredPage.java @@ -0,0 +1,182 @@ +/* + * Copyright 2017-2020 original authors + * + * 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 + * + * https://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 io.micronaut.data.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import io.micronaut.context.annotation.DefaultImplementation; +import io.micronaut.core.annotation.NonNull; +import io.micronaut.core.annotation.Nullable; +import io.micronaut.core.annotation.ReflectiveAccess; +import io.micronaut.core.annotation.TypeHint; +import io.micronaut.data.model.Pageable.Cursor; +import io.micronaut.data.model.Pageable.Mode; +import io.micronaut.serde.annotation.Serdeable; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * Inspired by the Jakarta's {@code CursoredPage}, this models a type that supports + * pagination operations with cursors. + * + *

A CursoredPage is a result set associated with a particular {@link Pageable} that includes + * a calculation of the total size of page of records.

+ * + * @param The generic type + * @author Andriy Dmytruk + * @since 4.8.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@TypeHint(CursoredPage.class) +@JsonDeserialize(as = DefaultCursoredPage.class) +@Serdeable +@DefaultImplementation(DefaultCursoredPage.class) +public interface CursoredPage extends Page { + + CursoredPage EMPTY = new DefaultCursoredPage<>(Collections.emptyList(), Pageable.unpaged(), Collections.emptyList(), null); + + /** + * @return Whether this {@link CursoredPage} contains the total count of the records + * @since 4.8.0 + */ + boolean hasTotalSize(); + + /** + * Get the total count of all the records that can be given by this query. + * The method may produce a {@link IllegalStateException} if the {@link Pageable} request + * did not ask for total size. + * + * @return The total size of the all records. + */ + long getTotalSize(); + + /** + * Get the total count of pages that can be given by this query. + * The method may produce a {@link IllegalStateException} if the {@link Pageable} request + * did not ask for total size. + * + * @return The total page of pages + */ + default int getTotalPages() { + int size = getSize(); + return size == 0 ? 1 : (int) Math.ceil((double) getTotalSize() / (double) size); + } + + @Override + default boolean hasNext() { + Pageable pageable = getPageable(); + if (pageable.getMode() == Mode.CURSOR_NEXT) { + return getContent().size() == pageable.getSize(); + } else { + return true; + } + } + + @Override + default boolean hasPrevious() { + Pageable pageable = getPageable(); + if (pageable.getMode() == Mode.CURSOR_PREVIOUS) { + return getContent().size() == pageable.getSize(); + } else { + return true; + } + } + + @Override + default CursoredPageable nextPageable() { + Pageable pageable = getPageable(); + Cursor cursor = getCursor(getCursors().size() - 1).orElse(pageable.cursor().orElse(null)); + return Pageable.afterCursor(cursor, pageable.getNumber() + 1, pageable.getSize(), pageable.getSort()); + } + + @Override + default CursoredPageable previousPageable() { + Pageable pageable = getPageable(); + Cursor cursor = getCursor(0).orElse(pageable.cursor().orElse(null)); + return Pageable.beforeCursor(cursor, Math.max(0, pageable.getNumber() - 1), pageable.getSize(), pageable.getSort()); + } + + + /** + * Maps the content with the given function. + * + * @param function The function to apply to each element in the content. + * @param The type returned by the function + * @return A new slice with the mapped content + */ + @Override + default @NonNull CursoredPage map(Function function) { + List content = getContent().stream().map(function).collect(Collectors.toList()); + return new DefaultCursoredPage<>(content, getPageable(), getCursors(), getTotalSize()); + } + + /** + * Creates a cursored page from the given content, pageable, cursors and totalSize. + * + * @param content The content + * @param pageable The pageable + * @param cursors The cursors for cursored pagination + * @param totalSize The total size + * @param The generic type + * @return The slice + */ + @JsonCreator + @ReflectiveAccess + static @NonNull CursoredPage of( + @JsonProperty("content") @NonNull List content, + @JsonProperty("pageable") @NonNull Pageable pageable, + @JsonProperty("cursors") @Nullable List cursors, + @JsonProperty("totalSize") @Nullable Long totalSize + ) { + return new DefaultCursoredPage<>(content, pageable, cursors, totalSize); + } + + /** + * Get cursor at the given position or empty if no such cursor exists. + * There must be a cursor for each of the data entities in the same order. + * To start pagination after or before a cursor create a pageable from it using the + * same sorting as before. + * + * @param i The index of cursor to retrieve. + * @return The cursor at the provided index. + */ + Optional getCursor(int i); + + /** + * Get all the cursors. + * + * @see #getCursor(int) getCursor(i) for more details. + * @return All the cursors + */ + List getCursors(); + + /** + * Creates an empty page object. + * @param The generic type + * @return The slice + */ + @SuppressWarnings("unchecked") + static @NonNull CursoredPage empty() { + return (CursoredPage) EMPTY; + } + +} diff --git a/data-model/src/main/java/io/micronaut/data/model/CursoredPageable.java b/data-model/src/main/java/io/micronaut/data/model/CursoredPageable.java index fe98ef8a6ce..53bef1495bb 100644 --- a/data-model/src/main/java/io/micronaut/data/model/CursoredPageable.java +++ b/data-model/src/main/java/io/micronaut/data/model/CursoredPageable.java @@ -25,7 +25,7 @@ import io.micronaut.serde.annotation.Serdeable; /** - * Models pageable data that uses a currentCursor. + * Models a pageable request that uses a cursor. * * @author Andriy Dmytruk * @since 4.8.0 @@ -35,13 +35,6 @@ @JsonIgnoreProperties(ignoreUnknown = true) public interface CursoredPageable extends Pageable { - /** - * Constant for no pagination. - */ - CursoredPageable UNPAGED = new DefaultCursoredPageable( - -1, null, Mode.CURSOR_NEXT, 0, Sort.UNSORTED, true - ); - /** * Whether the pageable is traversing backwards. * @@ -62,7 +55,7 @@ default Mode getMode() { */ static @NonNull CursoredPageable from(Sort sort) { if (sort == null) { - return UNPAGED; + sort = Sort.UNSORTED; } return new DefaultCursoredPageable( -1, null, Mode.CURSOR_NEXT, 0, sort, true @@ -113,11 +106,4 @@ default Mode getMode() { return new DefaultCursoredPageable(size, cursor, mode, page, sort, requestTotal); } - /** - * @return A new instance without paging data. - */ - static @NonNull CursoredPageable unpaged() { - return UNPAGED; - } - } diff --git a/data-model/src/main/java/io/micronaut/data/model/DefaultCursoredPage.java b/data-model/src/main/java/io/micronaut/data/model/DefaultCursoredPage.java index 48d3d7491dc..423eca438e2 100644 --- a/data-model/src/main/java/io/micronaut/data/model/DefaultCursoredPage.java +++ b/data-model/src/main/java/io/micronaut/data/model/DefaultCursoredPage.java @@ -20,7 +20,6 @@ import io.micronaut.core.annotation.Creator; import io.micronaut.core.annotation.ReflectiveAccess; import io.micronaut.data.model.Pageable.Cursor; -import io.micronaut.data.model.Pageable.Mode; import io.micronaut.serde.annotation.Serdeable; import java.util.List; @@ -35,7 +34,7 @@ * @param The generic type */ @Serdeable -class DefaultCursoredPage extends DefaultPage { +class DefaultCursoredPage extends DefaultPage implements CursoredPage { private final List cursors; @@ -78,41 +77,12 @@ public boolean equals(Object o) { @Override public Optional getCursor(int i) { - return i >= cursors.size() ? Optional.empty() : Optional.of(cursors.get(i)); + return i >= cursors.size() || i < 0 ? Optional.empty() : Optional.of(cursors.get(i)); } @Override - public boolean hasNext() { - Pageable pageable = getPageable(); - if (pageable.getMode() == Mode.CURSOR_NEXT) { - return cursors.size() == pageable.getSize(); - } else { - return true; - } - } - - @Override - public boolean hasPrevious() { - Pageable pageable = getPageable(); - if (pageable.getMode() == Mode.CURSOR_PREVIOUS) { - return cursors.size() == pageable.getSize(); - } else { - return true; - } - } - - @Override - public Pageable nextPageable() { - Pageable pageable = getPageable(); - Cursor cursor = cursors.isEmpty() ? pageable.cursor().orElse(null) : cursors.get(cursors.size() - 1); - return Pageable.afterCursor(cursor, pageable.getNumber() + 1, pageable.getSize(), pageable.getSort()); - } - - @Override - public Pageable previousPageable() { - Pageable pageable = getPageable(); - Cursor cursor = cursors.isEmpty() ? pageable.cursor().orElse(null) : cursors.get(0); - return Pageable.beforeCursor(cursor, Math.max(0, pageable.getNumber() - 1), pageable.getSize(), pageable.getSort()); + public List getCursors() { + return cursors; } @Override diff --git a/data-model/src/main/java/io/micronaut/data/model/Page.java b/data-model/src/main/java/io/micronaut/data/model/Page.java index 107d92429a0..ac49597b029 100644 --- a/data-model/src/main/java/io/micronaut/data/model/Page.java +++ b/data-model/src/main/java/io/micronaut/data/model/Page.java @@ -20,6 +20,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import io.micronaut.context.annotation.DefaultImplementation; +import io.micronaut.core.annotation.Internal; import io.micronaut.core.annotation.NonNull; import io.micronaut.core.annotation.Nullable; import io.micronaut.core.annotation.ReflectiveAccess; @@ -29,7 +30,6 @@ import java.util.Collections; import java.util.List; -import java.util.Optional; import java.util.function.Function; import java.util.stream.Collectors; @@ -80,21 +80,6 @@ default int getTotalPages() { return size == 0 ? 1 : (int) Math.ceil((double) getTotalSize() / (double) size); } - /** - * Get cursor at the given position. - * Cursors are only available if {@code getPageable().getMode()} is one of - * {@link Pageable.Mode#CURSOR_NEXT} or {@link Pageable.Mode#CURSOR_PREVIOUS}. - * In that case there is a cursor for each of the data entities in the same order. - * To start pagination after or before a cursor create a pageable from it using the - * same sorting as before. - * - * @param i The index of cursor to retrieve. - * @return The cursor at the provided index. - */ - default Optional getCursor(int i) { - return Optional.empty(); - } - @Override default boolean hasNext() { return hasTotalSize() @@ -135,6 +120,7 @@ default boolean hasNext() { /** * Creates a page from the given content, pageable, cursors and totalSize. + * This method is for JSON deserialization. Please use {@link CursoredPage#of} instead. * * @param content The content * @param pageable The pageable @@ -144,6 +130,7 @@ default boolean hasNext() { * @return The slice */ @JsonCreator + @Internal @ReflectiveAccess static @NonNull Page ofCursors( @JsonProperty("content") @NonNull List content, diff --git a/data-model/src/main/java/io/micronaut/data/model/Pageable.java b/data-model/src/main/java/io/micronaut/data/model/Pageable.java index 74dc83010fb..70ea4c294c6 100644 --- a/data-model/src/main/java/io/micronaut/data/model/Pageable.java +++ b/data-model/src/main/java/io/micronaut/data/model/Pageable.java @@ -314,7 +314,7 @@ default Pageable withoutTotal() { * @param sort The sorting * @return The pageable */ - static @NonNull Pageable afterCursor(@NonNull Cursor cursor, int page, int size, @Nullable Sort sort) { + static @NonNull CursoredPageable afterCursor(@NonNull Cursor cursor, int page, int size, @Nullable Sort sort) { if (sort == null) { sort = UNSORTED; } @@ -331,7 +331,7 @@ default Pageable withoutTotal() { * @param sort The sorting * @return The pageable */ - static @NonNull Pageable beforeCursor(@NonNull Cursor cursor, int page, int size, @Nullable Sort sort) { + static @NonNull CursoredPageable beforeCursor(@NonNull Cursor cursor, int page, int size, @Nullable Sort sort) { if (sort == null) { sort = UNSORTED; } diff --git a/data-model/src/main/java/io/micronaut/data/model/runtime/PagedQuery.java b/data-model/src/main/java/io/micronaut/data/model/runtime/PagedQuery.java index 341beabe7a4..4674f7360cd 100644 --- a/data-model/src/main/java/io/micronaut/data/model/runtime/PagedQuery.java +++ b/data-model/src/main/java/io/micronaut/data/model/runtime/PagedQuery.java @@ -53,4 +53,5 @@ public interface PagedQuery extends Named, AnnotationMetadataProvider { default Map getQueryHints() { return Collections.emptyMap(); } + } diff --git a/data-processor/src/main/java/io/micronaut/data/processor/visitors/RepositoryTypeElementVisitor.java b/data-processor/src/main/java/io/micronaut/data/processor/visitors/RepositoryTypeElementVisitor.java index 8e6ff37198b..9d302802ec7 100644 --- a/data-processor/src/main/java/io/micronaut/data/processor/visitors/RepositoryTypeElementVisitor.java +++ b/data-processor/src/main/java/io/micronaut/data/processor/visitors/RepositoryTypeElementVisitor.java @@ -38,6 +38,7 @@ import io.micronaut.data.annotation.sql.Procedure; import io.micronaut.data.intercept.annotation.DataMethod; import io.micronaut.data.intercept.annotation.DataMethodQueryParameter; +import io.micronaut.data.model.CursoredPage; import io.micronaut.data.model.DataType; import io.micronaut.data.model.JsonDataType; import io.micronaut.data.model.Page; @@ -121,6 +122,7 @@ public class RepositoryTypeElementVisitor implements TypeElementVisitor count = cb.createQuery(); // count.select(cb.count(query.getRoots().iterator().next())); // CommonAbstractCriteria countQueryCriteria = defineQuery(matchContext, matchContext.getRootEntity(), cb); diff --git a/data-processor/src/test/groovy/io/micronaut/data/processor/visitors/PageSpec.groovy b/data-processor/src/test/groovy/io/micronaut/data/processor/visitors/PageSpec.groovy index 173532f8913..2384b976c3c 100644 --- a/data-processor/src/test/groovy/io/micronaut/data/processor/visitors/PageSpec.groovy +++ b/data-processor/src/test/groovy/io/micronaut/data/processor/visitors/PageSpec.groovy @@ -19,8 +19,10 @@ import io.micronaut.annotation.processing.TypeElementVisitorProcessor import io.micronaut.annotation.processing.test.JavaParser import io.micronaut.data.annotation.Join import io.micronaut.data.annotation.Query +import io.micronaut.data.intercept.FindCursoredPageInterceptor import io.micronaut.data.intercept.FindPageInterceptor import io.micronaut.data.intercept.annotation.DataMethod +import io.micronaut.data.model.CursoredPageable import io.micronaut.data.model.Pageable import io.micronaut.data.model.PersistentEntity import io.micronaut.data.model.entities.Person @@ -266,6 +268,42 @@ interface MyInterface extends GenericRepository { e.message.contains('Query returns a Page and does not specify a \'countQuery\' member') } + void "test cursored page method match"() { + given: + BeanDefinition beanDefinition = buildRepository('test.MyInterface' , """ + +import io.micronaut.context.annotation.Executable; +import io.micronaut.data.model.entities.Person; + +@Repository +@Executable +interface MyInterface extends GenericRepository { + + CursoredPage list(Pageable pageable); + + CursoredPage findByName(String title, Pageable pageable); + +} +""") + + def alias = new JpaQueryBuilder().getAliasName(PersistentEntity.of(Person)) + + when: "the list method is retrieved" + def listMethod = beanDefinition.getRequiredMethod("list", Pageable) + def listAnn = listMethod.synthesize(DataMethod) + + def findMethod = beanDefinition.getRequiredMethod("findByName", String, Pageable) + def findAnn = findMethod.synthesize(DataMethod) + + + then:"it is configured correctly" + listAnn.interceptor() == FindCursoredPageInterceptor + findAnn.interceptor() == FindCursoredPageInterceptor + findMethod.hasAnnotation(Query.class) + findMethod.getValue(Query.class, "value", String).get() == "SELECT $alias FROM io.micronaut.data.model.entities.Person AS $alias WHERE (${alias}.name = :p1)" + findMethod.getValue(Query.class, "countQuery", String).get() == "SELECT COUNT($alias) FROM io.micronaut.data.model.entities.Person AS $alias WHERE (${alias}.name = :p1)" + } + @Override protected JavaParser newJavaParser() { return new JavaParser() { diff --git a/data-runtime/src/main/java/io/micronaut/data/runtime/intercept/DefaultFindCursoredPageInterceptor.java b/data-runtime/src/main/java/io/micronaut/data/runtime/intercept/DefaultFindCursoredPageInterceptor.java new file mode 100644 index 00000000000..37ef89a3570 --- /dev/null +++ b/data-runtime/src/main/java/io/micronaut/data/runtime/intercept/DefaultFindCursoredPageInterceptor.java @@ -0,0 +1,57 @@ +/* + * Copyright 2017-2020 original authors + * + * 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 + * + * https://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 io.micronaut.data.runtime.intercept; + +import io.micronaut.aop.MethodInvocationContext; +import io.micronaut.core.annotation.NonNull; +import io.micronaut.data.intercept.FindCursoredPageInterceptor; +import io.micronaut.data.model.CursoredPageable; +import io.micronaut.data.model.Pageable; +import io.micronaut.data.model.Pageable.Mode; +import io.micronaut.data.operations.RepositoryOperations; + +/** + * Default implementation of {@link FindCursoredPageInterceptor}. + * + * @param The declaring type + * @param The paged type. + * @author Andriy Dmytruk + * @since 4.8.0 + */ +public class DefaultFindCursoredPageInterceptor extends DefaultFindPageInterceptor implements FindCursoredPageInterceptor { + + /** + * Default constructor. + * + * @param datastore The operations + */ + protected DefaultFindCursoredPageInterceptor(@NonNull RepositoryOperations datastore) { + super(datastore); + } + + @Override + protected Pageable getPageable(MethodInvocationContext context) { + Pageable pageable = super.getPageable(context); + if (pageable.getMode() == Mode.OFFSET) { + if (pageable.getNumber() == 0) { + pageable = CursoredPageable.from(pageable.getSize(), pageable.getSort()); + } else { + throw new IllegalArgumentException("Pageable with offset mode provided, but method must return a cursored page"); + } + } + return pageable; + } +} diff --git a/data-runtime/src/main/java/io/micronaut/data/runtime/intercept/DefaultFindPageInterceptor.java b/data-runtime/src/main/java/io/micronaut/data/runtime/intercept/DefaultFindPageInterceptor.java index 97898ea02a8..40268322a51 100644 --- a/data-runtime/src/main/java/io/micronaut/data/runtime/intercept/DefaultFindPageInterceptor.java +++ b/data-runtime/src/main/java/io/micronaut/data/runtime/intercept/DefaultFindPageInterceptor.java @@ -21,6 +21,7 @@ import io.micronaut.data.annotation.Query; import io.micronaut.data.intercept.FindPageInterceptor; import io.micronaut.data.intercept.RepositoryMethodKey; +import io.micronaut.data.model.CursoredPage; import io.micronaut.data.model.Page; import io.micronaut.data.model.Pageable; import io.micronaut.data.model.Pageable.Cursor; @@ -70,9 +71,9 @@ public R intercept(RepositoryMethodKey methodKey, MethodInvocationContext page = Page.of(results, pageable, totalCount); } else if (preparedQuery instanceof DefaultSqlPreparedQuery sqlPreparedQuery) { List cursors = sqlPreparedQuery.createCursors((List) results, pageable); - page = Page.ofCursors(results, pageable, cursors, totalCount); + page = CursoredPage.of(results, pageable, cursors, totalCount); } else { - throw new UnsupportedOperationException("Only offest pageable mode is supported by this query implementation"); + throw new UnsupportedOperationException("Only offset pageable mode is supported by this query implementation"); } if (returnType.isInstance(page)) { return (R) page; diff --git a/data-tck/src/main/groovy/io/micronaut/data/tck/tests/AbstractCursoredPageSpec.groovy b/data-tck/src/main/groovy/io/micronaut/data/tck/tests/AbstractCursoredPageSpec.groovy index a08ffb7fc76..a8a967793f3 100644 --- a/data-tck/src/main/groovy/io/micronaut/data/tck/tests/AbstractCursoredPageSpec.groovy +++ b/data-tck/src/main/groovy/io/micronaut/data/tck/tests/AbstractCursoredPageSpec.groovy @@ -17,6 +17,7 @@ package io.micronaut.data.tck.tests import io.micronaut.data.model.CursoredPageable import io.micronaut.data.model.Page +import io.micronaut.data.model.Pageable import io.micronaut.data.model.Sort import io.micronaut.data.tck.entities.Book import io.micronaut.data.tck.entities.Person @@ -115,8 +116,8 @@ abstract class AbstractCursoredPageSpec extends Specification { void "test pageable list with row removal"() { when: "10 people are paged" - def pageable = CursoredPageable.from(10, sorting) - Page page = personRepository.findAll(pageable) + def pageable = Pageable.from(0, 10, sorting) // The first pageable can be non-cursored + Page page = personRepository.retrieve(pageable) // The retrieve method explicitly returns CursoredPage then: "The data is correct" page.content.size() == 10 @@ -127,7 +128,7 @@ abstract class AbstractCursoredPageSpec extends Specification { when: "The next page is selected after deletion" personRepository.delete(page.content[1]) personRepository.delete(page.content[9]) - page = personRepository.findAll(page.nextPageable()) + page = personRepository.retrieve(page.nextPageable()) then: "it is correct" page.offset == 10 @@ -140,7 +141,7 @@ abstract class AbstractCursoredPageSpec extends Specification { when: "The previous page is selected" pageable = page.previousPageable() - page = personRepository.findAll(pageable) + page = personRepository.retrieve(pageable) then: "it is correct" page.offset == 0 @@ -163,7 +164,7 @@ abstract class AbstractCursoredPageSpec extends Specification { void "test pageable list with row addition"() { when: "10 people are paged" def pageable = CursoredPageable.from(10, sorting) - Page page = personRepository.findAll(pageable) + Page page = personRepository.retrieve(pageable) then: "The data is correct" page.content.size() == 10 @@ -176,7 +177,7 @@ abstract class AbstractCursoredPageSpec extends Specification { new Person(name: "AAAAA00"), new Person(name: "AAAAA01"), new Person(name: "ZZZZZ08"), new Person(name: "ZZZZZ07") ]) - page = personRepository.findAll(page.nextPageable()) + page = personRepository.retrieve(page.nextPageable()) then: "it is correct" page.offset == 10 @@ -189,7 +190,7 @@ abstract class AbstractCursoredPageSpec extends Specification { when: "The previous page is selected" pageable = page.previousPageable() - page = personRepository.findAll(pageable) + page = personRepository.retrieve(pageable) then: "it is correct" page.offset == 0 @@ -199,7 +200,7 @@ abstract class AbstractCursoredPageSpec extends Specification { page.hasPrevious() when: "The second previous page is selected" - page = personRepository.findAll(page.previousPageable()) + page = personRepository.retrieve(page.previousPageable()) then: page.offset == 0 diff --git a/data-tck/src/main/java/io/micronaut/data/tck/repositories/PersonRepository.java b/data-tck/src/main/java/io/micronaut/data/tck/repositories/PersonRepository.java index f875d37e573..617f2bc48da 100644 --- a/data-tck/src/main/java/io/micronaut/data/tck/repositories/PersonRepository.java +++ b/data-tck/src/main/java/io/micronaut/data/tck/repositories/PersonRepository.java @@ -16,10 +16,12 @@ package io.micronaut.data.tck.repositories; import io.micronaut.context.annotation.Parameter; +import io.micronaut.core.annotation.NonNull; import io.micronaut.core.annotation.Nullable; import io.micronaut.data.annotation.Id; import io.micronaut.data.annotation.ParameterExpression; import io.micronaut.data.annotation.Query; +import io.micronaut.data.model.CursoredPage; import io.micronaut.data.model.Page; import io.micronaut.data.model.Pageable; import io.micronaut.data.model.Slice; @@ -173,6 +175,8 @@ public interface PersonRepository extends CrudRepository, Pageable List findDistinctName(); + CursoredPage retrieve(@NonNull Pageable pageable); + class Specifications { public static PredicateSpecification nameEquals(String name) {