Skip to content

Commit d35522c

Browse files
committed
Remove duplicate keys from RedisQueryEngine.
We now avoid duplicate keys if a key is found in two indices. Closes #2799
1 parent 4e5aabf commit d35522c

File tree

2 files changed

+67
-27
lines changed

2 files changed

+67
-27
lines changed

src/main/java/org/springframework/data/redis/core/RedisQueryEngine.java

+46-27
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,10 @@
2020
import java.util.Collections;
2121
import java.util.Comparator;
2222
import java.util.LinkedHashMap;
23+
import java.util.LinkedHashSet;
2324
import java.util.List;
2425
import java.util.Map;
26+
import java.util.Set;
2527

2628
import org.springframework.data.geo.Circle;
2729
import org.springframework.data.geo.GeoResult;
@@ -31,15 +33,18 @@
3133
import org.springframework.data.keyvalue.core.SortAccessor;
3234
import org.springframework.data.keyvalue.core.SpelSortAccessor;
3335
import org.springframework.data.keyvalue.core.query.KeyValueQuery;
36+
import org.springframework.data.redis.connection.RedisConnection;
3437
import org.springframework.data.redis.connection.RedisGeoCommands.GeoLocation;
3538
import org.springframework.data.redis.connection.RedisGeoCommands.GeoRadiusCommandArgs;
39+
import org.springframework.data.redis.connection.util.ByteArrayWrapper;
3640
import org.springframework.data.redis.core.convert.GeoIndexedPropertyValue;
3741
import org.springframework.data.redis.core.convert.RedisData;
3842
import org.springframework.data.redis.repository.query.RedisOperationChain;
3943
import org.springframework.data.redis.repository.query.RedisOperationChain.NearPath;
4044
import org.springframework.data.redis.repository.query.RedisOperationChain.PathAndValue;
4145
import org.springframework.data.redis.util.ByteUtils;
4246
import org.springframework.expression.spel.standard.SpelExpressionParser;
47+
import org.springframework.lang.NonNullApi;
4348
import org.springframework.lang.Nullable;
4449
import org.springframework.util.CollectionUtils;
4550

