DATAGRAPH-294 derived finder methods for numerically indexed values
This commit is contained in:
@@ -99,4 +99,6 @@ public interface Neo4jPersistentProperty extends PersistentProperty<Neo4jPersist
|
||||
Class<?> getTargetType();
|
||||
|
||||
boolean isTargetTypeEnforced();
|
||||
|
||||
boolean isIndexedNumerically();
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty;
|
||||
import org.springframework.data.neo4j.support.Neo4jTemplate;
|
||||
import org.springframework.data.repository.query.Parameter;
|
||||
import org.springframework.data.repository.query.parser.Part;
|
||||
import org.neo4j.index.lucene.ValueContext;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@@ -73,7 +74,8 @@ class StartClause {
|
||||
if (shouldRenderQuery()) {
|
||||
result.put(firstParam, renderQuery(values));
|
||||
} else {
|
||||
result.put(firstParam, IteratorUtil.first(values.values()));
|
||||
Object value=IteratorUtil.first(values.values());
|
||||
result.put(firstParam, value);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -102,6 +104,7 @@ class StartClause {
|
||||
}
|
||||
|
||||
private Object convertIfNecessary(Neo4jTemplate template, Object value, Neo4jPersistentProperty property) {
|
||||
if (property.isIndexedNumerically()) return new ValueContext(value).indexNumeric();
|
||||
if (property.isNeo4jPropertyType() && property.isNeo4jPropertyValue(value)) return value;
|
||||
|
||||
PropertyConverter converter = new PropertyConverter(template.getConversionService(), property);
|
||||
|
||||
@@ -225,22 +225,33 @@ class Neo4jPersistentPropertyImpl extends AbstractPersistentProperty<Neo4jPersis
|
||||
return isNeo4jEntityType;
|
||||
}
|
||||
|
||||
public static boolean isNumeric(final Class<?> fieldType) {
|
||||
return (fieldType.isPrimitive() && !fieldType.equals(boolean.class) && !fieldType.equals(void.class))
|
||||
|| fieldType.equals(Character.class)
|
||||
|| (fieldType.getName().startsWith("java.lang") && Number.class.isAssignableFrom(fieldType));
|
||||
}
|
||||
|
||||
public boolean isIndexedNumerically() {
|
||||
if (!isIndexed() || !getIndexInfo().isNumeric()) return false;
|
||||
return isNumeric(getType()) || isNumeric(getPropertyType()) ||
|
||||
(getType().isArray() && !getType().getComponentType().isArray() && isNumeric(getType().getComponentType()));
|
||||
}
|
||||
|
||||
private static boolean isNeo4jPropertyType(final Class<?> fieldType) {
|
||||
// todo: add array support
|
||||
return fieldType.isPrimitive()
|
||||
|| fieldType.equals(String.class)
|
||||
|| fieldType.equals(Character.class)
|
||||
return fieldType.equals(String.class)
|
||||
|| fieldType.equals(Boolean.class)
|
||||
|| (fieldType.getName().startsWith("java.lang") && Number.class.isAssignableFrom(fieldType))
|
||||
|| fieldType.equals(boolean.class)
|
||||
|| isNumeric(fieldType)
|
||||
|| (fieldType.isArray() && !fieldType.getComponentType().isArray() && isNeo4jPropertyType(fieldType.getComponentType()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNeo4jPropertyValue(Object value) {
|
||||
if (value == null || value.getClass().isArray()) {
|
||||
return false;
|
||||
}
|
||||
return isNeo4jPropertyType(value.getClass());
|
||||
if (value == null || value.getClass().isArray()) {
|
||||
return false;
|
||||
}
|
||||
return isNeo4jPropertyType(value.getClass());
|
||||
}
|
||||
|
||||
public boolean isSyntheticField() {
|
||||
@@ -336,8 +347,8 @@ class Neo4jPersistentPropertyImpl extends AbstractPersistentProperty<Neo4jPersis
|
||||
*/
|
||||
@Deprecated
|
||||
public boolean isReallyTransient() {
|
||||
return Modifier.isTransient(field.getModifiers()) || isAnnotationPresent(Transient.class) || isAnnotationPresent("javax.persistence.Transient");
|
||||
}
|
||||
return Modifier.isTransient(field.getModifiers()) || isAnnotationPresent(Transient.class) || isAnnotationPresent("javax.persistence.Transient");
|
||||
}
|
||||
|
||||
private boolean isAnnotationPresent(String className) {
|
||||
for (Class<? extends Annotation> annotationType : annotations.keySet()) {
|
||||
|
||||
@@ -24,10 +24,7 @@ import org.neo4j.test.ImpermanentGraphDatabase;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.neo4j.annotation.Fetch;
|
||||
import org.springframework.data.neo4j.annotation.GraphId;
|
||||
import org.springframework.data.neo4j.annotation.NodeEntity;
|
||||
import org.springframework.data.neo4j.annotation.RelatedTo;
|
||||
import org.springframework.data.neo4j.annotation.*;
|
||||
import org.springframework.data.neo4j.config.EnableNeo4jRepositories;
|
||||
import org.springframework.data.neo4j.config.Neo4jConfiguration;
|
||||
import org.springframework.data.neo4j.support.Neo4jTemplate;
|
||||
@@ -59,6 +56,20 @@ class Ingredient {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
@NodeEntity
|
||||
class Dish {
|
||||
@GraphId
|
||||
Long id;
|
||||
|
||||
@Indexed(unique = true) int number;
|
||||
|
||||
Dish() {
|
||||
}
|
||||
|
||||
Dish(int number) {
|
||||
this.number = number;
|
||||
}
|
||||
}
|
||||
|
||||
@NodeEntity
|
||||
class CookBook {
|
||||
@@ -103,6 +114,10 @@ class Recipe {
|
||||
}
|
||||
}
|
||||
|
||||
interface DishRepository extends GraphRepository<Dish> {
|
||||
Dish findByNumber(int number);
|
||||
}
|
||||
|
||||
interface RecipeRepository extends GraphRepository<Recipe> {
|
||||
Set<Recipe> findById(long id);
|
||||
|
||||
@@ -127,6 +142,8 @@ interface RecipeRepository extends GraphRepository<Recipe> {
|
||||
@ContextConfiguration
|
||||
public class DerivedFinderTests {
|
||||
|
||||
private Dish dish;
|
||||
|
||||
@Configuration
|
||||
@EnableNeo4jRepositories
|
||||
static class TestConfig extends Neo4jConfiguration {
|
||||
@@ -147,6 +164,9 @@ public class DerivedFinderTests {
|
||||
@Autowired
|
||||
private RecipeRepository recipeRepository;
|
||||
|
||||
@Autowired
|
||||
private DishRepository dishRepository;
|
||||
|
||||
private Ingredient fish, spice, oliveOil, pear;
|
||||
|
||||
private CookBook nakedChef, baking101;
|
||||
@@ -172,6 +192,7 @@ public class DerivedFinderTests {
|
||||
recipeRepository.save(new Recipe("The Colonel", "fried chicken", null, spice, null));
|
||||
recipeRepository.save(new Recipe("Jamie", "pesto", oliveOil, null, nakedChef));
|
||||
focaccia = recipeRepository.save(new Recipe("Hugh", "focaccia", oliveOil, null, baking101));
|
||||
dish = dishRepository.save(new Dish(100));
|
||||
transaction.success();
|
||||
} finally {
|
||||
transaction.finish();
|
||||
@@ -250,4 +271,10 @@ public class DerivedFinderTests {
|
||||
|
||||
assertThat(single(recipes).title, is(equalTo("pesto")));
|
||||
}
|
||||
@Test
|
||||
public void shouldFindUsingIndexedNumericValue() throws Exception {
|
||||
Dish foundDish = dishRepository.findByNumber(100);
|
||||
|
||||
assertThat(foundDish.number, is(equalTo(dish.number)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,8 @@ import java.util.concurrent.TimeUnit;
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.neo4j.index.lucene.ValueContext;
|
||||
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration
|
||||
@TestExecutionListeners({CleanContextCacheTestExecutionListener.class, DependencyInjectionTestExecutionListener.class, TransactionalTestExecutionListener.class})
|
||||
@@ -57,6 +59,8 @@ public class DerivedFinderMethodTest {
|
||||
Long id;
|
||||
@Indexed
|
||||
String firstName;
|
||||
@Indexed
|
||||
int number;
|
||||
@Indexed
|
||||
String lastName;
|
||||
|
||||
@@ -299,6 +303,13 @@ public class DerivedFinderMethodTest {
|
||||
param.getTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindByNumericIndexedField() throws Exception {
|
||||
assertRepositoryQueryMethod(ThingRepository.class, "findByNumber", new Object[]{10},
|
||||
"START `thing`=node:`Thing`(`number`={0})",
|
||||
ValueContext.numeric(10));
|
||||
}
|
||||
|
||||
private void assertRepositoryQueryMethod(Class<ThingRepository> repositoryClass, String methodName, Object[] paramValues, String expectedQuery, Object...expectedParam) {
|
||||
Method method = methodFor(repositoryClass, methodName);
|
||||
DerivedCypherRepositoryQuery derivedCypherRepositoryQuery = new DerivedCypherRepositoryQuery(ctx, new GraphQueryMethod(method, new DefaultRepositoryMetadata(repositoryClass), null, ctx), template);
|
||||
@@ -310,7 +321,11 @@ public class DerivedFinderMethodTest {
|
||||
assertEquals(expectedQuery,query.substring(query.indexOf(firstWord)).substring(0,expectedQuery.length()));
|
||||
assertEquals(expectedParam.length,params.size());
|
||||
for (int i = 0; i < expectedParam.length; i++) {
|
||||
assertEquals(expectedParam[i],params.get(String.valueOf(i)));
|
||||
if (expectedParam[i] instanceof ValueContext) {
|
||||
assertEquals(((ValueContext)expectedParam[i]).getValue(),((ValueContext)params.get(String.valueOf(i))).getValue());
|
||||
} else {
|
||||
assertEquals(expectedParam[i],params.get(String.valueOf(i)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,35 +21,36 @@ import java.util.Collection;
|
||||
import java.util.Date;
|
||||
|
||||
public interface ThingRepository extends GraphRepository<DerivedFinderMethodTest.Thing> {
|
||||
public DerivedFinderMethodTest.Thing findByFirstNameAndLastName(String firstName, String lastName);
|
||||
public DerivedFinderMethodTest.Thing findByFirstName(String firstName);
|
||||
public DerivedFinderMethodTest.Thing findByDescription(String firstName);
|
||||
public DerivedFinderMethodTest.Thing findByDescriptionAndFirstName(String description,String firstName);
|
||||
public DerivedFinderMethodTest.Thing findByFirstNameAndDescription(String firstName,String description);
|
||||
public DerivedFinderMethodTest.Thing findByAge(int age);
|
||||
public DerivedFinderMethodTest.Thing findByAgeAndFirstName(int age,String firstName);
|
||||
public DerivedFinderMethodTest.Thing findByFirstNameLike(String firstName);
|
||||
public DerivedFinderMethodTest.Thing findByFirstNameContains(String firstName);
|
||||
public DerivedFinderMethodTest.Thing findByFirstNameEndsWith(String firstName);
|
||||
public DerivedFinderMethodTest.Thing findByFirstNameStartsWith(String firstName);
|
||||
|
||||
public DerivedFinderMethodTest.Thing findByName(String name);
|
||||
public DerivedFinderMethodTest.Thing findByNameStartsWith(String name);
|
||||
public DerivedFinderMethodTest.Thing findByNameEndsWith(String name);
|
||||
public DerivedFinderMethodTest.Thing findByNameContains(String name);
|
||||
|
||||
public DerivedFinderMethodTest.Thing findByNameLike(String name);
|
||||
public DerivedFinderMethodTest.Thing findByNameNotLike(String name);
|
||||
public DerivedFinderMethodTest.Thing findByNameMatches(String name);
|
||||
public DerivedFinderMethodTest.Thing findByTaggedIsTrue();
|
||||
public DerivedFinderMethodTest.Thing findByTaggedIsFalse();
|
||||
|
||||
public DerivedFinderMethodTest.Thing findByNameExists();
|
||||
public DerivedFinderMethodTest.Thing findByNameIn(Collection<String> values);
|
||||
public DerivedFinderMethodTest.Thing findByNameNotIn(Collection<String> values);
|
||||
|
||||
public DerivedFinderMethodTest.Thing findByBornBefore(Date date);
|
||||
public DerivedFinderMethodTest.Thing findByBornAfter(Date date);
|
||||
public DerivedFinderMethodTest.Thing findById(long id);
|
||||
public DerivedFinderMethodTest.Thing findByOwnerId(long id);
|
||||
DerivedFinderMethodTest.Thing findByFirstNameAndLastName(String firstName, String lastName);
|
||||
DerivedFinderMethodTest.Thing findByFirstName(String firstName);
|
||||
DerivedFinderMethodTest.Thing findByDescription(String firstName);
|
||||
DerivedFinderMethodTest.Thing findByDescriptionAndFirstName(String description,String firstName);
|
||||
DerivedFinderMethodTest.Thing findByFirstNameAndDescription(String firstName,String description);
|
||||
DerivedFinderMethodTest.Thing findByAge(int age);
|
||||
DerivedFinderMethodTest.Thing findByAgeAndFirstName(int age,String firstName);
|
||||
DerivedFinderMethodTest.Thing findByFirstNameLike(String firstName);
|
||||
DerivedFinderMethodTest.Thing findByFirstNameContains(String firstName);
|
||||
DerivedFinderMethodTest.Thing findByFirstNameEndsWith(String firstName);
|
||||
DerivedFinderMethodTest.Thing findByFirstNameStartsWith(String firstName);
|
||||
DerivedFinderMethodTest.Thing findByNumber(int number);
|
||||
|
||||
DerivedFinderMethodTest.Thing findByName(String name);
|
||||
DerivedFinderMethodTest.Thing findByNameStartsWith(String name);
|
||||
DerivedFinderMethodTest.Thing findByNameEndsWith(String name);
|
||||
DerivedFinderMethodTest.Thing findByNameContains(String name);
|
||||
|
||||
DerivedFinderMethodTest.Thing findByNameLike(String name);
|
||||
DerivedFinderMethodTest.Thing findByNameNotLike(String name);
|
||||
DerivedFinderMethodTest.Thing findByNameMatches(String name);
|
||||
DerivedFinderMethodTest.Thing findByTaggedIsTrue();
|
||||
DerivedFinderMethodTest.Thing findByTaggedIsFalse();
|
||||
|
||||
DerivedFinderMethodTest.Thing findByNameExists();
|
||||
DerivedFinderMethodTest.Thing findByNameIn(Collection<String> values);
|
||||
DerivedFinderMethodTest.Thing findByNameNotIn(Collection<String> values);
|
||||
|
||||
DerivedFinderMethodTest.Thing findByBornBefore(Date date);
|
||||
DerivedFinderMethodTest.Thing findByBornAfter(Date date);
|
||||
DerivedFinderMethodTest.Thing findById(long id);
|
||||
DerivedFinderMethodTest.Thing findByOwnerId(long id);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user