diff --git a/README.md b/README.md index e6ec23b..0f9eea2 100644 --- a/README.md +++ b/README.md @@ -434,6 +434,25 @@ Such a reversed cursor is, changing the direction of the query, but not the sort There is only value in this feature, if the client is not able to cache the previous requested pages by himself, i.e. forgets them while moving forward. This might be the case in a very memory limited client scenario which is usually _not_ a web application/web browser. +## API for reverting page-requests (=cursor positions) +There is an API (`PageReqiest::toReversed`) for getting a page-request from an existing page-request, which will traverse the results in the opposite direction - while maintaining the sort order. +Example: +```java + // Extracted from test-case + final PageRequest request = PageRequest.create( b -> b.pageSize( 5 ).asc( DataRecord_.name ) + .asc( DataRecord_.id ) ); + + final var firstPage = dataRecordRepository.loadPage( request ); + final var secondPage = dataRecordRepository.loadPage( firstPage.next().orElseThrow() ); + final var reversedFirstPage = dataRecordRepository.loadPage( secondPage.self().toReversed() ); + + // everything before the second page (self-pointer) is the first page: + assertThat( reversedFirstPage ).containsExactlyElementsOf( firstPage ); + + // no next before the "first page": + assertThat( reversedFirstPage.next() ).isNotPresent(); +``` + ## Limitations - Such a cursor implementation is not transaction-safe. Which is good enough for most UIs, and it is not so important, to miss a record or have a duplicate one in two-page requests. This is i.e. the case when the PK is not an ascending numerical ID but maybe a UUID, so that it is possible that an inserted record appears before the page which a client is going to request. In case you need transaction-safe cursor queries, this is most likely a server-side use case, and you can use DB-cursors. diff --git a/cursorpaging-jpa-api/src/main/java/io/vigier/cursorpaging/jpa/serializer/FromDtoMapper.java b/cursorpaging-jpa-api/src/main/java/io/vigier/cursorpaging/jpa/serializer/FromDtoMapper.java index b1f9ff4..0040cc6 100644 --- a/cursorpaging-jpa-api/src/main/java/io/vigier/cursorpaging/jpa/serializer/FromDtoMapper.java +++ b/cursorpaging-jpa-api/src/main/java/io/vigier/cursorpaging/jpa/serializer/FromDtoMapper.java @@ -69,7 +69,7 @@ private Position positionOf( final Cursor.Position position ) { case ASC -> Order.ASC; case DESC -> Order.DESC; case UNRECOGNIZED -> throw new IllegalArgumentException( "Unrecognized order" ); - } ) ); + } ).reversed( position.getReversed() ) ); } private Attribute attributeOf( final Cursor.Attribute attribute ) { diff --git a/cursorpaging-jpa-api/src/main/java/io/vigier/cursorpaging/jpa/serializer/ToDtoMapper.java b/cursorpaging-jpa-api/src/main/java/io/vigier/cursorpaging/jpa/serializer/ToDtoMapper.java index 869b5a2..bc1d543 100644 --- a/cursorpaging-jpa-api/src/main/java/io/vigier/cursorpaging/jpa/serializer/ToDtoMapper.java +++ b/cursorpaging-jpa-api/src/main/java/io/vigier/cursorpaging/jpa/serializer/ToDtoMapper.java @@ -31,7 +31,7 @@ private Iterable positions() { .setValue( valueOf( p.value() ) ).setOrder( switch ( p.order() ) { case ASC -> Cursor.Order.ASC; case DESC -> Cursor.Order.DESC; - } ) + } ).setReversed( p.reversed() ) .build() ).toList(); } diff --git a/cursorpaging-jpa-api/src/main/proto/pagerequest.proto b/cursorpaging-jpa-api/src/main/proto/pagerequest.proto index fb74f5f..eb1b4a0 100644 --- a/cursorpaging-jpa-api/src/main/proto/pagerequest.proto +++ b/cursorpaging-jpa-api/src/main/proto/pagerequest.proto @@ -19,6 +19,7 @@ message Position { Attribute attribute = 1; Value value = 2; Order order = 3; + optional bool reversed = 4; } message Filter { diff --git a/cursorpaging-jpa-api/src/test/java/io/vigier/cursorpaging/jpa/serializer/SerializerTest.java b/cursorpaging-jpa-api/src/test/java/io/vigier/cursorpaging/jpa/serializer/SerializerTest.java index db9f968..c233b60 100644 --- a/cursorpaging-jpa-api/src/test/java/io/vigier/cursorpaging/jpa/serializer/SerializerTest.java +++ b/cursorpaging-jpa-api/src/test/java/io/vigier/cursorpaging/jpa/serializer/SerializerTest.java @@ -1,9 +1,9 @@ package io.vigier.cursorpaging.jpa.serializer; -import static org.assertj.core.api.Assertions.assertThat; - import io.vigier.cursorpaging.jpa.Attribute; +import io.vigier.cursorpaging.jpa.Order; import io.vigier.cursorpaging.jpa.PageRequest; +import io.vigier.cursorpaging.jpa.Position; import io.vigier.cursorpaging.jpa.SingleAttribute; import jakarta.persistence.metamodel.SingularAttribute; import java.time.Instant; @@ -12,6 +12,8 @@ import org.junit.jupiter.api.Test; import org.mockito.Mockito; +import static org.assertj.core.api.Assertions.assertThat; + public class SerializerTest { @Data @@ -58,6 +60,18 @@ void shouldSerializePageRequestsWithMultipleAttributes() { assertThat( deserializeRequest ).isEqualTo( pageRequest ); } + @Test + void shouldSerializeReversedPageRequests() { + final var request = PageRequest.create( r -> r.position( Position.create( + p -> p.reversed( true ).order( Order.ASC ).attribute( Attribute.of( "some_name", String.class ) ) ) ) ); + final EntitySerializer serializer = EntitySerializer.create(); + final var serializedRequest = serializer.toBase64( request ); + final var deserializedRequest = serializer.toPageRequest( serializedRequest ); + assertThat( deserializedRequest.isReversed() ).isTrue(); + assertThat( deserializedRequest ).isEqualTo( request ); + + } + @Test void shouldLearnAttributesBySerializing() { final var request = createPageRequest(); diff --git a/cursorpaging-jpa/src/main/java/io/vigier/cursorpaging/jpa/PageRequest.java b/cursorpaging-jpa/src/main/java/io/vigier/cursorpaging/jpa/PageRequest.java index 643b970..dadf039 100644 --- a/cursorpaging-jpa/src/main/java/io/vigier/cursorpaging/jpa/PageRequest.java +++ b/cursorpaging-jpa/src/main/java/io/vigier/cursorpaging/jpa/PageRequest.java @@ -165,4 +165,8 @@ public PageRequest toReversed() { public boolean isFirstPage() { return positions.get( 0 ).isFirst(); } + + public boolean isReversed() { + return positions.get( 0 ).reversed(); + } }