Skip to content

Commit c4de53d

Browse files
committed
fix: correctly implement Pagination for Query by Example (QBE) with repositories (resolves #gh-449)
1 parent d405cc3 commit c4de53d

15 files changed

+1045
-124
lines changed

redis-om-spring/src/main/java/com/redis/om/spring/repository/support/SimpleRedisDocumentRepository.java

+23-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.redis.om.spring.repository.support;
22

3+
import com.google.common.collect.Iterables;
34
import com.google.common.collect.Lists;
45
import com.google.gson.Gson;
56
import com.google.gson.GsonBuilder;
@@ -17,7 +18,8 @@
1718
import com.redis.om.spring.repository.RedisDocumentRepository;
1819
import com.redis.om.spring.search.stream.EntityStream;
1920
import com.redis.om.spring.search.stream.EntityStreamImpl;
20-
import com.redis.om.spring.search.stream.FluentQueryByExample;
21+
import com.redis.om.spring.search.stream.RedisFluentQueryByExample;
22+
import com.redis.om.spring.search.stream.SearchStream;
2123
import com.redis.om.spring.serialization.gson.GsonListOfType;
2224
import com.redis.om.spring.util.ObjectUtils;
2325
import com.redis.om.spring.vectorize.FeatureExtractor;
@@ -26,6 +28,7 @@
2628
import org.springframework.beans.PropertyAccessor;
2729
import org.springframework.beans.PropertyAccessorFactory;
2830
import org.springframework.beans.factory.annotation.Qualifier;
31+
import org.springframework.dao.IncorrectResultSizeDataAccessException;
2932
import org.springframework.dao.OptimisticLockingFailureException;
3033
import org.springframework.data.annotation.Reference;
3134
import org.springframework.data.annotation.Version;
@@ -89,7 +92,9 @@ public SimpleRedisDocumentRepository( //
8992
KeyValueOperations operations, //
9093
@Qualifier("redisModulesOperations") RedisModulesOperations<?> rmo, //
9194
RediSearchIndexer indexer, //
92-
RedisMappingContext mappingContext, GsonBuilder gsonBuilder, FeatureExtractor featureExtractor, //
95+
RedisMappingContext mappingContext, //
96+
GsonBuilder gsonBuilder, //
97+
FeatureExtractor featureExtractor, //
9398
RedisOMProperties properties) {
9499
super(metadata, operations);
95100
this.modulesOperations = (RedisModulesOperations<String>) rmo;
@@ -373,7 +378,13 @@ private Number getEntityVersion(String key, String versionProperty) {
373378

374379
@Override
375380
public <S extends T> Optional<S> findOne(Example<S> example) {
376-
return entityStream.of(example.getProbeType()).filter(example).findFirst();
381+
Iterable<S> result = findAll(example);
382+
var size = Iterables.size(result);
383+
if (size > 1) {
384+
throw new IncorrectResultSizeDataAccessException("Query returned non unique result", 1);
385+
}
386+
387+
return StreamSupport.stream(result.spliterator(), false).findFirst();
377388
}
378389

379390
@Override
@@ -388,7 +399,13 @@ public <S extends T> Iterable<S> findAll(Example<S> example, Sort sort) {
388399

389400
@Override
390401
public <S extends T> Page<S> findAll(Example<S> example, Pageable pageable) {
391-
return pageFromSlice(entityStream.of(example.getProbeType()).filter(example).getSlice(pageable));
402+
SearchStream<S> stream = entityStream.of(example.getProbeType());
403+
var offset = pageable.getOffset() * pageable.getPageSize();
404+
var limit = pageable.getPageSize();
405+
Slice<S> slice = stream.filter(example).loadAll().limit(limit, Math.toIntExact(offset))
406+
.toList(pageable, stream.getEntityClass());
407+
408+
return pageFromSlice(slice);
392409
}
393410

394411
/* (non-Javadoc)
@@ -467,7 +484,8 @@ public <S extends T, R> R findBy(Example<S> example, Function<FetchableFluentQue
467484
Assert.notNull(queryFunction, "Query function must not be null");
468485

469486
return queryFunction.apply(
470-
new FluentQueryByExample<>(example, example.getProbeType(), entityStream, getSearchOps()));
487+
new RedisFluentQueryByExample<>(example, example.getProbeType(), entityStream, getSearchOps(),
488+
mappingConverter.getMappingContext()));
471489
}
472490

473491
private SearchOperations<String> getSearchOps() {

redis-om-spring/src/main/java/com/redis/om/spring/repository/support/SimpleRedisEnhancedRepository.java

+20-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.redis.om.spring.repository.support;
22

3+
import com.google.common.collect.Iterables;
34
import com.google.common.collect.Lists;
45
import com.redis.om.spring.RedisEnhancedKeyValueAdapter;
56
import com.redis.om.spring.RedisOMProperties;
@@ -14,10 +15,12 @@
1415
import com.redis.om.spring.repository.RedisEnhancedRepository;
1516
import com.redis.om.spring.search.stream.EntityStream;
1617
import com.redis.om.spring.search.stream.EntityStreamImpl;
17-
import com.redis.om.spring.search.stream.FluentQueryByExample;
18+
import com.redis.om.spring.search.stream.RedisFluentQueryByExample;
19+
import com.redis.om.spring.search.stream.SearchStream;
1820
import com.redis.om.spring.util.ObjectUtils;
1921
import com.redis.om.spring.vectorize.FeatureExtractor;
2022
import org.springframework.beans.factory.annotation.Qualifier;
23+
import org.springframework.dao.IncorrectResultSizeDataAccessException;
2124
import org.springframework.data.domain.*;
2225
import org.springframework.data.domain.Sort.Order;
2326
import org.springframework.data.keyvalue.core.IterableConverter;
@@ -299,7 +302,13 @@ private boolean expires(RedisData data) {
299302

300303
@Override
301304
public <S extends T> Optional<S> findOne(Example<S> example) {
302-
return entityStream.of(example.getProbeType()).filter(example).findFirst();
305+
Iterable<S> result = findAll(example);
306+
var size = Iterables.size(result);
307+
if (size > 1) {
308+
throw new IncorrectResultSizeDataAccessException("Query returned non unique result", 1);
309+
}
310+
311+
return StreamSupport.stream(result.spliterator(), false).findFirst();
303312
}
304313

305314
@Override
@@ -314,7 +323,13 @@ public <S extends T> Iterable<S> findAll(Example<S> example, Sort sort) {
314323

315324
@Override
316325
public <S extends T> Page<S> findAll(Example<S> example, Pageable pageable) {
317-
return pageFromSlice(entityStream.of(example.getProbeType()).filter(example).getSlice(pageable));
326+
SearchStream<S> stream = entityStream.of(example.getProbeType());
327+
var offset = pageable.getOffset() * pageable.getPageSize();
328+
var limit = pageable.getPageSize();
329+
Slice<S> slice = stream.filter(example).loadAll().limit(limit, Math.toIntExact(offset))
330+
.toList(pageable, stream.getEntityClass());
331+
332+
return pageFromSlice(slice);
318333
}
319334

320335
@Override
@@ -337,7 +352,8 @@ public <S extends T, R> R findBy(Example<S> example, Function<FetchableFluentQue
337352
Assert.notNull(queryFunction, "Query function must not be null");
338353

339354
return queryFunction.apply(
340-
new FluentQueryByExample<>(example, example.getProbeType(), entityStream, getSearchOps()));
355+
new RedisFluentQueryByExample<>(example, example.getProbeType(), entityStream, getSearchOps(),
356+
mappingConverter.getMappingContext()));
341357
}
342358

343359
private SearchOperations<String> getSearchOps() {

redis-om-spring/src/main/java/com/redis/om/spring/search/stream/AggregationStream.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import com.redis.om.spring.annotations.ReducerFunction;
44
import com.redis.om.spring.metamodel.MetamodelField;
55
import com.redis.om.spring.search.stream.aggregations.filters.AggregationFilter;
6-
import org.springframework.data.domain.PageRequest;
6+
import org.springframework.data.domain.Pageable;
77
import org.springframework.data.domain.Slice;
88
import org.springframework.data.domain.Sort.Order;
99
import redis.clients.jedis.search.aggr.AggregationResult;
@@ -53,7 +53,7 @@ public interface AggregationStream<T> {
5353
// Cursor API
5454
AggregationStream<T> cursor(int i, Duration duration);
5555

56-
<R extends T> Slice<R> toList(PageRequest pageRequest, Class<?>... contentTypes);
56+
<R extends T> Slice<R> toList(Pageable pageRequest, Class<?>... contentTypes);
5757

58-
<R extends T> Slice<R> toList(PageRequest pageRequest, Duration duration, Class<?>... contentTypes);
58+
<R extends T> Slice<R> toList(Pageable pageRequest, Duration duration, Class<?>... contentTypes);
5959
}

redis-om-spring/src/main/java/com/redis/om/spring/search/stream/AggregationStreamImpl.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import com.redis.om.spring.search.stream.aggregations.filters.AggregationFilter;
1111
import com.redis.om.spring.tuple.Tuples;
1212
import com.redis.om.spring.util.ObjectUtils;
13-
import org.springframework.data.domain.PageRequest;
13+
import org.springframework.data.domain.Pageable;
1414
import org.springframework.data.domain.Slice;
1515
import org.springframework.data.domain.Sort.Direction;
1616
import org.springframework.data.domain.Sort.Order;
@@ -208,7 +208,7 @@ public AggregationStream<T> limit(int limit) {
208208
}
209209

210210
@Override
211-
public AggregationStream<T> limit(int offset, int limit) {
211+
public AggregationStream<T> limit(int limit, int offset) {
212212
applyCurrentGroupBy();
213213
aggregation.limit(offset, limit);
214214
limitSet = true;
@@ -387,15 +387,15 @@ public AggregationStream<T> cursor(int count, Duration timeout) {
387387

388388
@Override
389389
@SuppressWarnings({ "unchecked", "rawtypes" })
390-
public <R extends T> Slice<R> toList(PageRequest pageRequest, Class<?>... contentTypes) {
390+
public <R extends T> Slice<R> toList(Pageable pageRequest, Class<?>... contentTypes) {
391391
applyCurrentGroupBy();
392392
aggregation.cursor(pageRequest.getPageSize(), 300000);
393393
return new AggregationPage(this, pageRequest, entityClass, gson, mappingConverter, isDocument);
394394
}
395395

396396
@Override
397397
@SuppressWarnings({ "unchecked", "rawtypes" })
398-
public <R extends T> Slice<R> toList(PageRequest pageRequest, Duration timeout, Class<?>... contentTypes) {
398+
public <R extends T> Slice<R> toList(Pageable pageRequest, Duration timeout, Class<?>... contentTypes) {
399399
applyCurrentGroupBy();
400400
aggregation.cursor(pageRequest.getPageSize(), timeout.toMillis());
401401
return new AggregationPage(this, pageRequest, entityClass, gson, mappingConverter, isDocument);

redis-om-spring/src/main/java/com/redis/om/spring/search/stream/FluentQueryByExample.java

-104
This file was deleted.

0 commit comments

Comments
 (0)