Skip to content

Commit d5ad532

Browse files
author
Artemiy Degtyarev
committed
add: test with two keys
Signed-off-by: Artemiy Degtyarev <[email protected]>
1 parent bf33816 commit d5ad532

File tree

3 files changed

+119
-16
lines changed

3 files changed

+119
-16
lines changed

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/PartTreeJdbcQuery.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -282,12 +282,12 @@ static class ScrollQueryExecution<T> implements JdbcQueryExecution<Window<T>> {
282282

283283
if (position instanceof KeysetScrollPosition) {
284284
Map<String, Object> keys = ((KeysetScrollPosition) position).getKeys();
285+
List<String> orders = new ArrayList<>(keys.keySet());
285286

286-
if (keys.isEmpty()) {
287-
List<String> orders = sort.get().map(Sort.Order::getProperty).toList();
287+
if (orders.isEmpty())
288+
orders = sort.get().map(Sort.Order::getProperty).toList();
288289

289-
keys = extractKeys(resultList, orders);
290-
}
290+
keys = extractKeys(resultList, orders);
291291

292292
Map<String, Object> finalKeys = keys;
293293
positionFunction = (ignoredI) -> ScrollPosition.of(finalKeys, ((KeysetScrollPosition) position).getDirection()) ;
@@ -299,6 +299,9 @@ static class ScrollQueryExecution<T> implements JdbcQueryExecution<Window<T>> {
299299
}
300300

301301
private Map<String, Object> extractKeys(List<T> resultList, List<String> orders) {
302+
if (resultList.isEmpty())
303+
return Map.of();
304+
302305
T last = resultList.get(resultList.size() - 1);
303306

304307
Field[] fields = last.getClass().getDeclaredFields();

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/StatementFactory.java

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import java.util.*;
1919
import java.util.function.Predicate;
20+
import java.util.stream.Collectors;
2021

2122
import org.jspecify.annotations.Nullable;
2223

@@ -205,7 +206,7 @@ public String build(MapSqlParameterSource parameterSource) {
205206

206207
SelectBuilder.SelectLimitOffset limitOffsetBuilder = createSelectClause(entity, table);
207208
SelectBuilder.SelectWhere whereBuilder = applyLimitAndOffset(limitOffsetBuilder);
208-
criteria = applyScrollCriteria(criteria, scrollPosition);
209+
criteria = applyScrollCriteria(criteria, scrollPosition, sort);
209210

210211
SelectBuilder.SelectOrdered selectOrderBuilder = applyCriteria(criteria, entity, table, parameterSource,
211212
whereBuilder);
@@ -221,7 +222,7 @@ public String build(MapSqlParameterSource parameterSource) {
221222
return SqlRenderer.create(renderContextFactory.createRenderContext()).render(select);
222223
}
223224

224-
@Nullable Criteria applyScrollCriteria(@Nullable Criteria criteria, @Nullable ScrollPosition position) {
225+
@Nullable Criteria applyScrollCriteria(@Nullable Criteria criteria, @Nullable ScrollPosition position, Sort sort) {
225226
if (!(position instanceof KeysetScrollPosition) || position.isInitial() || ((KeysetScrollPosition) position).getKeys().isEmpty())
226227
return criteria;
227228

@@ -234,11 +235,32 @@ public String build(MapSqlParameterSource parameterSource) {
234235
Criteria result = null;
235236

236237
for (int i = 0; i < keys.size(); i++) {
237-
Criteria orCriteria = Criteria.where(columns.get(i)).greaterThan(values.get(i));
238+
Map<String, Sort.Direction> orders =
239+
sort.get().collect(Collectors.toMap(Sort.Order::getProperty, Sort.Order::getDirection));
240+
241+
Criteria.CriteriaStep orCriteriaStep = Criteria.where(columns.get(i));
242+
Criteria orCriteria;
243+
244+
Sort.Direction defaultDirection = orders.values().stream().findFirst().orElse(Sort.DEFAULT_DIRECTION);
245+
Sort.Direction curDirection = orders.get(columns.get(i));
246+
247+
if (curDirection == null)
248+
curDirection = defaultDirection;
249+
250+
if (((KeysetScrollPosition) position).scrollsForward()) {
251+
if (curDirection.isAscending())
252+
orCriteria = orCriteriaStep.greaterThan(values.get(i));
253+
else
254+
orCriteria = orCriteriaStep.lessThan(values.get(i));
255+
} else {
256+
if (curDirection.isAscending())
257+
orCriteria = orCriteriaStep.lessThan(values.get(i));
258+
else
259+
orCriteria = orCriteriaStep.greaterThan(values.get(i));
260+
}
238261

239-
for (int j = 0; j < i; j++) {
262+
for (int j = 0; j < i; j++)
240263
orCriteria = Criteria.where(columns.get(j)).is(values.get(j)).and(orCriteria);
241-
}
242264

243265
result = (result == null) ? orCriteria : result.or(orCriteria);
244266
}

spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java

Lines changed: 85 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,7 @@
2727
import java.time.OffsetDateTime;
2828
import java.time.ZoneOffset;
2929
import java.time.temporal.ChronoUnit;
30-
import java.util.ArrayList;
31-
import java.util.Arrays;
32-
import java.util.Collections;
33-
import java.util.List;
34-
import java.util.Objects;
35-
import java.util.Optional;
36-
import java.util.Set;
30+
import java.util.*;
3731
import java.util.function.Consumer;
3832
import java.util.stream.Stream;
3933

@@ -1493,6 +1487,88 @@ void queryByWindowKeyset() {
14931487
assertThat(entities).extracting("idProp").contains(three.idProp);
14941488
}
14951489

1490+
@Test
1491+
void queryByWindowKeySetDesc() {
1492+
DummyEntity one = repository.save(createEntity("one"));
1493+
DummyEntity two = repository.save(createEntity("two"));
1494+
DummyEntity three = repository.save(createEntity("three"));
1495+
1496+
WindowIterator<DummyEntity> iter = WindowIterator.of(position -> repository.findFirst2ByOrderByIdPropDesc(position))
1497+
.startingAt(ScrollPosition.keyset());
1498+
1499+
List<DummyEntity> entities = new ArrayList<>();
1500+
while (iter.hasNext())
1501+
entities.add(iter.next());
1502+
1503+
assertThat(entities).extracting("idProp").contains(one.idProp);
1504+
}
1505+
1506+
@Test
1507+
void queryByWindowKeySetDescBackward() {
1508+
DummyEntity one = repository.save(createEntity("one"));
1509+
DummyEntity two = repository.save(createEntity("two"));
1510+
DummyEntity three = repository.save(createEntity("three"));
1511+
DummyEntity four = repository.save(createEntity("four"));
1512+
DummyEntity five = repository.save(createEntity("five"));
1513+
1514+
WindowIterator<DummyEntity> iter = WindowIterator.of(position -> repository.findFirst2ByOrderByIdPropDesc(position))
1515+
.startingAt(ScrollPosition.backward(Map.of("idProp", 3)));
1516+
1517+
List<DummyEntity> entities = new ArrayList<>();
1518+
while (iter.hasNext())
1519+
entities.add(iter.next());
1520+
1521+
assertThat(entities).extracting("idProp").contains(five.idProp);
1522+
}
1523+
1524+
@Test
1525+
void queryByWindowKeySetAscBackward() {
1526+
DummyEntity one = repository.save(createEntity("one"));
1527+
DummyEntity two = repository.save(createEntity("two"));
1528+
DummyEntity three = repository.save(createEntity("three"));
1529+
DummyEntity four = repository.save(createEntity("four"));
1530+
DummyEntity five = repository.save(createEntity("five"));
1531+
1532+
WindowIterator<DummyEntity> iter = WindowIterator.of(position -> repository.findFirst2ByOrderByIdPropAsc(position))
1533+
.startingAt(ScrollPosition.backward(Map.of("idProp", 5)));
1534+
1535+
List<DummyEntity> entities = new ArrayList<>();
1536+
while (iter.hasNext())
1537+
entities.add(iter.next());
1538+
1539+
assertThat(entities).extracting("idProp").contains(one.idProp);
1540+
}
1541+
1542+
@Test
1543+
void queryByWindowKeySetEmptyDb() {
1544+
WindowIterator<DummyEntity> iter = WindowIterator.of(position -> repository.findFirst2ByOrderByIdPropAsc(position))
1545+
.startingAt(ScrollPosition.backward(Map.of("idProp", 5)));
1546+
1547+
List<DummyEntity> entities = new ArrayList<>();
1548+
while (iter.hasNext())
1549+
entities.add(iter.next());
1550+
1551+
assertThat(entities).isEmpty();
1552+
}
1553+
1554+
@Test
1555+
void queryByWindowKeySetTwoKeys() {
1556+
DummyEntity one = repository.save(createEntity("one", it -> it.setPointInTime(Instant.ofEpochSecond(1000))));
1557+
DummyEntity two = repository.save(createEntity("two", it -> it.setPointInTime(Instant.ofEpochSecond(2000))));
1558+
DummyEntity three = repository.save(createEntity("three", it -> it.setPointInTime(Instant.ofEpochSecond(3000))));
1559+
DummyEntity four = repository.save(createEntity("four", it -> it.setPointInTime(Instant.ofEpochSecond(4000))));
1560+
DummyEntity five = repository.save(createEntity("five", it -> it.setPointInTime(Instant.ofEpochSecond(5000))));
1561+
1562+
WindowIterator<DummyEntity> iter = WindowIterator.of(position -> repository.findFirst2ByOrderByIdPropDesc(position))
1563+
.startingAt(ScrollPosition.forward(Map.of("idProp", 5, "pointInTime", Instant.now())));
1564+
1565+
List<DummyEntity> entities = new ArrayList<>();
1566+
while (iter.hasNext())
1567+
entities.add(iter.next());
1568+
1569+
assertThat(entities).extracting("idProp").contains(one.idProp);
1570+
}
1571+
14961572
private Root createRoot(String namePrefix) {
14971573

14981574
return new Root(null, namePrefix,
@@ -1640,6 +1716,8 @@ public interface DummyEntityRepository
16401716
List<DummyEntity> findByBytes(byte[] bytes);
16411717

16421718
Window<DummyEntity> findFirst2ByOrderByIdPropAsc(ScrollPosition position);
1719+
1720+
Window<DummyEntity> findFirst2ByOrderByIdPropDesc(ScrollPosition position);
16431721
}
16441722

16451723
public interface RootRepository extends ListCrudRepository<Root, Long> {

0 commit comments

Comments
 (0)