@@ -95,44 +100,21 @@ private <T> List<T> doFind(RedisOperationChain criteria, long offset, int rows,
95100

96101
RedisCallback<Map<byte[], Map<byte[], byte[]>>> callback = connection -> {
97102

98-
List<byte[]> allKeys = new ArrayList<>();
99-
if (!criteria.getSismember().isEmpty()) {
100-
allKeys.addAll(connection.sInter(keys(keyspace + ":", criteria.getSismember())));
101-
}
102-
103-
if (!criteria.getOrSismember().isEmpty()) {
104-
allKeys.addAll(connection.sUnion(keys(keyspace + ":", criteria.getOrSismember())));
105-
}
106-
107-
if (criteria.getNear() != null) {
108-
109-
GeoRadiusCommandArgs limit = GeoRadiusCommandArgs.newGeoRadiusArgs();
110-
111-
if (rows > 0) {
112-
limit = limit.limit(rows);
113-
}
114-
115-
GeoResults<GeoLocation<byte[]>> x = connection.geoRadius(geoKey(keyspace + ":", criteria.getNear()),
116-
new Circle(criteria.getNear().getPoint(), criteria.getNear().getDistance()), limit);
117-
for (GeoResult<GeoLocation<byte[]>> y : x) {
118-
allKeys.add(y.getContent().getName());
119-
}
120-
}
121-
103+
List<byte[]> keys = findKeys(criteria, rows, keyspace, connection);
122104
byte[] keyspaceBin = getRequiredAdapter().getConverter().getConversionService().convert(keyspace + ":",
123105
byte[].class);
124106

125107
Map<byte[], Map<byte[], byte[]>> rawData = new LinkedHashMap<>();
126108

127-
if (allKeys.isEmpty() || allKeys.size() < offset) {
109+
if (keys.isEmpty() || keys.size() < offset) {
128110
return Collections.emptyMap();
129111
}
130112

131113
int offsetToUse = Math.max(0, (int) offset);
132114
if (rows > 0) {
133-
allKeys = allKeys.subList(Math.max(0, offsetToUse), Math.min(offsetToUse + rows, allKeys.size()));
115+
keys = keys.subList(Math.max(0, offsetToUse), Math.min(offsetToUse + rows, keys.size()));
134116
}
135-
for (byte[] id : allKeys) {
117+
for (byte[] id : keys) {
136118

137119
byte[] singleKey = ByteUtils.concat(keyspaceBin, id);
138120
rawData.put(id, connection.hGetAll(singleKey));
@@ -161,6 +143,43 @@ private <T> List<T> doFind(RedisOperationChain criteria, long offset, int rows,
161143
return result;
162144
}
163145

146+
private List<byte[]> findKeys(RedisOperationChain criteria, int rows, String keyspace, RedisConnection connection) {
147+
148+
List<byte[]> allKeys = new ArrayList<>();
149+
150+
if (!criteria.getSismember().isEmpty()) {
151+
allKeys.addAll(connection.sInter(keys(keyspace + ":", criteria.getSismember())));
152+
}
153+
154+
if (!criteria.getOrSismember().isEmpty()) {
155+
allKeys.addAll(connection.sUnion(keys(keyspace + ":", criteria.getOrSismember())));
156+
}
157+
158+
if (criteria.getNear() != null) {
159+
160+
GeoRadiusCommandArgs limit = GeoRadiusCommandArgs.newGeoRadiusArgs();
161+
162+
if (rows > 0) {
163+
limit = limit.limit(rows);
164+
}
165+
166+
GeoResults<GeoLocation<byte[]>> x = connection.geoRadius(geoKey(keyspace + ":", criteria.getNear()),
167+
new Circle(criteria.getNear().getPoint(), criteria.getNear().getDistance()), limit);
168+
for (GeoResult<GeoLocation<byte[]>> y : x) {
169+
allKeys.add(y.getContent().getName());
170+
}
171+
}
172+
173+
174+
Set<ByteArrayWrapper> unique = new LinkedHashSet<>(allKeys.size());
175+
allKeys.forEach(key -> unique.add(new ByteArrayWrapper(key)));
176+
177+
List<byte[]> uniqueKeys = new ArrayList<>(unique.size());
178+
unique.forEach(key -> uniqueKeys.add(key.getArray()));
179+
180+
return uniqueKeys;
181+
}
182+
164183
@Override
165184
public Collection<?> execute(RedisOperationChain criteria, Comparator<?> sort, long offset, int rows,
166185
String keyspace) {

src/test/java/org/springframework/data/redis/repository/RedisRepositoryIntegrationTestBase.java

+21
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,25 @@ void shouldApplyPageableCorrectlyWhenUsingFindByWithoutCriteria() {
268268
assertThat(repo.findBy(firstPage.nextPageable()).getContent()).hasSize(1);
269269
}
270270

271+
@Test // GH-2799
272+
void shouldReturnEntitiesWithoutDuplicates() {
273+
274+
Person eddard = new Person("eddard", "stark");
275+
eddard.setAlive(true);
276+
277+
Person robb = new Person("robb", "stark");
278+
robb.setAlive(false);
279+
280+
Person jon = new Person("jon", "snow");
281+
jon.setAlive(true);
282+
283+
repo.saveAll(Arrays.asList(eddard, robb, jon));
284+
285+
List<Person> result = repo.findByFirstnameAndLastnameOrAliveIsTrue("eddard", "stark");
286+
assertThat(result).hasSize(2);
287+
assertThat(result).contains(eddard, jon);
288+
}
289+
271290
@Test // DATAREDIS-771
272291
void shouldFindByBooleanIsTrue() {
273292

@@ -537,6 +556,8 @@ public interface PersonRepository extends PagingAndSortingRepository<Person, Str
537556

538557
List<Person> findByFirstnameOrLastname(String firstname, String lastname);
539558

559+
List<Person> findByFirstnameAndLastnameOrAliveIsTrue(String firstname, String lastname);
560+
540561
List<Person> findFirstBy();
541562

542563
List<Person> findTop2By();

0 commit comments

Comments
 (0)