Add support for GEOSEARCH/GEOSEARCHSTORE commands.

Supported through Lettuce only. GeoOperations support GEOSEARCH for now.

Closes: #2043
Original Pull Request: #2113
This commit is contained in:
Mark Paluch
2021-07-05 11:20:39 +02:00
committed by Christoph Strobl
parent 2f6dc3e79e
commit ce52baff98
21 changed files with 2319 additions and 38 deletions

View File

@@ -24,6 +24,7 @@ import static org.springframework.data.redis.connection.BitFieldSubCommands.BitF
import static org.springframework.data.redis.connection.ClusterTestVariables.*;
import static org.springframework.data.redis.connection.RedisGeoCommands.DistanceUnit.*;
import static org.springframework.data.redis.connection.RedisGeoCommands.GeoRadiusCommandArgs.*;
import static org.springframework.data.redis.connection.RedisGeoCommands.GeoSearchStoreCommandArgs.*;
import static org.springframework.data.redis.core.ScanOptions.*;
import java.time.Duration;
@@ -45,6 +46,7 @@ import org.springframework.dao.DataAccessException;
import org.springframework.data.domain.Range.Bound;
import org.springframework.data.geo.Circle;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.GeoResult;
import org.springframework.data.geo.GeoResults;
import org.springframework.data.geo.Metrics;
import org.springframework.data.geo.Point;
@@ -3282,6 +3284,102 @@ public abstract class AbstractConnectionIntegrationTests {
assertThat(((GeoResults<GeoLocation<String>>) results.get(1)).getContent()).hasSize(2);
}
@Test // GH-2043
@EnabledOnCommand("GEOSEARCH")
@EnabledOnRedisDriver(RedisDriver.LETTUCE)
void geoSearchByMemberShouldReturnMembersCorrectly() {
String key = "geo-" + UUID.randomUUID();
actual.add(connection.geoAdd(key, Arrays.asList(ARIGENTO, CATANIA, PALERMO)));
actual.add(connection.geoSearch(key, RedisGeoCommands.GeoReference.fromMember(PALERMO),
RedisGeoCommands.GeoShape.byRadius(new Distance(200, KILOMETERS)), newGeoSearchArgs().limit(2)));
List<Object> results = getResults();
List<GeoResult<GeoLocation<String>>> content = ((GeoResults<GeoLocation<String>>) results.get(1)).getContent();
assertThat(content).hasSize(2);
assertThat(content.get(0).getDistance()).isEqualTo(new Distance(0, KILOMETERS));
assertThat(content.get(0).getContent().getPoint()).isNull();
}
@Test // GH-2043
@EnabledOnCommand("GEOSEARCH")
@EnabledOnRedisDriver(RedisDriver.LETTUCE)
void geoSearchByPointShouldReturnMembersCorrectly() {
String key = "geo-" + UUID.randomUUID();
actual.add(connection.geoAdd(key, Arrays.asList(ARIGENTO, CATANIA, PALERMO)));
actual.add(connection.geoSearch(key, RedisGeoCommands.GeoReference.fromCoordinate(PALERMO),
RedisGeoCommands.GeoShape.byRadius(new Distance(200, KILOMETERS)), newGeoSearchArgs().limit(2)));
List<Object> results = getResults();
List<GeoResult<GeoLocation<String>>> content = ((GeoResults<GeoLocation<String>>) results.get(1)).getContent();
assertThat(content).hasSize(2);
assertThat(content.get(0).getDistance()).isEqualTo(new Distance(0, KILOMETERS));
assertThat(content.get(0).getContent().getPoint()).isNull();
}
@Test // GH-2043
@EnabledOnCommand("GEOSEARCH")
@EnabledOnRedisDriver(RedisDriver.LETTUCE)
void geoSearchShouldConsiderDistanceCorrectly() {
String key = "geo-" + UUID.randomUUID();
actual.add(connection.geoAdd(key, Arrays.asList(ARIGENTO, CATANIA, PALERMO)));
actual.add(
connection.geoSearch(key, RedisGeoCommands.GeoReference.fromMember(PALERMO),
RedisGeoCommands.GeoShape.byRadius(new Distance(200, KILOMETERS)),
newGeoSearchArgs().limit(2).includeDistance().includeCoordinates()));
List<Object> results = getResults();
List<GeoResult<GeoLocation<String>>> content = ((GeoResults<GeoLocation<String>>) results.get(1)).getContent();
assertThat(content).hasSize(2);
assertThat(content.get(0).getDistance()).isNotNull();
assertThat(content.get(0).getContent().getPoint()).isNotNull();
}
@Test // GH-2043
@EnabledOnCommand("GEOSEARCHSTORE")
@EnabledOnRedisDriver(RedisDriver.LETTUCE)
void geoSearchStoreByMemberShouldStoreResult() {
String key = "geo-" + UUID.randomUUID();
actual.add(connection.geoAdd(key, Arrays.asList(ARIGENTO, CATANIA, PALERMO)));
actual.add(connection.geoSearchStore("georesults", key, RedisGeoCommands.GeoReference.fromMember(PALERMO),
RedisGeoCommands.GeoShape.byRadius(new Distance(200, KILOMETERS)),
newGeoSearchStoreArgs().limit(2).storeDistance()));
actual.add(connection.zScore("georesults", PALERMO.getName()));
actual.add(connection.zScore("georesults", ARIGENTO.getName()));
List<Object> results = getResults();
assertThat(results.get(1)).isEqualTo(2L);
assertThat((Double) results.get(2)).isLessThan(1);
assertThat((Double) results.get(3)).isGreaterThan(1);
}
@Test // GH-2043
@EnabledOnCommand("GEOSEARCHSTORE")
@EnabledOnRedisDriver(RedisDriver.LETTUCE)
void geoSearchStoreByPointShouldStoreResult() {
String key = "geo-" + UUID.randomUUID();
actual.add(connection.geoAdd(key, Arrays.asList(ARIGENTO, CATANIA, PALERMO)));
actual.add(connection.geoSearchStore("georesults", key, RedisGeoCommands.GeoReference.fromCoordinate(PALERMO),
RedisGeoCommands.GeoShape.byRadius(new Distance(200, KILOMETERS)),
newGeoSearchStoreArgs().limit(2).storeDistance()));
actual.add(connection.zScore("georesults", PALERMO.getName()));
actual.add(connection.zScore("georesults", ARIGENTO.getName()));
List<Object> results = getResults();
assertThat(results.get(1)).isEqualTo(2L);
assertThat((Double) results.get(2)).isLessThan(1);
assertThat((Double) results.get(3)).isGreaterThan(1);
}
@Test // DATAREDIS-698
void hStrLenReturnsFieldLength() {

View File

@@ -17,6 +17,7 @@ package org.springframework.data.redis.connection.lettuce;
import static org.assertj.core.api.Assertions.*;
import static org.assertj.core.data.Offset.offset;
import static org.springframework.data.redis.connection.RedisGeoCommands.*;
import static org.springframework.data.redis.connection.RedisGeoCommands.DistanceUnit.*;
import static org.springframework.data.redis.connection.RedisGeoCommands.GeoRadiusCommandArgs.*;
@@ -31,7 +32,7 @@ import org.springframework.data.geo.Circle;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.Metrics;
import org.springframework.data.geo.Point;
import org.springframework.data.redis.connection.RedisGeoCommands.GeoLocation;
import org.springframework.data.redis.test.condition.EnabledOnCommand;
import org.springframework.data.redis.test.extension.parametrized.ParameterizedRedisTest;
/**
@@ -231,7 +232,6 @@ public class LettuceReactiveGeoCommandsIntegrationTests extends LettuceReactiveC
}) //
.expectNextCount(1) //
.verifyComplete();
}
@ParameterizedRedisTest // DATAREDIS-525
@@ -248,4 +248,43 @@ public class LettuceReactiveGeoCommandsIntegrationTests extends LettuceReactiveC
.verifyComplete();
}
@ParameterizedRedisTest // GH-2043
@EnabledOnCommand("GEOSEARCH")
void geoSearchShouldReturnMembersCorrectly() {
nativeCommands.geoadd(KEY_1, PALERMO.getPoint().getX(), PALERMO.getPoint().getY(), PALERMO_MEMBER_NAME);
nativeCommands.geoadd(KEY_1, CATANIA.getPoint().getX(), CATANIA.getPoint().getY(), CATANIA_MEMBER_NAME);
nativeCommands.geoadd(KEY_1, ARIGENTO.getPoint().getX(), ARIGENTO.getPoint().getY(), ARIGENTO_MEMBER_NAME);
connection.geoCommands()
.geoSearch(KEY_1_BBUFFER, GeoReference.fromMember(PALERMO.getName()),
GeoShape.byRadius(new Distance(200, KILOMETERS)), newGeoRadiusArgs().limit(2))
.as(StepVerifier::create) //
.expectNextCount(2) //
.verifyComplete();
}
@ParameterizedRedisTest // GH-2043
@EnabledOnCommand("GEOSEARCHSTORE")
void geoSearchStoreShouldStoreMembersCorrectly() {
nativeCommands.geoadd(SAME_SLOT_KEY_1, PALERMO.getPoint().getX(), PALERMO.getPoint().getY(), PALERMO_MEMBER_NAME);
nativeCommands.geoadd(SAME_SLOT_KEY_1, CATANIA.getPoint().getX(), CATANIA.getPoint().getY(), CATANIA_MEMBER_NAME);
nativeCommands.geoadd(SAME_SLOT_KEY_1, ARIGENTO.getPoint().getX(), ARIGENTO.getPoint().getY(),
ARIGENTO_MEMBER_NAME);
connection.geoCommands()
.geoSearchStore(SAME_SLOT_KEY_2_BBUFFER, SAME_SLOT_KEY_1_BBUFFER, GeoReference.fromMember(PALERMO.getName()),
GeoShape.byRadius(new Distance(200, KILOMETERS)),
GeoSearchStoreCommandArgs.newGeoSearchStoreArgs().limit(2))
.as(StepVerifier::create) //
.expectNext(2L) //
.verifyComplete();
connection.zSetCommands().zCard(SAME_SLOT_KEY_2_BBUFFER) //
.as(StepVerifier::create) //
.expectNext(2L) //
.verifyComplete();
}
}

View File

@@ -16,6 +16,7 @@
package org.springframework.data.redis.core;
import static org.assertj.core.api.Assertions.*;
import static org.assertj.core.api.Assumptions.*;
import static org.assertj.core.data.Offset.offset;
import static org.springframework.data.redis.connection.RedisGeoCommands.DistanceUnit.*;
import static org.springframework.data.redis.connection.RedisGeoCommands.GeoRadiusCommandArgs.*;
@@ -32,8 +33,10 @@ import org.springframework.data.geo.Distance;
import org.springframework.data.geo.GeoResults;
import org.springframework.data.geo.Point;
import org.springframework.data.redis.ObjectFactory;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.connection.RedisGeoCommands.DistanceUnit;
import org.springframework.data.redis.connection.RedisGeoCommands.GeoLocation;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.test.condition.EnabledOnCommand;
import org.springframework.data.redis.test.extension.parametrized.MethodSource;
import org.springframework.data.redis.test.extension.parametrized.ParameterizedRedisTest;
@@ -429,4 +432,153 @@ public class DefaultGeoOperationsIntegrationTests<K, M> {
assertThat(geoOperations.remove(key, member1)).isEqualTo(1L);
}
@ParameterizedRedisTest // GH-2043
@EnabledOnCommand("GEOSEARCH")
void geoSearchWithinShouldReturnMembers() {
assumeThat(redisTemplate.getRequiredConnectionFactory()).isInstanceOf(LettuceConnectionFactory.class);
K key = keyFactory.instance();
M member1 = valueFactory.instance();
M member2 = valueFactory.instance();
M member3 = valueFactory.instance();
geoOperations.add(key, POINT_PALERMO, member1);
geoOperations.add(key, POINT_CATANIA, member2);
geoOperations.add(key, POINT_ARIGENTO, member3);
GeoResults<GeoLocation<M>> result = geoOperations.search(key,
RedisGeoCommands.GeoReference.fromCoordinate(POINT_PALERMO), new Distance(150, KILOMETERS),
newGeoSearchArgs().includeCoordinates().sortAscending());
assertThat(result.getContent()).hasSize(2);
assertThat(result.getContent().get(0).getContent().getPoint().getX()).isCloseTo(POINT_PALERMO.getX(), offset(0.05));
assertThat(result.getContent().get(0).getContent().getPoint().getY()).isCloseTo(POINT_PALERMO.getY(), offset(0.05));
assertThat(result.getContent().get(0).getContent().getName()).isEqualTo(member1);
assertThat(result.getContent().get(1).getContent().getPoint().getX()).isCloseTo(POINT_ARIGENTO.getX(),
offset(0.05));
assertThat(result.getContent().get(1).getContent().getPoint().getY()).isCloseTo(POINT_ARIGENTO.getY(),
offset(0.05));
assertThat(result.getContent().get(1).getContent().getName()).isEqualTo(member3);
}
@ParameterizedRedisTest // GH-2043
@EnabledOnCommand("GEOSEARCH")
void geoSearchByMemberShouldReturnResults() {
assumeThat(redisTemplate.getRequiredConnectionFactory()).isInstanceOf(LettuceConnectionFactory.class);
K key = keyFactory.instance();
M member1 = valueFactory.instance();
M member2 = valueFactory.instance();
M member3 = valueFactory.instance();
geoOperations.add(key, POINT_PALERMO, member1);
geoOperations.add(key, POINT_CATANIA, member2);
geoOperations.add(key, POINT_ARIGENTO, member3);
GeoResults<GeoLocation<M>> result = geoOperations.search(key, RedisGeoCommands.GeoReference.fromMember(member1),
new Distance(150, KILOMETERS),
newGeoSearchArgs().includeCoordinates().sortAscending());
assertThat(result.getContent()).hasSize(2);
assertThat(result.getContent().get(0).getContent().getPoint().getX()).isCloseTo(POINT_PALERMO.getX(), offset(0.05));
assertThat(result.getContent().get(0).getContent().getPoint().getY()).isCloseTo(POINT_PALERMO.getY(), offset(0.05));
assertThat(result.getContent().get(0).getContent().getName()).isEqualTo(member1);
assertThat(result.getContent().get(1).getContent().getPoint().getX()).isCloseTo(POINT_ARIGENTO.getX(),
offset(0.05));
assertThat(result.getContent().get(1).getContent().getPoint().getY()).isCloseTo(POINT_ARIGENTO.getY(),
offset(0.05));
assertThat(result.getContent().get(1).getContent().getName()).isEqualTo(member3);
}
@ParameterizedRedisTest // GH-2043
@EnabledOnCommand("GEOSEARCH")
void geoSearchByPointWithinBoundingBoxShouldReturnMembers() {
assumeThat(redisTemplate.getRequiredConnectionFactory()).isInstanceOf(LettuceConnectionFactory.class);
K key = keyFactory.instance();
M member1 = valueFactory.instance();
M member2 = valueFactory.instance();
M member3 = valueFactory.instance();
geoOperations.add(key, POINT_PALERMO, member1);
geoOperations.add(key, POINT_CATANIA, member2);
geoOperations.add(key, POINT_ARIGENTO, member3);
GeoResults<GeoLocation<M>> result = geoOperations.search(key,
RedisGeoCommands.GeoReference.fromCoordinate(POINT_PALERMO),
new RedisGeoCommands.BoundingBox(180, 180, KILOMETERS),
newGeoSearchArgs().includeCoordinates().sortAscending());
assertThat(result.getContent()).hasSize(2);
assertThat(result.getContent().get(0).getContent().getPoint().getX()).isCloseTo(POINT_PALERMO.getX(), offset(0.05));
assertThat(result.getContent().get(0).getContent().getPoint().getY()).isCloseTo(POINT_PALERMO.getY(), offset(0.05));
assertThat(result.getContent().get(0).getContent().getName()).isEqualTo(member1);
assertThat(result.getContent().get(1).getContent().getPoint().getX()).isCloseTo(POINT_ARIGENTO.getX(),
offset(0.05));
assertThat(result.getContent().get(1).getContent().getPoint().getY()).isCloseTo(POINT_ARIGENTO.getY(),
offset(0.05));
assertThat(result.getContent().get(1).getContent().getName()).isEqualTo(member3);
}
@ParameterizedRedisTest // GH-2043
@EnabledOnCommand("GEOSEARCH")
void geoSearchByMemberWithinBoundingBoxShouldReturnMembers() {
assumeThat(redisTemplate.getRequiredConnectionFactory()).isInstanceOf(LettuceConnectionFactory.class);
K key = keyFactory.instance();
M member1 = valueFactory.instance();
M member2 = valueFactory.instance();
M member3 = valueFactory.instance();
geoOperations.add(key, POINT_PALERMO, member1);
geoOperations.add(key, POINT_CATANIA, member2);
geoOperations.add(key, POINT_ARIGENTO, member3);
GeoResults<GeoLocation<M>> result = geoOperations.search(key, RedisGeoCommands.GeoReference.fromMember(member1),
new RedisGeoCommands.BoundingBox(180, 180, KILOMETERS),
newGeoSearchArgs().includeCoordinates().sortAscending());
assertThat(result.getContent()).hasSize(2);
assertThat(result.getContent().get(0).getContent().getPoint().getX()).isCloseTo(POINT_PALERMO.getX(), offset(0.05));
assertThat(result.getContent().get(0).getContent().getPoint().getY()).isCloseTo(POINT_PALERMO.getY(), offset(0.05));
assertThat(result.getContent().get(0).getContent().getName()).isEqualTo(member1);
assertThat(result.getContent().get(1).getContent().getPoint().getX()).isCloseTo(POINT_ARIGENTO.getX(),
offset(0.05));
assertThat(result.getContent().get(1).getContent().getPoint().getY()).isCloseTo(POINT_ARIGENTO.getY(),
offset(0.05));
assertThat(result.getContent().get(1).getContent().getName()).isEqualTo(member3);
}
@ParameterizedRedisTest // GH-2043
@EnabledOnCommand("GEOSEARCHSTORE")
void geoSearchAndStoreWithinShouldReturnMembers() {
assumeThat(redisTemplate.getRequiredConnectionFactory()).isInstanceOf(LettuceConnectionFactory.class);
K key = keyFactory.instance();
K destKey = keyFactory.instance();
M member1 = valueFactory.instance();
M member2 = valueFactory.instance();
M member3 = valueFactory.instance();
geoOperations.add(key, POINT_PALERMO, member1);
geoOperations.add(key, POINT_CATANIA, member2);
geoOperations.add(key, POINT_ARIGENTO, member3);
Long result = geoOperations.searchAndStore(key, destKey,
RedisGeoCommands.GeoReference.fromCoordinate(POINT_PALERMO), new Distance(150, KILOMETERS),
RedisGeoCommands.GeoSearchStoreCommandArgs.newGeoSearchStoreArgs().sortAscending());
assertThat(result).isEqualTo(2);
assertThat(redisTemplate.boundZSetOps(destKey).size()).isEqualTo(2);
}
}

View File

@@ -16,6 +16,7 @@
package org.springframework.data.redis.core;
import static org.assertj.core.api.Assertions.*;
import static org.springframework.data.redis.connection.RedisGeoCommands.*;
import static org.springframework.data.redis.connection.RedisGeoCommands.DistanceUnit.*;
import static org.springframework.data.redis.connection.RedisGeoCommands.GeoRadiusCommandArgs.*;
@@ -37,7 +38,6 @@ import org.springframework.data.geo.Point;
import org.springframework.data.redis.ObjectFactory;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisGeoCommands.GeoLocation;
import org.springframework.data.redis.core.ReactiveOperationsTestParams.Fixture;
import org.springframework.data.redis.test.condition.EnabledOnCommand;
import org.springframework.data.redis.test.extension.parametrized.MethodSource;
@@ -434,4 +434,59 @@ public class DefaultReactiveGeoOperationsIntegrationTests<K, V> {
.as(StepVerifier::create) //
.verifyComplete();
}
@ParameterizedRedisTest // GH-2043
@EnabledOnCommand("GEOSEARCH")
void geoSearchShouldReturnLocationsWithDistance() {
K key = keyFactory.instance();
V member1 = valueFactory.instance();
V member2 = valueFactory.instance();
V member3 = valueFactory.instance();
geoOperations.add(key, POINT_PALERMO, member1).block();
geoOperations.add(key, POINT_CATANIA, member2).block();
geoOperations.add(key, POINT_ARIGENTO, member3).block();
geoOperations
.search(key, GeoReference.fromMember(member3), GeoShape.byRadius(new Distance(100D, KILOMETERS)),
newGeoRadiusArgs().includeDistance().sortDescending())
.as(StepVerifier::create) //
.consumeNextWith(actual -> {
assertThat(actual.getDistance().getValue()).isCloseTo(90.9778, offset(0.005));
assertThat(actual.getContent().getName()).isEqualTo(member1);
}) //
.consumeNextWith(actual -> {
assertThat(actual.getDistance().getValue()).isCloseTo(0.0, offset(0.005));
assertThat(actual.getContent().getName()).isEqualTo(member3);
}) //
.verifyComplete();
}
@ParameterizedRedisTest // GH-2043
@EnabledOnCommand("GEOSEARCH")
void geoSearchAndStoreShouldStoreItems() {
K key = keyFactory.instance();
K destKey = keyFactory.instance();
V member1 = valueFactory.instance();
V member2 = valueFactory.instance();
V member3 = valueFactory.instance();
geoOperations.add(key, POINT_PALERMO, member1).block();
geoOperations.add(key, POINT_CATANIA, member2).block();
geoOperations.add(key, POINT_ARIGENTO, member3).block();
geoOperations.searchAndStore(key, destKey, GeoReference.fromMember(member3), new Distance(100D, KILOMETERS))
.as(StepVerifier::create) //
.expectNext(2L) //
.verifyComplete();
redisTemplate.opsForZSet().size(destKey) //
.as(StepVerifier::create) //
.expectNext(2L) //
.verifyComplete();
}
}