From 2ff709e799e2344fefd6d2e502388d3f204bcb6c Mon Sep 17 00:00:00 2001 From: Evgeniy Cheban Date: Mon, 6 Jan 2025 03:10:45 +0200 Subject: [PATCH] Cover limit, findFirst, findTop query method keywords with tests Closes gh-32 --- pom.xml | 7 + .../query/ReindexerQueryCreator.java | 30 +++- .../query/ReindexerRepositoryQuery.java | 15 +- .../repository/ReindexerRepositoryTests.java | 131 +++++++++++++++++- 4 files changed, 166 insertions(+), 17 deletions(-) diff --git a/pom.xml b/pom.xml index abe90a1..a007cad 100644 --- a/pom.xml +++ b/pom.xml @@ -57,6 +57,7 @@ 1.5.12 1.20.4 4.5.14 + 3.27.2 @@ -236,6 +237,12 @@ ${httpclient.version} test + + org.assertj + assertj-core + ${assertj.version} + test + diff --git a/src/main/java/org/springframework/data/reindexer/repository/query/ReindexerQueryCreator.java b/src/main/java/org/springframework/data/reindexer/repository/query/ReindexerQueryCreator.java index 853f123..bcd2b17 100644 --- a/src/main/java/org/springframework/data/reindexer/repository/query/ReindexerQueryCreator.java +++ b/src/main/java/org/springframework/data/reindexer/repository/query/ReindexerQueryCreator.java @@ -28,6 +28,7 @@ import ru.rt.restream.reindexer.Query.Condition; import ru.rt.restream.reindexer.ReindexerIndex; +import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Order; @@ -156,29 +157,48 @@ protected Query complete(Query criteria, Sort sort) { criteria = this.namespace.query(); } if (this.returnedType.needsCustomConstruction()) { - criteria = criteria.select(this.returnedType.getInputProperties().toArray(String[]::new)); + criteria.select(this.returnedType.getInputProperties().toArray(String[]::new)); } else if (this.tree.isExistsProjection()) { - criteria = criteria.select(this.entityInformation.getIdFieldName()); + criteria.select(this.entityInformation.getIdFieldName()); } Pageable pageable = this.parameters.getPageable(); if (pageable.isPaged()) { - criteria.limit(pageable.getPageSize()).offset((int) pageable.getOffset()); + criteria.limit(pageable.getPageSize()).offset(getOffsetAsInteger(pageable)); if (this.queryMethod.isPageQuery()) { criteria = criteria.reqTotal(); } } if (sort.isSorted()) { for (Order order : sort) { - criteria = criteria.sort(order.getProperty(), order.isDescending()); + criteria.sort(order.getProperty(), order.isDescending()); } } if (this.tree.getMaxResults() != null) { - criteria = criteria.limit(this.tree.getMaxResults()); + if (pageable.isPaged()) { + /* + * In order to return the correct results, we have to adjust the first result offset to be returned if: + * - a Pageable parameter is present + * - AND the requested page number > 0 + * - AND the requested page size was bigger than the derived result limitation via the First/Top keyword. + */ + int firstResult = getOffsetAsInteger(pageable); + if (pageable.getPageSize() > this.tree.getMaxResults() && firstResult > 0) { + criteria.offset(firstResult - (pageable.getPageSize() - this.tree.getMaxResults())); + } + } + criteria.limit(this.tree.getMaxResults()); } if (this.tree.isExistsProjection()) { criteria.limit(1); } return criteria; } + + private int getOffsetAsInteger(Pageable pageable) { + if (pageable.getOffset() > Integer.MAX_VALUE) { + throw new InvalidDataAccessApiUsageException("Page offset exceeds Integer.MAX_VALUE (" + Integer.MAX_VALUE + ")"); + } + return Math.toIntExact(pageable.getOffset()); + } } diff --git a/src/main/java/org/springframework/data/reindexer/repository/query/ReindexerRepositoryQuery.java b/src/main/java/org/springframework/data/reindexer/repository/query/ReindexerRepositoryQuery.java index e7a45c1..9932054 100644 --- a/src/main/java/org/springframework/data/reindexer/repository/query/ReindexerRepositoryQuery.java +++ b/src/main/java/org/springframework/data/reindexer/repository/query/ReindexerRepositoryQuery.java @@ -86,7 +86,7 @@ public ReindexerRepositoryQuery(ReindexerQueryMethod queryMethod, ReindexerEntit this.namespace = new TransactionalNamespace<>(namespace); this.tree = new PartTree(queryMethod.getName(), entityInformation.getJavaType()); this.queryExecution = Lazy.of(() -> { - if (queryMethod.isCollectionQuery() && !queryMethod.getParameters().hasPageableParameter()) { + if (queryMethod.isCollectionQuery()) { return QueryMethodExecution.COLLECTION; } if (queryMethod.isStreamQuery()) { @@ -95,7 +95,7 @@ public ReindexerRepositoryQuery(ReindexerQueryMethod queryMethod, ReindexerEntit if (queryMethod.isIteratorQuery()) { return QueryMethodExecution.ITERATOR; } - if (queryMethod.getParameters().hasPageableParameter()) { + if (queryMethod.isPageQuery()) { return QueryMethodExecution.PAGEABLE; } if (tree.isCountProjection()) { @@ -173,15 +173,8 @@ public Object execute(ReindexerQueryCreator queryCreator) { while (iterator.hasNext()) { content.add(iterator.next()); } - if (queryCreator.getQueryMethod().isPageQuery()) { - Pageable pageable = queryCreator.getParameters().getPageable(); - return pageable.isPaged() ? PageableExecutionUtils.getPage(content, pageable, iterator::getTotalCount) - : new PageImpl<>(content); - } - if (queryCreator.getQueryMethod().isListQuery()) { - return content; - } - throw new IllegalStateException("Unsupported return type for Pageable query " + queryCreator.getQueryMethod().getReturnType()); + Pageable pageable = queryCreator.getParameters().getPageable(); + return PageableExecutionUtils.getPage(content, pageable, iterator::getTotalCount); } } }, diff --git a/src/test/java/org/springframework/data/reindexer/repository/ReindexerRepositoryTests.java b/src/test/java/org/springframework/data/reindexer/repository/ReindexerRepositoryTests.java index fccadf7..66b92d0 100644 --- a/src/test/java/org/springframework/data/reindexer/repository/ReindexerRepositoryTests.java +++ b/src/test/java/org/springframework/data/reindexer/repository/ReindexerRepositoryTests.java @@ -59,8 +59,10 @@ import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.data.annotation.PersistenceCreator; +import org.springframework.data.domain.Limit; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Direction; @@ -76,7 +78,13 @@ import org.springframework.transaction.annotation.EnableTransactionManagement; import org.springframework.transaction.annotation.Transactional; -import static org.junit.jupiter.api.Assertions.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * Tests for {@link ReindexerRepository}. @@ -895,6 +903,28 @@ public void findPageByIdIn() { assertEquals(0, expectedItems.size()); } + @Test + public void findFirst2By() { + TestItem item1 = this.repository.save(new TestItem(1L, "TestName1", "TestValue1")); + TestItem item2 = this.repository.save(new TestItem(2L, "TestName2", "TestValue2")); + TestItem item3 = this.repository.save(new TestItem(3L, "TestName3", "TestValue3")); + Page firstPage = this.repository.findFirst2By(PageRequest.of(0, 3, Direction.ASC, "id")); + assertThat(firstPage.getContent()).contains(item1, item2); + Page secondPage = this.repository.findFirst2By(PageRequest.of(1, 3, Direction.ASC, "id")); + assertThat(secondPage).contains(item3); + } + + @Test + public void findFirst3By() { + TestItem item1 = this.repository.save(new TestItem(1L, "TestName1", "TestValue1")); + TestItem item2 = this.repository.save(new TestItem(2L, "TestName2", "TestValue2")); + TestItem item3 = this.repository.save(new TestItem(3L, "TestName3", "TestValue3")); + Page firstPage = this.repository.findFirst3By(PageRequest.of(0, 2, Direction.ASC, "id")); + assertThat(firstPage.getContent()).contains(item1, item2); + Page secondPage = this.repository.findFirst3By(PageRequest.of(1, 2, Direction.ASC, "id")); + assertThat(secondPage).contains(item3); + } + @Test public void findAllPageable() { Set expectedItems = new HashSet<>(); @@ -1005,6 +1035,87 @@ public void saveAndDelete() { assertFalse(this.repository.existsById(testItem.getId())); } + @Test + public void findAllByLimit() { + Set expectedItems = new HashSet<>(); + for (long i = 0; i < 100; i++) { + expectedItems.add(this.repository.save(new TestItem(i, "TestName" + i, "TestValue" + i))); + } + List foundItems = this.repository.findAllBy(Limit.of(10)); + for (TestItem item : foundItems) { + assertTrue(expectedItems.remove(item)); + } + assertEquals(90, expectedItems.size()); + } + + @Test + public void findFirstByOrderByIdAsc() { + List expectedItems = new ArrayList<>(); + for (long i = 0; i < 100; i++) { + expectedItems.add(this.repository.save(new TestItem(i, "TestName" + i, "TestValue" + i))); + } + TestItem foundItem = this.repository.findFirstByOrderByIdAsc().orElse(null); + assertNotNull(foundItem); + assertEquals(expectedItems.get(0), foundItem); + } + + @Test + public void findFirstByOrderByIdDesc() { + List expectedItems = new ArrayList<>(); + for (long i = 0; i < 100; i++) { + expectedItems.add(this.repository.save(new TestItem(i, "TestName" + i, "TestValue" + i))); + } + TestItem foundItem = this.repository.findFirstByOrderByIdDesc().orElse(null); + assertNotNull(foundItem); + assertEquals(expectedItems.get(expectedItems.size() - 1), foundItem); + } + + @Test + public void findTopByOrderByIdAsc() { + List expectedItems = new ArrayList<>(); + for (long i = 0; i < 100; i++) { + expectedItems.add(this.repository.save(new TestItem(i, "TestName" + i, "TestValue" + i))); + } + TestItem foundItem = this.repository.findTopByOrderByIdAsc().orElse(null); + assertEquals(expectedItems.get(0), foundItem); + } + + @Test + public void findTopByOrderByIdDesc() { + List expectedItems = new ArrayList<>(); + for (long i = 0; i < 100; i++) { + expectedItems.add(this.repository.save(new TestItem(i, "TestName" + i, "TestValue" + i))); + } + TestItem foundItem = this.repository.findTopByOrderByIdDesc().orElse(null); + assertEquals(expectedItems.get(expectedItems.size() - 1), foundItem); + } + + @Test + public void findTop10ByOrderByIdAsc() { + Set expectedItems = new HashSet<>(); + for (long i = 0; i < 100; i++) { + expectedItems.add(this.repository.save(new TestItem(i, "TestName" + i, "TestValue" + i))); + } + List foundItems = this.repository.findTop10ByOrderByIdAsc(); + for (TestItem item : foundItems) { + assertTrue(expectedItems.remove(item)); + } + assertEquals(90, expectedItems.size()); + } + + @Test + public void findTop10ByOrderByIdDesc() { + Set expectedItems = new HashSet<>(); + for (long i = 0; i < 100; i++) { + expectedItems.add(this.repository.save(new TestItem(i, "TestName" + i, "TestValue" + i))); + } + List foundItems = this.repository.findTop10ByOrderByIdDesc(); + for (TestItem item : foundItems) { + assertTrue(expectedItems.remove(item)); + } + assertEquals(90, expectedItems.size()); + } + @Configuration @EnableReindexerRepositories(basePackageClasses = TestItemReindexerRepository.class, considerNestedRepositories = true) @EnableTransactionManagement @@ -1155,6 +1266,10 @@ Optional findOneSqlByNameAndValueManyParams(String name1, String name2 Page findPageByIdIn(List ids, Pageable pageable); + Page findFirst2By(Pageable pageable); + + Page findFirst3By(Pageable pageable); + List findItemProjectionByIdIn(List ids); List findItemDtoByIdIn(List ids); @@ -1166,6 +1281,20 @@ Optional findOneSqlByNameAndValueManyParams(String name1, String name2 List findItemPreferredConstructorRecordByIdIn(List ids); List findByIdIn(List ids, Class type); + + List findAllBy(Limit limit); + + Optional findFirstByOrderByIdAsc(); + + Optional findFirstByOrderByIdDesc(); + + Optional findTopByOrderByIdAsc(); + + Optional findTopByOrderByIdDesc(); + + List findTop10ByOrderByIdAsc(); + + List findTop10ByOrderByIdDesc(); } @Namespace(name = NAMESPACE_NAME)