DATAREDIS-533 - Add support for geo indexes.
We now allow usage of @GeoIndexed to mark GeoLocation or Point properties as candidates for secondary index creation. Non null values will be included in GEOADD command as follows:
GEOADD keyspace:property-path point.x point.y entity-id
@GeoIndexed can be used on top level as well as on nested properties.
class Person {
@Id String id;
String firstname, lastname;
Address hometown;
}
class Address {
String city, street, housenumber;
@GeoIndexed Point location;
}
The above allows us to derive geospatial queries from a given method using NEAR and WITHIN keywords like:
interface PersonRepository extends CrudRepository<Person, String> {
List<Person> findByAddressLocationNear(Point point, Distance distance);
List<Person> findByAddressLocationWithin(Circle circle);
}
Partial updates on the Point itself also trigger an index refresh operation. So it is possible to alter existing entities via:
template.save(new PartialUpdate<Person>("1", Person.class).set("address.location", new Point(17, 18));
Original pull request: #215.
This commit is contained in:
committed by
Mark Paluch
parent
5d09272876
commit
07d0b82100
@@ -35,6 +35,7 @@ import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.core.convert.support.GenericConversionService;
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.redis.connection.RedisConnection;
|
||||
import org.springframework.data.redis.core.convert.GeoIndexedPropertyValue;
|
||||
import org.springframework.data.redis.core.convert.IndexedData;
|
||||
import org.springframework.data.redis.core.convert.MappingRedisConverter;
|
||||
import org.springframework.data.redis.core.convert.PathIndexResolver;
|
||||
@@ -215,6 +216,21 @@ public class IndexWriterUnitTests {
|
||||
verify(connectionMock, times(1)).sRem(any(byte[].class), eq(KEY_BIN));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-533
|
||||
*/
|
||||
@Test
|
||||
public void removeGeoIndexShouldCallGeoRemove() {
|
||||
|
||||
byte[] indexKey1 = "persons:location".getBytes(CHARSET);
|
||||
|
||||
when(connectionMock.keys(any(byte[].class))).thenReturn(new LinkedHashSet<byte[]>(Arrays.asList(indexKey1)));
|
||||
|
||||
writer.removeKeyFromExistingIndexes(KEY_BIN, new GeoIndexedPropertyValue(KEYSPACE, "address.city", null));
|
||||
|
||||
verify(connectionMock).geoRemove(indexKey1, KEY_BIN);
|
||||
}
|
||||
|
||||
static class StubIndxedData implements IndexedData {
|
||||
|
||||
@Override
|
||||
|
||||
@@ -36,6 +36,7 @@ import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.annotation.Reference;
|
||||
import org.springframework.data.geo.Point;
|
||||
import org.springframework.data.keyvalue.annotation.KeySpace;
|
||||
import org.springframework.data.redis.ConnectionFactoryTracker;
|
||||
import org.springframework.data.redis.connection.RedisConnection;
|
||||
@@ -45,6 +46,7 @@ import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactor
|
||||
import org.springframework.data.redis.core.convert.Bucket;
|
||||
import org.springframework.data.redis.core.convert.KeyspaceConfiguration;
|
||||
import org.springframework.data.redis.core.convert.MappingConfiguration;
|
||||
import org.springframework.data.redis.core.index.GeoIndexed;
|
||||
import org.springframework.data.redis.core.index.IndexConfiguration;
|
||||
import org.springframework.data.redis.core.index.Indexed;
|
||||
import org.springframework.data.redis.core.mapping.RedisMappingContext;
|
||||
@@ -510,6 +512,90 @@ public class RedisKeyValueAdapterTests {
|
||||
assertThat(template.opsForHash().hasKey("persons:1", "relatives.[stepfather].firstname"), is(false));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-533
|
||||
*/
|
||||
@Test
|
||||
public void putShouldCreateGeoIndexCorrectly() {
|
||||
|
||||
Person tam = new Person();
|
||||
tam.id = "1";
|
||||
tam.firstname = "tam";
|
||||
tam.address = new Address();
|
||||
tam.address.location = new Point(10, 20);
|
||||
|
||||
adapter.put("1", tam, "persons");
|
||||
|
||||
assertThat(template.opsForZSet().score("persons:address:location", "1"), is(notNullValue()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-533
|
||||
*/
|
||||
@Test
|
||||
public void deleteShouldRemoveGeoIndexCorrectly() {
|
||||
|
||||
Person tam = new Person();
|
||||
tam.id = "1";
|
||||
tam.firstname = "tam";
|
||||
tam.address = new Address();
|
||||
tam.address.location = new Point(10, 20);
|
||||
|
||||
adapter.put("1", tam, "persons");
|
||||
|
||||
adapter.delete("1", "persons", Person.class);
|
||||
|
||||
assertThat(template.opsForZSet().score("persons:address:location", "1"), is(nullValue()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-533
|
||||
*/
|
||||
@Test
|
||||
public void updateShouldAlterGeoIndexCorrectlyOnDelete() {
|
||||
|
||||
Person tam = new Person();
|
||||
tam.id = "1";
|
||||
tam.firstname = "tam";
|
||||
tam.address = new Address();
|
||||
tam.address.location = new Point(10, 20);
|
||||
|
||||
adapter.put("1", tam, "persons");
|
||||
|
||||
PartialUpdate<Person> update = new PartialUpdate<Person>("1", Person.class) //
|
||||
.del("address.location");
|
||||
|
||||
adapter.update(update);
|
||||
|
||||
assertThat(template.opsForZSet().score("persons:address:location", "1"), is(nullValue()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-533
|
||||
*/
|
||||
@Test
|
||||
public void updateShouldAlterGeoIndexCorrectlyOnUpdate() {
|
||||
|
||||
Person tam = new Person();
|
||||
tam.id = "1";
|
||||
tam.firstname = "tam";
|
||||
tam.address = new Address();
|
||||
tam.address.location = new Point(10, 20);
|
||||
|
||||
adapter.put("1", tam, "persons");
|
||||
|
||||
PartialUpdate<Person> update = new PartialUpdate<Person>("1", Person.class) //
|
||||
.set("address.location", new Point(17, 18));
|
||||
|
||||
adapter.update(update);
|
||||
|
||||
assertThat(template.opsForZSet().score("persons:address:location", "1"), is(notNullValue()));
|
||||
Point updatedLocation = template.opsForGeo().geoPos("persons:address:location", "1").iterator().next();
|
||||
|
||||
assertThat(updatedLocation.getX(), is(closeTo(17D, 0.005)));
|
||||
assertThat(updatedLocation.getY(), is(closeTo(18D, 0.005)));
|
||||
}
|
||||
|
||||
@KeySpace("persons")
|
||||
static class Person {
|
||||
|
||||
@@ -536,6 +622,8 @@ public class RedisKeyValueAdapterTests {
|
||||
|
||||
String city;
|
||||
@Indexed String country;
|
||||
@GeoIndexed Point location;
|
||||
|
||||
}
|
||||
|
||||
static class AddressWithId extends Address {
|
||||
|
||||
@@ -34,10 +34,13 @@ import java.util.Set;
|
||||
import org.hamcrest.core.IsCollectionContaining;
|
||||
import org.hamcrest.core.IsInstanceOf;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.springframework.data.geo.Point;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.redis.core.convert.ConversionTestEntities.Address;
|
||||
import org.springframework.data.redis.core.convert.ConversionTestEntities.AddressWithId;
|
||||
@@ -48,6 +51,7 @@ import org.springframework.data.redis.core.convert.ConversionTestEntities.Person
|
||||
import org.springframework.data.redis.core.convert.ConversionTestEntities.Size;
|
||||
import org.springframework.data.redis.core.convert.ConversionTestEntities.TaVeren;
|
||||
import org.springframework.data.redis.core.convert.ConversionTestEntities.TheWheelOfTime;
|
||||
import org.springframework.data.redis.core.index.GeoIndexed;
|
||||
import org.springframework.data.redis.core.index.IndexConfiguration;
|
||||
import org.springframework.data.redis.core.index.Indexed;
|
||||
import org.springframework.data.redis.core.index.SimpleIndexDefinition;
|
||||
@@ -61,6 +65,8 @@ import org.springframework.data.util.ClassTypeInformation;
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class PathIndexResolverUnitTests {
|
||||
|
||||
public @Rule ExpectedException exception = ExpectedException.none();
|
||||
|
||||
IndexConfiguration indexConfig;
|
||||
PathIndexResolver indexResolver;
|
||||
|
||||
@@ -514,6 +520,68 @@ public class PathIndexResolverUnitTests {
|
||||
new SimpleIndexedPropertyValue(IndexedOnPrimitiveArrayField.class.getName(), "values", 3)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-533
|
||||
*/
|
||||
@Test
|
||||
public void resolveGeoIndexShouldMapNameCorrectly() {
|
||||
|
||||
when(propertyMock.isMap()).thenReturn(true);
|
||||
when(propertyMock.isAnnotationPresent(eq(GeoIndexed.class))).thenReturn(true);
|
||||
when(propertyMock.findAnnotation(eq(GeoIndexed.class))).thenReturn(createGeoIndexedInstance());
|
||||
|
||||
IndexedData index = resolve("location", new Point(1D, 2D));
|
||||
|
||||
assertThat(index.getIndexName(), is("location"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-533
|
||||
*/
|
||||
@Test
|
||||
public void resolveGeoIndexShouldMapNameForNestedPropertyCorrectly() {
|
||||
|
||||
when(propertyMock.isMap()).thenReturn(true);
|
||||
when(propertyMock.isAnnotationPresent(eq(GeoIndexed.class))).thenReturn(true);
|
||||
when(propertyMock.findAnnotation(eq(GeoIndexed.class))).thenReturn(createGeoIndexedInstance());
|
||||
|
||||
IndexedData index = resolve("property.location", new Point(1D, 2D));
|
||||
|
||||
assertThat(index.getIndexName(), is("property:location"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-533
|
||||
*/
|
||||
@Test
|
||||
public void resolveGeoIndexOnPointField() {
|
||||
|
||||
GeoIndexedOnPoint source = new GeoIndexedOnPoint();
|
||||
source.location = new Point(1D, 2D);
|
||||
|
||||
Set<IndexedData> indexes = indexResolver.resolveIndexesFor(ClassTypeInformation.from(GeoIndexedOnPoint.class),
|
||||
source);
|
||||
|
||||
assertThat(indexes.size(), is(1));
|
||||
assertThat(indexes, IsCollectionContaining.<IndexedData> hasItems(
|
||||
new GeoIndexedPropertyValue(GeoIndexedOnPoint.class.getName(), "location", source.location)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-533
|
||||
*/
|
||||
@Test
|
||||
public void resolveGeoIndexOnArrayFieldThrowsError() {
|
||||
|
||||
exception.expect(IllegalArgumentException.class);
|
||||
exception.expectMessage("GeoIndexed property needs to be of type Point or GeoLocation");
|
||||
|
||||
GeoIndexedOnArray source = new GeoIndexedOnArray();
|
||||
source.location = new double[] { 10D, 20D };
|
||||
|
||||
indexResolver.resolveIndexesFor(ClassTypeInformation.from(GeoIndexedOnArray.class), source);
|
||||
}
|
||||
|
||||
private IndexedData resolve(String path, Object value) {
|
||||
|
||||
Set<IndexedData> data = indexResolver.resolveIndex(KEYSPACE_PERSON, path, propertyMock, value);
|
||||
@@ -534,7 +602,16 @@ public class PathIndexResolverUnitTests {
|
||||
public Class<? extends Annotation> annotationType() {
|
||||
return Indexed.class;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private GeoIndexed createGeoIndexedInstance() {
|
||||
|
||||
return new GeoIndexed() {
|
||||
@Override
|
||||
public Class<? extends Annotation> annotationType() {
|
||||
return GeoIndexed.class;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -553,4 +630,12 @@ public class PathIndexResolverUnitTests {
|
||||
@Indexed Map<String, String> values;
|
||||
}
|
||||
|
||||
static class GeoIndexedOnPoint {
|
||||
@GeoIndexed Point location;
|
||||
}
|
||||
|
||||
static class GeoIndexedOnArray {
|
||||
@GeoIndexed double[] location;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -51,8 +51,8 @@ public class RedisRepositoryClusterIntegrationTests extends RedisRepositoryInteg
|
||||
|
||||
@Configuration
|
||||
@EnableRedisRepositories(considerNestedRepositories = true, indexConfiguration = MyIndexConfiguration.class,
|
||||
keyspaceConfiguration = MyKeyspaceConfiguration.class,
|
||||
includeFilters = { @ComponentScan.Filter(type = FilterType.REGEX, pattern = ".*PersonRepository") })
|
||||
keyspaceConfiguration = MyKeyspaceConfiguration.class, includeFilters = {
|
||||
@ComponentScan.Filter(type = FilterType.REGEX, pattern = ".*PersonRepository|.*CityRepository") })
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -15,9 +15,7 @@
|
||||
*/
|
||||
package org.springframework.data.redis.repository;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.hamcrest.collection.IsCollectionWithSize.*;
|
||||
import static org.hamcrest.collection.IsIterableContainingInAnyOrder.*;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.io.Serializable;
|
||||
@@ -34,13 +32,18 @@ import org.springframework.data.annotation.Reference;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.geo.Distance;
|
||||
import org.springframework.data.geo.Metrics;
|
||||
import org.springframework.data.geo.Point;
|
||||
import org.springframework.data.keyvalue.core.KeyValueTemplate;
|
||||
import org.springframework.data.redis.core.RedisHash;
|
||||
import org.springframework.data.redis.core.convert.KeyspaceConfiguration;
|
||||
import org.springframework.data.redis.core.index.GeoIndexed;
|
||||
import org.springframework.data.redis.core.index.IndexConfiguration;
|
||||
import org.springframework.data.redis.core.index.IndexDefinition;
|
||||
import org.springframework.data.redis.core.index.Indexed;
|
||||
import org.springframework.data.redis.core.index.SimpleIndexDefinition;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.data.repository.PagingAndSortingRepository;
|
||||
|
||||
/**
|
||||
@@ -52,6 +55,7 @@ import org.springframework.data.repository.PagingAndSortingRepository;
|
||||
public abstract class RedisRepositoryIntegrationTestBase {
|
||||
|
||||
@Autowired PersonRepository repo;
|
||||
@Autowired CityRepository cityRepo;
|
||||
@Autowired KeyValueTemplate kvTemplate;
|
||||
|
||||
@Before
|
||||
@@ -318,6 +322,74 @@ public abstract class RedisRepositoryIntegrationTestBase {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-533
|
||||
*/
|
||||
@Test
|
||||
public void nearQueryShouldReturnResultsCorrectly() {
|
||||
|
||||
City palermo = new City();
|
||||
palermo.location = new Point(13.361389D, 38.115556D);
|
||||
|
||||
City catania = new City();
|
||||
catania.location = new Point(15.087269D, 37.502669D);
|
||||
|
||||
cityRepo.save(Arrays.asList(palermo, catania));
|
||||
|
||||
List<City> result = cityRepo.findByLocationNear(new Point(15D, 37D), new Distance(200, Metrics.KILOMETERS));
|
||||
assertThat(result, hasItems(palermo, catania));
|
||||
|
||||
result = cityRepo.findByLocationNear(new Point(15D, 37D), new Distance(100, Metrics.KILOMETERS));
|
||||
assertThat(result, hasItems(catania));
|
||||
assertThat(result, not(hasItems(palermo)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-533
|
||||
*/
|
||||
@Test
|
||||
public void nearQueryShouldFindNothingIfOutOfRange() {
|
||||
|
||||
City palermo = new City();
|
||||
palermo.location = new Point(13.361389D, 38.115556D);
|
||||
|
||||
City catania = new City();
|
||||
catania.location = new Point(15.087269D, 37.502669D);
|
||||
|
||||
cityRepo.save(Arrays.asList(palermo, catania));
|
||||
|
||||
List<City> result = cityRepo.findByLocationNear(new Point(15D, 37D), new Distance(10, Metrics.KILOMETERS));
|
||||
assertThat(result, is(empty()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-533
|
||||
*/
|
||||
@Test
|
||||
public void nearQueryShouldReturnResultsCorrectlyOnNestedProperty() {
|
||||
|
||||
City palermo = new City();
|
||||
palermo.location = new Point(13.361389D, 38.115556D);
|
||||
|
||||
City catania = new City();
|
||||
catania.location = new Point(15.087269D, 37.502669D);
|
||||
|
||||
Person p1 = new Person("foo", "bar");
|
||||
p1.hometown = palermo;
|
||||
|
||||
Person p2 = new Person("two", "two");
|
||||
p2.hometown = catania;
|
||||
|
||||
repo.save(Arrays.asList(p1, p2));
|
||||
|
||||
List<Person> result = repo.findByHometownLocationNear(new Point(15D, 37D), new Distance(200, Metrics.KILOMETERS));
|
||||
assertThat(result, hasItems(p1, p2));
|
||||
|
||||
result = repo.findByHometownLocationNear(new Point(15D, 37D), new Distance(100, Metrics.KILOMETERS));
|
||||
assertThat(result, hasItems(p2));
|
||||
assertThat(result, not(hasItems(p1)));
|
||||
}
|
||||
|
||||
public static interface PersonRepository extends PagingAndSortingRepository<Person, String> {
|
||||
|
||||
List<Person> findByFirstname(String firstname);
|
||||
@@ -337,6 +409,13 @@ public abstract class RedisRepositoryIntegrationTestBase {
|
||||
List<Person> findTop2ByLastname(String lastname);
|
||||
|
||||
Page<Person> findBy(Pageable page);
|
||||
|
||||
List<Person> findByHometownLocationNear(Point point, Distance distance);
|
||||
}
|
||||
|
||||
public static interface CityRepository extends CrudRepository<City, String> {
|
||||
|
||||
List<City> findByLocationNear(Point point, Distance distance);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -373,6 +452,7 @@ public abstract class RedisRepositoryIntegrationTestBase {
|
||||
@Indexed String firstname;
|
||||
String lastname;
|
||||
@Reference City city;
|
||||
City hometown;
|
||||
|
||||
public Person() {}
|
||||
|
||||
@@ -460,9 +540,12 @@ public abstract class RedisRepositoryIntegrationTestBase {
|
||||
}
|
||||
|
||||
public static class City {
|
||||
|
||||
@Id String id;
|
||||
String name;
|
||||
|
||||
@GeoIndexed Point location;
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
|
||||
@@ -35,8 +35,8 @@ public class RedisRepositoryIntegrationTests extends RedisRepositoryIntegrationT
|
||||
|
||||
@Configuration
|
||||
@EnableRedisRepositories(considerNestedRepositories = true, indexConfiguration = MyIndexConfiguration.class,
|
||||
keyspaceConfiguration = MyKeyspaceConfiguration.class,
|
||||
includeFilters = { @ComponentScan.Filter(type = FilterType.REGEX, pattern = ".*PersonRepository") })
|
||||
keyspaceConfiguration = MyKeyspaceConfiguration.class, includeFilters = {
|
||||
@ComponentScan.Filter(type = FilterType.REGEX, pattern = ".*PersonRepository|.*CityRepository") })
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -16,15 +16,26 @@
|
||||
package org.springframework.data.redis.repository.query;
|
||||
|
||||
import static org.hamcrest.collection.IsCollectionWithSize.*;
|
||||
import static org.hamcrest.core.Is.*;
|
||||
import static org.hamcrest.core.IsCollectionContaining.*;
|
||||
import static org.hamcrest.core.IsNull.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.mockito.Mock;
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.geo.Box;
|
||||
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.geo.Shape;
|
||||
import org.springframework.data.keyvalue.core.query.KeyValueQuery;
|
||||
import org.springframework.data.redis.core.convert.ConversionTestEntities;
|
||||
import org.springframework.data.redis.core.convert.ConversionTestEntities.Person;
|
||||
import org.springframework.data.redis.repository.query.RedisOperationChain.PathAndValue;
|
||||
import org.springframework.data.repository.Repository;
|
||||
import org.springframework.data.repository.core.RepositoryMetadata;
|
||||
@@ -37,6 +48,8 @@ import org.springframework.data.repository.query.parser.PartTree;
|
||||
*/
|
||||
public class RedisQueryCreatorUnitTests {
|
||||
|
||||
public @Rule ExpectedException exception = ExpectedException.none();
|
||||
|
||||
private @Mock RepositoryMetadata metadataMock;
|
||||
|
||||
/**
|
||||
@@ -61,8 +74,8 @@ public class RedisQueryCreatorUnitTests {
|
||||
public void findByMultipleSimpleProperties() throws SecurityException, NoSuchMethodException {
|
||||
|
||||
RedisQueryCreator creator = createQueryCreatorForMethodWithArgs(
|
||||
SampleRepository.class.getMethod("findByFirstnameAndAge", String.class, Integer.class), new Object[] {
|
||||
"eddard", 43 });
|
||||
SampleRepository.class.getMethod("findByFirstnameAndAge", String.class, Integer.class),
|
||||
new Object[] { "eddard", 43 });
|
||||
|
||||
KeyValueQuery<RedisOperationChain> query = creator.createQuery();
|
||||
|
||||
@@ -78,8 +91,8 @@ public class RedisQueryCreatorUnitTests {
|
||||
public void findByMultipleSimplePropertiesUsingOr() throws SecurityException, NoSuchMethodException {
|
||||
|
||||
RedisQueryCreator creator = createQueryCreatorForMethodWithArgs(
|
||||
SampleRepository.class.getMethod("findByAgeOrFirstname", Integer.class, String.class), new Object[] { 43,
|
||||
"eddard" });
|
||||
SampleRepository.class.getMethod("findByAgeOrFirstname", Integer.class, String.class),
|
||||
new Object[] { 43, "eddard" });
|
||||
|
||||
KeyValueQuery<RedisOperationChain> query = creator.createQuery();
|
||||
|
||||
@@ -88,21 +101,127 @@ public class RedisQueryCreatorUnitTests {
|
||||
assertThat(query.getCritieria().getOrSismember(), hasItem(new PathAndValue("firstname", "eddard")));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-533
|
||||
*/
|
||||
@Test
|
||||
public void findWithinCircle() throws SecurityException, NoSuchMethodException {
|
||||
|
||||
RedisQueryCreator creator = createQueryCreatorForMethodWithArgs(
|
||||
SampleRepository.class.getMethod("findByLocationWithin", Circle.class),
|
||||
new Object[] { new Circle(new Point(1, 2), new Distance(200, Metrics.KILOMETERS)) });
|
||||
|
||||
KeyValueQuery<RedisOperationChain> query = creator.createQuery();
|
||||
|
||||
assertThat(query.getCritieria().getNear(), is(notNullValue()));
|
||||
assertThat(query.getCritieria().getNear().getPoint(), is(new Point(1, 2)));
|
||||
assertThat(query.getCritieria().getNear().getDistance(), is(new Distance(200, Metrics.KILOMETERS)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-533
|
||||
*/
|
||||
@Test
|
||||
public void findNearWithPointAndDistance() throws SecurityException, NoSuchMethodException {
|
||||
|
||||
RedisQueryCreator creator = createQueryCreatorForMethodWithArgs(
|
||||
SampleRepository.class.getMethod("findByLocationNear", Point.class, Distance.class),
|
||||
new Object[] { new Point(1, 2), new Distance(200, Metrics.KILOMETERS) });
|
||||
|
||||
KeyValueQuery<RedisOperationChain> query = creator.createQuery();
|
||||
|
||||
assertThat(query.getCritieria().getNear(), is(notNullValue()));
|
||||
assertThat(query.getCritieria().getNear().getPoint(), is(new Point(1, 2)));
|
||||
assertThat(query.getCritieria().getNear().getDistance(), is(new Distance(200, Metrics.KILOMETERS)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-533
|
||||
*/
|
||||
@Test
|
||||
public void findNearWithPointAndNumericValueDefaultsToKilometers() throws SecurityException, NoSuchMethodException {
|
||||
|
||||
RedisQueryCreator creator = createQueryCreatorForMethodWithArgs(
|
||||
SampleRepository.class.getMethod("findByLocationNear", Shape.class, Object.class),
|
||||
new Object[] { new Point(1, 2), 200F });
|
||||
|
||||
KeyValueQuery<RedisOperationChain> query = creator.createQuery();
|
||||
|
||||
assertThat(query.getCritieria().getNear(), is(notNullValue()));
|
||||
assertThat(query.getCritieria().getNear().getPoint(), is(new Point(1, 2)));
|
||||
assertThat(query.getCritieria().getNear().getDistance(), is(new Distance(200, Metrics.KILOMETERS)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-533
|
||||
*/
|
||||
@Test
|
||||
public void findNearWithInvalidShapeParameter() throws SecurityException, NoSuchMethodException {
|
||||
|
||||
exception.expect(InvalidDataAccessApiUsageException.class);
|
||||
exception.expectMessage("Expected to find a Circle or Point/Distance");
|
||||
|
||||
RedisQueryCreator creator = createQueryCreatorForMethodWithArgs(
|
||||
SampleRepository.class.getMethod("findByLocationNear", Shape.class, Object.class),
|
||||
new Object[] { new Box(new Point(0, 0), new Point(1, 1)), 200F });
|
||||
|
||||
creator.createQuery();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-533
|
||||
*/
|
||||
@Test
|
||||
public void findNearWithInvalidDistanceParameter() throws SecurityException, NoSuchMethodException {
|
||||
|
||||
exception.expect(InvalidDataAccessApiUsageException.class);
|
||||
exception.expectMessage("Expected to find Distance or Numeric value");
|
||||
|
||||
RedisQueryCreator creator = createQueryCreatorForMethodWithArgs(
|
||||
SampleRepository.class.getMethod("findByLocationNear", Shape.class, Object.class),
|
||||
new Object[] { new Point(0, 0), "200" });
|
||||
|
||||
creator.createQuery();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-533
|
||||
*/
|
||||
@Test
|
||||
public void findNearWithMissingDistanceParameter() throws SecurityException, NoSuchMethodException {
|
||||
|
||||
exception.expect(InvalidDataAccessApiUsageException.class);
|
||||
exception.expectMessage("Are you missing a parameter?");
|
||||
|
||||
RedisQueryCreator creator = createQueryCreatorForMethodWithArgs(
|
||||
SampleRepository.class.getMethod("findByLocationNear", Shape.class), new Object[] { new Point(0, 0) });
|
||||
|
||||
creator.createQuery();
|
||||
}
|
||||
|
||||
private RedisQueryCreator createQueryCreatorForMethodWithArgs(Method method, Object[] args) {
|
||||
|
||||
PartTree partTree = new PartTree(method.getName(), method.getReturnType());
|
||||
RedisQueryCreator creator = new RedisQueryCreator(partTree, new ParametersParameterAccessor(new DefaultParameters(
|
||||
method), args));
|
||||
RedisQueryCreator creator = new RedisQueryCreator(partTree,
|
||||
new ParametersParameterAccessor(new DefaultParameters(method), args));
|
||||
|
||||
return creator;
|
||||
}
|
||||
|
||||
private interface SampleRepository extends Repository<ConversionTestEntities.Person, String> {
|
||||
private interface SampleRepository extends Repository<Person, String> {
|
||||
|
||||
ConversionTestEntities.Person findByFirstname(String firstname);
|
||||
Person findByFirstname(String firstname);
|
||||
|
||||
ConversionTestEntities.Person findByFirstnameAndAge(String firstname, Integer age);
|
||||
Person findByFirstnameAndAge(String firstname, Integer age);
|
||||
|
||||
ConversionTestEntities.Person findByAgeOrFirstname(Integer age, String firstname);
|
||||
Person findByAgeOrFirstname(Integer age, String firstname);
|
||||
|
||||
Person findByLocationWithin(Circle circle);
|
||||
|
||||
Person findByLocationNear(Point point, Distance distance);
|
||||
|
||||
Person findByLocationNear(Shape point, Object distance);
|
||||
|
||||
Person findByLocationNear(Shape point);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user