added gremlin REST addon, parameters for queries, updated documentation

This commit is contained in:
Michael Hunger
2011-07-25 16:10:45 +02:00
parent 85b05507d5
commit 63742c55f0
23 changed files with 158 additions and 390 deletions

View File

@@ -17,10 +17,13 @@
package org.springframework.data.neo4j.rest;
import org.neo4j.helpers.collection.MapUtil;
import org.springframework.data.neo4j.conversion.*;
import org.springframework.data.neo4j.support.query.QueryEngine;
import java.util.*;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* @author mh
@@ -42,11 +45,8 @@ public class RestCypherQueryEngine implements QueryEngine<Map<String,Object>> {
@Override
public QueryResult<Map<String, Object>> query(String statement, Map<String, Object> params) {
return executeStatement(statement);
}
private RestQueryResult executeStatement(String statement) {
final RequestResult requestResult = restRequest.get("ext/CypherPlugin/graphdb/execute_query", JsonHelper.createJsonFrom(Collections.singletonMap("query", statement)));
final String parametrizedStatement = QueryResultBuilder.replaceParams(statement, params);
final RequestResult requestResult = restRequest.get("ext/CypherPlugin/graphdb/execute_query", JsonHelper.createJsonFrom(MapUtil.map("query", parametrizedStatement)));
return new RestQueryResult(restRequest.toMap(requestResult),restGraphDatabase,resultConverter);
}

View File

@@ -22,7 +22,7 @@ import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.neo4j.core.GraphDatabase;
import org.springframework.data.neo4j.rest.RestGraphDatabase;
import org.springframework.data.neo4j.support.query.QueryOperationsTest;
import org.springframework.data.neo4j.support.query.QueryEngineTest;
import org.springframework.test.context.CleanContextCacheTestExecutionListener;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
@@ -39,7 +39,7 @@ import org.springframework.test.context.transaction.TransactionalTestExecutionLi
@ContextConfiguration(locations = {"classpath:org/springframework/data/neo4j/support/Neo4jGraphPersistenceTest-context.xml",
"classpath:RestTest-context.xml"})
@TestExecutionListeners({CleanContextCacheTestExecutionListener.class, DependencyInjectionTestExecutionListener.class, TransactionalTestExecutionListener.class})
public class RestQueryEngineTest extends QueryOperationsTest {
public class RestQueryEngineTest extends QueryEngineTest {
@Autowired
RestGraphDatabase restGraphDatabase;

View File

@@ -36,7 +36,7 @@ import java.lang.annotation.Target;
@Target({ElementType.FIELD,ElementType.METHOD})
public @interface Query {
/**
* @return Query to be executed %d will be replaced by the node-id of the current entity other placeholders by the given params
* @return Query to be executed %start will be replaced by the node-id of the current entity other placeholders (%name) by the given named params
*/
String value() default "";
@@ -46,7 +46,7 @@ public @interface Query {
Class<?> elementClass() default Object.class;
/**
* @return parameters that are replaced in the to the @see query-string
* @return tuple list of parameters that are replaced in the to the @see query-string {"name", value}
*/
String[] params() default {};
}

View File

@@ -46,7 +46,7 @@ public class QueryResultBuilder<T> implements QueryResult<T> {
public static String replaceParams(String statement, Map<String, Object> params) {
if (params==null || params.isEmpty()) return statement;
for (Map.Entry<String, Object> param : params.entrySet()) {
statement = statement.replaceAll(":"+param.getKey(),""+param.getValue());
statement = statement.replaceAll("%"+param.getKey()+"\\b",""+param.getValue());
}
return statement;
}

View File

@@ -19,6 +19,10 @@ package org.springframework.data.neo4j.core;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.traversal.TraversalDescription;
import org.springframework.data.neo4j.support.node.Neo4jNodeBacking;
import org.springframework.data.neo4j.support.query.CypherQueryExecutor;
import java.util.Map;
/**
* Interface introduced to objects annotated with &#64;NodeEntity by the {@link org.springframework.data.neo4j.support.node.Neo4jNodeBacking} aspect.
@@ -125,6 +129,11 @@ public interface NodeBacked extends GraphBacked<Node> {
<R extends RelationshipBacked> R getRelationshipTo(NodeBacked target, Class<R> relationshipClass, String type);
<T> Iterable<T> findAllByQuery(final String query, final Class<T> targetType,Map<String,Object> params);
Iterable<Map<String,Object>> findAllByQuery(final String query,Map<String,Object> params);
<T> T findByQuery(final String query, final Class<T> targetType,Map<String,Object> params);

View File

@@ -22,6 +22,7 @@ import org.springframework.data.neo4j.core.NodeBacked;
import org.springframework.data.neo4j.support.GenericTypeExtractor;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import static org.springframework.data.neo4j.support.DoReturn.doReturn;
@@ -48,13 +49,16 @@ public class QueryFieldAccessorFactory implements FieldAccessorFactory<NodeBacke
protected final Field field;
private final String query;
private Class<?> target;
protected String[] params;
protected String[] annotationParams;
private boolean iterableResult;
public QueryFieldAccessor(final Field field) {
this.field = field;
final Query query = field.getAnnotation(Query.class);
this.params = query.params();
this.annotationParams = query.params();
if ((this.annotationParams.length % 2) != 0) {
throw new IllegalArgumentException("Number of parameters has to be even to construct a parameter map");
}
this.query = query.value();
this.iterableResult = Iterable.class.isAssignableFrom(field.getType());
this.target = resolveTarget(query,field);
@@ -77,23 +81,23 @@ public class QueryFieldAccessorFactory implements FieldAccessorFactory<NodeBacke
@Override
public Object getValue(final NodeBacked nodeBacked) {
final String queryString = String.format(this.query, (Object[])createPlaceholderParams(nodeBacked));
return doReturn(executeQuery(nodeBacked, queryString));
return doReturn(executeQuery(nodeBacked, this.query, createPlaceholderParams(nodeBacked)));
}
private Object executeQuery(NodeBacked nodeBacked, String queryString) {
if (!iterableResult) return nodeBacked.findByQuery(queryString,this.target);
if (Map.class.isAssignableFrom(target)) return nodeBacked.findAllByQuery(queryString);
return nodeBacked.findAllByQuery(queryString, this.target);
private Object executeQuery(NodeBacked nodeBacked, String queryString, Map<String, Object> params) {
if (!iterableResult) return nodeBacked.findByQuery(queryString,this.target,params);
if (Map.class.isAssignableFrom(target)) return nodeBacked.findAllByQuery(queryString,params);
return nodeBacked.findAllByQuery(queryString, this.target,params);
}
private Object[] createPlaceholderParams(NodeBacked nodeBacked) {
if (params.length==0) return new Object[] {nodeBacked.getNodeId()};
final Object[] parameters = new Object[1 + this.params.length];
parameters[0]=nodeBacked.getNodeId();
System.arraycopy(this.params,0,parameters,1,this.params.length);
return parameters;
private Map<String, Object> createPlaceholderParams(NodeBacked nodeBacked) {
Map<String,Object> params=new HashMap<String, Object>();
params.put("start",nodeBacked.getNodeId());
if (annotationParams.length==0) return params;
for (int i = 0; i < annotationParams.length; i+=2) {
params.put(annotationParams[i],annotationParams[i+1]);
}
return params;
}
}
}

View File

@@ -19,9 +19,11 @@ package org.springframework.data.neo4j.fieldaccess;
import org.neo4j.graphdb.traversal.TraversalDescription;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.neo4j.annotation.GraphTraversal;
import org.springframework.data.neo4j.annotation.Query;
import org.springframework.data.neo4j.core.FieldTraversalDescriptionBuilder;
import org.springframework.data.neo4j.core.NodeBacked;
import org.springframework.data.neo4j.repository.DirectGraphRepositoryFactory;
import org.springframework.data.neo4j.support.GenericTypeExtractor;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
@@ -56,11 +58,19 @@ public class TraversalFieldAccessorFactory implements FieldAccessorFactory<NodeB
public TraversalFieldAccessor(final Field field) {
this.field = field;
final GraphTraversal graphEntityTraversal = field.getAnnotation(GraphTraversal.class);
this.target = graphEntityTraversal.elementClass();
this.target = resolveTarget(graphEntityTraversal,field);
this.params = graphEntityTraversal.params();
this.fieldTraversalDescriptionBuilder = createTraversalDescription(graphEntityTraversal);
}
private Class<? extends NodeBacked> resolveTarget(GraphTraversal graphTraversal, Field field) {
if (!graphTraversal.elementClass().equals(Object.class)) return graphTraversal.elementClass();
final Class<?> result = GenericTypeExtractor.resolveFieldType(field);
if (!NodeBacked.class.isAssignableFrom(result)) throw new IllegalArgumentException("The target result type of the traversal is no node entity: "+field);
return (Class<? extends NodeBacked>) result;
}
@Override
public boolean isWriteable(NodeBacked nodeBacked) {
return false;

View File

@@ -38,6 +38,7 @@ import org.springframework.util.Assert;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -150,19 +151,28 @@ public class GraphRepositoryFactory extends RepositoryFactorySupport {
private String prepareQuery(Object[] args) {
final Parameters parameters = getParameters();
Object[] resolvedParameters = resolveParameters(args,parameters.getBindableParameters());
String baseQuery = String.format(query, (Object[]) resolvedParameters);
String queryString = this.query;
if (parameters.hasSortParameter()) {
baseQuery = addSorting(baseQuery, (Sort) args[parameters.getSortIndex()]);
queryString = addSorting(queryString, (Sort) args[parameters.getSortIndex()]);
}
if (parameters.hasPageableParameter()) {
final Pageable pageable = getPageable(args);
if (pageable!=null) {
baseQuery = addSorting(baseQuery, pageable.getSort());
baseQuery = addPaging(baseQuery, pageable);
queryString = addSorting(queryString, pageable.getSort());
queryString = addPaging(queryString, pageable);
}
}
return baseQuery;
return queryString;
}
private Map<String, Object> resolveParams(Object[] parameters) {
Map<String,Object> params=new HashMap<String, Object>();
for (Parameter parameter : getParameters().getBindableParameters()) {
final Object value = parameters[parameter.getIndex()];
params.put(parameter.getName(),resolveParameter(value));
}
return params;
}
private Pageable getPageable(Object[] args) {
@@ -191,17 +201,6 @@ public class GraphRepositoryFactory extends RepositoryFactorySupport {
return result;
}
private Object[] resolveParameters(Object[] parameters, Parameters bindableParameters) {
final int paramCount = bindableParameters.getNumberOfParameters();
final Object[] result = new Object[paramCount];
for (int i = 0; i < paramCount; i++) {
final Parameter parameter = bindableParameters.getParameter(i);
final Object value = parameters[parameter.getIndex()];
result[i] = resolveParameter(value);
}
return result;
}
private Object resolveParameter(Object parameter) {
if (parameter instanceof NodeBacked) {
return ((NodeBacked)parameter).getNodeId();
@@ -251,24 +250,25 @@ public class GraphRepositoryFactory extends RepositoryFactorySupport {
@Override
public Object execute(Object[] parameters) {
Map<String, Object> params = queryMethod.resolveParams(parameters);
final String queryString = queryMethod.prepareQuery(parameters);
return dispatchQuery(queryString,queryMethod.getPageable(parameters));
return dispatchQuery(queryString,params,queryMethod.getPageable(parameters));
}
private Object dispatchQuery(String queryString, Pageable pageable) {
private Object dispatchQuery(String queryString, Map<String, Object> params, Pageable pageable) {
final QueryMethod.Type queryResultType = queryMethod.getType();
if (queryResultType== QueryMethod.Type.PAGING) {
return queryPaged(queryString,pageable);
return queryPaged(queryString,params,pageable);
}
if (iterableResult) {
if (compoundType.isAssignableFrom(Map.class)) return queryExecutor.queryForList(queryString);
return queryExecutor.query(queryString, queryMethod.getCompoundType());
if (compoundType.isAssignableFrom(Map.class)) return queryExecutor.queryForList(queryString,params);
return queryExecutor.query(queryString, queryMethod.getCompoundType(),params);
}
return queryExecutor.queryForObject(queryString, queryMethod.getReturnType());
return queryExecutor.queryForObject(queryString, queryMethod.getReturnType(),params);
}
private Object queryPaged(String queryString, Pageable pageable) {
final Iterable<?> result = queryExecutor.query(queryString, queryMethod.getCompoundType());
private Object queryPaged(String queryString, Map<String, Object> params, Pageable pageable) {
final Iterable<?> result = queryExecutor.query(queryString, queryMethod.getCompoundType(),params);
return createPage(result, pageable);
}

View File

@@ -31,6 +31,7 @@ import org.springframework.data.neo4j.annotation.RelationshipEntity;
import org.springframework.data.neo4j.annotation.RelatedTo;
import org.springframework.data.neo4j.annotation.GraphProperty;
import org.springframework.data.neo4j.annotation.GraphId;
import org.springframework.data.neo4j.annotation.Query;
import org.springframework.data.neo4j.annotation.RelatedToVia;
import org.springframework.data.neo4j.annotation.GraphTraversal;
@@ -71,6 +72,7 @@ public aspect Neo4jNodeBacking { // extends AbstractTypeAnnotatingMixinFields<No
declare @field: @RelatedToVia * (@Entity @NodeEntity(partial=true) *).*:@Transient;
declare @field: @GraphId * (@Entity @NodeEntity(partial=true) *).*:@Transient;
declare @field: @GraphTraversal * (@Entity @NodeEntity(partial=true) *).*:@Transient;
declare @field: @Query * (@Entity @NodeEntity(partial=true) *).*:@Transient;
@@ -183,19 +185,20 @@ public aspect Neo4jNodeBacking { // extends AbstractTypeAnnotatingMixinFields<No
final Traverser traverser = traversalDescription.traverse(this.getPersistentState());
return new NodeBackedNodeIterableWrapper<T>(traverser, targetType, Neo4jNodeBacking.aspectOf().graphDatabaseContext);
}
public <T> Iterable<T> NodeBacked.findAllByQuery(final String query, final Class<T> targetType) {
public <T> Iterable<T> NodeBacked.findAllByQuery(final String query, final Class<T> targetType, Map<String,Object> params) {
final CypherQueryExecutor executor = new CypherQueryExecutor(Neo4jNodeBacking.aspectOf().graphDatabaseContext);
return executor.query(query, targetType);
return executor.query(query, targetType,params);
}
public Iterable<Map<String,Object>> NodeBacked.findAllByQuery(final String query) {
public Iterable<Map<String,Object>> NodeBacked.findAllByQuery(final String query,Map<String,Object> params) {
final CypherQueryExecutor executor = new CypherQueryExecutor(Neo4jNodeBacking.aspectOf().graphDatabaseContext);
return executor.queryForList(query);
return executor.queryForList(query,params);
}
public <T> T NodeBacked.findByQuery(final String query, final Class<T> targetType) {
public <T> T NodeBacked.findByQuery(final String query, final Class<T> targetType,Map<String,Object> params) {
final CypherQueryExecutor executor = new CypherQueryExecutor(Neo4jNodeBacking.aspectOf().graphDatabaseContext);
return executor.queryForObject(query, targetType);
return executor.queryForObject(query, targetType,params);
}
public <S extends NodeBacked, E extends NodeBacked> Iterable<EntityPath<S,E>> NodeBacked.findAllPathsByTraversal(TraversalDescription traversalDescription) {

View File

@@ -20,7 +20,6 @@ import org.springframework.data.neo4j.support.GraphDatabaseContext;
import org.springframework.data.neo4j.support.conversion.EntityResultConverter;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
@@ -36,23 +35,19 @@ public class CypherQueryExecutor implements QueryOperations<Map<String,Object>>
queryEngine = new CypherQueryEngine(ctx.getGraphDatabaseService(), converter);
}
public Iterable<Map<String, Object>> queryForList(String statement, Map<String,Object>...params) {
public Iterable<Map<String, Object>> queryForList(String statement, Map<String,Object> params) {
return queryEngine.query(statement,mergeParams(params));
}
public <T> Iterable<T> query(String statement, Class<T> type, Map<String,Object>...params) {
public <T> Iterable<T> query(String statement, Class<T> type, Map<String,Object> params) {
return queryEngine.query(statement,mergeParams(params)).to(type);
}
public <T> T queryForObject(String statement, Class<T> type, Map<String,Object>...params) {
public <T> T queryForObject(String statement, Class<T> type, Map<String,Object> params) {
return (T) queryEngine.query(statement,mergeParams(params)).to(type).single();
}
private Map<String,Object> mergeParams(Map<String,Object>...params) {
if (params==null || params.length==0) return Collections.emptyMap();
Map<String,Object> result=new HashMap<String, Object>();
for (Map<String, Object> map : params) {
result.putAll(map);
}
return result;
private Map<String,Object> mergeParams(Map<String,Object> params) {
if (params==null) return Collections.emptyMap();
return params;
}
}

View File

@@ -23,9 +23,9 @@ import java.util.Map;
* @since 28.06.11
*/
public interface QueryOperations<R> {
Iterable<R> queryForList(String statement, Map<String,Object>...params);
Iterable<R> queryForList(String statement, Map<String,Object> params);
<T> Iterable<T> query(String statement, Class<T> type, Map<String,Object>...params);
<T> Iterable<T> query(String statement, Class<T> type, Map<String,Object> params);
<T> T queryForObject(String statement, Class<T> type, Map<String,Object>...params);
<T> T queryForObject(String statement, Class<T> type, Map<String,Object> params);
}

View File

@@ -66,16 +66,16 @@ public class Person {
@RelatedToVia(type = "knows", elementClass = Friendship.class)
private Iterable<Friendship> friendships;
@Query(value = "start person=(%d) match (person)<-[:boss]-(boss) return boss")
@Query(value = "start person=(%start) match (person)<-[:boss]-(boss) return boss")
private Person bossByQuery;
@Query(value = "start person=(%d) match (person)<-[:boss]-(boss) return boss.%s",params = "name")
@Query(value = "start person=(%start) match (person)<-[:boss]-(boss) return boss.%property",params = {"property","name"})
private String bossName;
@Query(value = "start person=(%d) match (person)<-[:persons]-(team)-[:persons]->(member) return member",elementClass = Person.class)
@Query(value = "start person=(%start) match (person)<-[:persons]-(team)-[:persons]->(member) return member",elementClass = Person.class)
private Iterable<Person> otherTeamMembers;
@Query(value = "start person=(%d) match (person)<-[:persons]-(team)-[:persons]->(member) return member.name, member.age")
@Query(value = "start person=(%start) match (person)<-[:persons]-(team)-[:persons]->(member) return member.name, member.age")
private Iterable<Map<String,Object>> otherTeamMemberData;
public String getBossName() {

View File

@@ -22,6 +22,7 @@ import org.springframework.data.domain.Sort;
import org.springframework.data.neo4j.annotation.Query;
import org.springframework.data.neo4j.repository.GraphRepository;
import org.springframework.data.neo4j.repository.NamedIndexRepository;
import org.springframework.data.repository.query.Param;
import java.util.Map;
@@ -31,19 +32,19 @@ import java.util.Map;
*/
public interface PersonRepository extends GraphRepository<Person>, NamedIndexRepository<Person> {
@Query("start team=(%d) match (team)-[:persons]->(member) return member")
Iterable<Person> findAllTeamMembers(Group team);
@Query("start team=(%team) match (team)-[:persons]->(member) return member")
Iterable<Person> findAllTeamMembers(@Param("team") Group team);
@Query("start team=(%d) match (team)-[:persons]->(member) return member.name,member.age")
Iterable<Map<String,Object>> findAllTeamMemberData(Group team);
@Query("start team=(%team) match (team)-[:persons]->(member) return member.name,member.age")
Iterable<Map<String,Object>> findAllTeamMemberData(@Param("team") Group team);
@Query("start person=(%d) match (boss)-[:boss]->(person) return boss")
Person findBoss(Person person);
@Query("start person=(%person) match (boss)-[:boss]->(person) return boss")
Person findBoss(@Param("person") Person person);
Group findTeam(Person person);
Group findTeam(@Param("person") Person person);
@Query("start team=(%d) match (team)-[:persons]->(member) return member")
Page<Person> findAllTeamMembersPaged(Pageable page, Group team);
@Query("start team=(%d) match (team)-[:persons]->(member) return member")
Iterable<Person> findAllTeamMembersSorted(Group team, Sort sort);
@Query("start team=(%team) match (team)-[:persons]->(member) return member")
Page<Person> findAllTeamMembersPaged(@Param("team") Group team, Pageable page);
@Query("start team=(%team) match (team)-[:persons]->(member) return member")
Iterable<Person> findAllTeamMembersSorted(@Param("team") Group team, Sort sort);
}

View File

@@ -93,21 +93,21 @@ public class GraphRepositoryTest {
@Transactional
public void testFindPaged() {
final PageRequest page = new PageRequest(0, 1, Sort.Direction.ASC, "member.name");
Page<Person> teamMemberPage1 = personRepository.findAllTeamMembersPaged(page, testTeam.sdg);
Page<Person> teamMemberPage1 = personRepository.findAllTeamMembersPaged(testTeam.sdg,page);
assertThat(teamMemberPage1, hasItem(testTeam.david));
}
@Test
@Transactional
public void testFindPagedDescending() {
final PageRequest page = new PageRequest(0, 2, Sort.Direction.DESC, "member.name");
Page<Person> teamMemberPage1 = personRepository.findAllTeamMembersPaged(page, testTeam.sdg);
Page<Person> teamMemberPage1 = personRepository.findAllTeamMembersPaged(testTeam.sdg,page);
assertEquals(asList(testTeam.michael, testTeam.emil), asCollection(teamMemberPage1));
assertThat(teamMemberPage1.isFirstPage(), is(true));
}
@Test
@Transactional
public void testFindPagedNull() {
Page<Person> teamMemberPage1 = personRepository.findAllTeamMembersPaged(null, testTeam.sdg);
Page<Person> teamMemberPage1 = personRepository.findAllTeamMembersPaged(testTeam.sdg,null);
assertEquals(asList(testTeam.michael, testTeam.emil,testTeam.david), asCollection(teamMemberPage1));
assertThat(teamMemberPage1.isFirstPage(), is(true));
assertThat(teamMemberPage1.isLastPage(), is(false));

View File

@@ -86,7 +86,7 @@ public class QueryEngineTest {
@Test
@Transactional
public void testQueryList() throws Exception {
final String queryString = "start person=(:michael,:david) return person.name, person.age";
final String queryString = "start person=(%michael,%david) return person.name, person.age";
final Collection<Map<String,Object>> result = IteratorUtil.asCollection(queryEngine.query(queryString, MapUtil.map("michael",idFor(michael), "david",idFor(testTeam.david))));
assertEquals(asList(testTeam.simpleRowFor(michael,"person"),testTeam.simpleRowFor(testTeam.david,"person")),result);
@@ -94,14 +94,14 @@ public class QueryEngineTest {
@Test
public void testQueryListOfTypeNode() throws Exception {
final String queryString = "start person=(name_index,name,\":name\") match (person) <-[:boss]- (boss) return boss";
final String queryString = "start person=(name_index,name,\"%name\") match (person) <-[:boss]- (boss) return boss";
final Collection<Node> result = IteratorUtil.asCollection(queryEngine.query(queryString, michaelsName()).to(Node.class));
assertEquals(asList(nodeFor(testTeam.emil)),result);
}
@Test
public void testQueryListOfTypePerson() throws Exception {
final String queryString = "start person=(name_index,name,\":name\") match (person) <-[:boss]- (boss) return boss";
final String queryString = "start person=(name_index,name,\"%name\") match (person) <-[:boss]- (boss) return boss";
final Collection<Person> result = IteratorUtil.asCollection(queryEngine.query(queryString, michaelsName()).to(Person.class, new EntityResultConverter(graphDatabaseContext)));
assertEquals(asList(testTeam.emil),result);
@@ -113,7 +113,7 @@ public class QueryEngineTest {
@Test
public void testQuerySingleOfTypePerson() throws Exception {
final String queryString = "start person=(name_index,name,\":name\") match (person) <-[:boss]- (boss) return boss";
final String queryString = "start person=(name_index,name,\"%name\") match (person) <-[:boss]- (boss) return boss";
final Person result = queryEngine.query(queryString, michaelsName()).to(Person.class, new EntityResultConverter<Map<String,Object>,Person>(graphDatabaseContext)).single();
assertEquals(testTeam.emil,result);
@@ -141,14 +141,14 @@ public class QueryEngineTest {
@Test
public void testQueryForObjectAsString() throws Exception {
final String queryString = "start person=(name_index,name,\":name\") match (person) <-[:persons]- (team) return team.name";
final String queryString = "start person=(name_index,name,\"%name\") match (person) <-[:persons]- (team) return team.name";
final String result = queryEngine.query(queryString, michaelsName()).to(String.class).single();
assertEquals(testTeam.sdg.getName(),result);
}
@Test
public void testQueryForObjectAsEnum() throws Exception {
final String queryString = "start person=(name_index,name,\":name\") return person.personality";
final String queryString = "start person=(name_index,name,\"%name\") return person.personality";
final Personality result = queryEngine.query(queryString, michaelsName()).to(Personality.class).single();
assertEquals(michael.getPersonality(),result);

View File

@@ -1,120 +0,0 @@
/**
* Copyright 2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.neo4j.support.query;
import org.junit.Before;
import org.junit.Test;
import org.junit.internal.matchers.IsCollectionContaining;
import org.junit.runner.RunWith;
import org.neo4j.helpers.collection.IteratorUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.neo4j.Group;
import org.springframework.data.neo4j.Person;
import org.springframework.data.neo4j.Personality;
import org.springframework.data.neo4j.support.GraphDatabaseContext;
import org.springframework.data.neo4j.support.TestTeam;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collection;
import java.util.Map;
import static java.util.Arrays.asList;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
/**
* @author mh
* @since 13.06.11
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:org/springframework/data/neo4j/support/Neo4jGraphPersistenceTest-context.xml"})
@Transactional
public class QueryExecutorTest {
@Autowired
GraphDatabaseContext graphDatabaseContext;
private CypherQueryExecutor queryExecutor;
private TestTeam testTeam;
private Person michael;
@Before
public void setUp() throws Exception {
testTeam = new TestTeam();
testTeam.createSDGTeam();
queryExecutor = new CypherQueryExecutor(graphDatabaseContext);
michael = testTeam.michael;
}
@Test
@Transactional
public void testQueryList() throws Exception {
final String queryString = String.format("start person=(%d,%d) return person.name, person.age", michael.getNodeId(), testTeam.david.getNodeId());
final Collection<Map<String,Object>> result = IteratorUtil.asCollection(queryExecutor.queryForList(queryString));
assertEquals(asList(testTeam.simpleRowFor(michael,"person"),testTeam.simpleRowFor(testTeam.david,"person")),result);
}
@Test
public void testQueryListOfTypePerson() throws Exception {
final String queryString = String.format("start person=(name_index,name,\"%s\") match (person) <-[:boss]- (boss) return boss", michael.getName());
final Collection<Person> result = IteratorUtil.asCollection(queryExecutor.query(queryString, Person.class));
assertEquals(asList(testTeam.emil),result);
}
@Test
public void testQueryOtherTeamMembers() throws Exception {
final String queryString = String.format("start person=(%d) match (person)<-[:persons]-(team)-[:persons]->(member) return member", michael.getNodeId());
System.out.println("testTeam = " + testTeam.sdg.getPersons());
final Collection<Person> result = IteratorUtil.asCollection(queryExecutor.query(queryString, Person.class));
assertThat(result, IsCollectionContaining.hasItems(testTeam.david, testTeam.emil));
}
@Test
public void testQueryAllTeamMembersByTeam() throws Exception {
final String queryString = String.format("start team=(Group,name,\"%s\") match (team)-[:persons]->(member) return member", testTeam.sdg.getName());
final Collection<Person> result = IteratorUtil.asCollection(queryExecutor.query(queryString, Person.class));
assertThat(result, IsCollectionContaining.hasItems(testTeam.david,testTeam.michael));
}
@Test
public void testQueryForObjectAsGroup() throws Exception {
final String queryString = String.format("start person=(name_index,name,\"%s\") match (person) <-[:persons]- (team) return team", michael.getName());
final Group result = queryExecutor.queryForObject(queryString, Group.class);
assertEquals(testTeam.sdg,result);
}
@Test
public void testQueryForObjectAsString() throws Exception {
final String queryString = String.format("start person=(name_index,name,\"%s\") match (person) <-[:persons]- (team) return team.name", michael.getName());
final String result = queryExecutor.queryForObject(queryString, String.class);
assertEquals(testTeam.sdg.getName(),result);
}
@Test
public void testQueryForObjectAsEnum() throws Exception {
final String queryString = String.format("start person=(name_index,name,\"%s\") return person.personality", michael.getName());
final Personality result = queryExecutor.queryForObject(queryString, Personality.class);
assertEquals(michael.getPersonality(),result);
}
}

View File

@@ -1,152 +0,0 @@
/**
* Copyright 2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.neo4j.support.query;
import org.junit.Before;
import org.junit.Test;
import org.junit.internal.matchers.IsCollectionContaining;
import org.junit.runner.RunWith;
import org.neo4j.graphdb.Node;
import org.neo4j.helpers.collection.IteratorUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.neo4j.Person;
import org.springframework.data.neo4j.Personality;
import org.springframework.data.neo4j.core.GraphDatabase;
import org.springframework.data.neo4j.core.NodeBacked;
import org.springframework.data.neo4j.Person;
import org.springframework.data.neo4j.Personality;
import org.springframework.data.neo4j.support.DelegatingGraphDatabase;
import org.springframework.data.neo4j.support.GraphDatabaseContext;
import org.springframework.data.neo4j.support.TestTeam;
import org.springframework.data.neo4j.support.node.Neo4jHelper;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.transaction.BeforeTransaction;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collection;
import java.util.Map;
import static java.util.Arrays.asList;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
/**
* @author mh
* @since 13.06.11
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:org/springframework/data/neo4j/support/Neo4jGraphPersistenceTest-context.xml"})
@Transactional
public class QueryOperationsTest {
@Autowired
protected ConversionService conversionService;
@Autowired
private GraphDatabaseContext graphDatabaseContext;
private CypherQueryExecutor queryOperations;
private TestTeam testTeam;
private Person michael;
private GraphDatabase graphDatabase;
@Before
public void setUp() throws Exception {
graphDatabase = createGraphDatabase();
testTeam = new TestTeam();
testTeam.createSDGTeam();
queryOperations = new CypherQueryExecutor(graphDatabaseContext);
//new DefaultQueryOperations<Map<String,Object>>(graphDatabase.queryEngineFor(QueryEngine.Type.Cypher));
michael = testTeam.michael;
}
@BeforeTransaction
public void cleanDb() {
Neo4jHelper.cleanDb(graphDatabaseContext);
}
protected GraphDatabase createGraphDatabase() throws Exception {
final DelegatingGraphDatabase graphDatabase = new DelegatingGraphDatabase(graphDatabaseContext.getGraphDatabaseService());
graphDatabase.setConversionService(conversionService);
return graphDatabase;
}
@Test
@Transactional
public void testQueryList() throws Exception {
final String queryString = String.format("start person=(%d,%d) return person.name, person.age", idFor(michael), idFor(testTeam.david));
final Collection<Map<String,Object>> result = IteratorUtil.asCollection(queryOperations.queryForList(queryString));
assertEquals(asList(testTeam.simpleRowFor(michael,"person"),testTeam.simpleRowFor(testTeam.david,"person")),result);
}
@Test
public void testQueryListOfTypePerson() throws Exception {
final String queryString = String.format("start person=(name_index,name,\"%s\") match (person) <-[:boss]- (boss) return boss", michael.getName());
final Collection<Node> result = IteratorUtil.asCollection(queryOperations.query(queryString, Node.class));
assertEquals(asList(nodeFor(testTeam.emil)),result);
}
private Node nodeFor(final NodeBacked entity) {
return entity.getPersistentState();
}
private long idFor(final NodeBacked entity) {
return entity.getNodeId();
}
@Test
public void testQueryOtherTeamMembers() throws Exception {
final String queryString = String.format("start person=(%d) match (person)<-[:persons]-(team)-[:persons]->(member) return member", idFor(michael));
System.out.println("testTeam = " + testTeam.sdg.getPersons());
final Collection<Node> result = IteratorUtil.asCollection(queryOperations.query(queryString, Node.class));
assertThat(result, IsCollectionContaining.hasItems(nodeFor(testTeam.david), nodeFor(testTeam.emil)));
}
@Test
public void testQueryAllTeamMembersByTeam() throws Exception {
final String queryString = String.format("start team=(Group,name,\"%s\") match (team)-[:persons]->(member) return member", testTeam.sdg.getName());
final Collection<Node> result = IteratorUtil.asCollection(queryOperations.query(queryString, Node.class));
assertThat(result, IsCollectionContaining.hasItems(nodeFor(testTeam.david),nodeFor(testTeam.michael)));
}
@Test
public void testQueryForObjectAsGroup() throws Exception {
final String queryString = String.format("start person=(name_index,name,\"%s\") match (person) <-[:persons]- (team) return team", michael.getName());
final Node result = queryOperations.queryForObject(queryString, Node.class);
assertEquals(nodeFor(testTeam.sdg),result);
}
@Test
public void testQueryForObjectAsString() throws Exception {
final String queryString = String.format("start person=(name_index,name,\"%s\") match (person) <-[:persons]- (team) return team.name", michael.getName());
final String result = queryOperations.queryForObject(queryString, String.class);
assertEquals(testTeam.sdg.getName(),result);
}
@Test
public void testQueryForObjectAsEnum() throws Exception {
final String queryString = String.format("start person=(name_index,name,\"%s\") return person.personality", michael.getName());
final Personality result = queryOperations.queryForObject(queryString, Personality.class);
assertEquals(michael.getPersonality(),result);
}
}

View File

@@ -1 +1 @@
Person.findTeam=start p=(%d) match (p)<-[:persons]-(group) return group
Person.findTeam=start p=(%person) match (p)<-[:persons]-(group) return group

View File

@@ -150,7 +150,7 @@
<property name="namedQueries">
<bean class="org.springframework.data.repository.core.support.PropertiesBasedNamedQueries">
<constructor-arg>
<props><prop key="Person.findTeam">start p=(%d) match (p)&lt;-[:persons]-(group) return group</prop></props>
<props><prop key="Person.findTeam">start p=(%person) match (p)&lt;-[:persons]-(group) return group</prop></props>
</constructor-arg>
</bean>
</property>

View File

@@ -48,6 +48,15 @@
The current version that does not show incorrect errors is AspectJ 1.6.12.M1 (included in STS 2.7.0.M2), previous versions are reported
to mislead the user.
</para>
<note>
<para>
There might be some issues with the eclipse maven plugin not adding AspectJ files correctly to the build path. If you encounter issues, please try the following:
Try editing the build path to <code>include **/*.aj</code> for the spring-data-neo4j project.
You can do this by selecting "Build Path -> Configure Build Path ..." from the Package Explorer.
Then for the <code>spring-data-neo4j/src/main/java</code> add <code>**/*.aj</code> to the Included path.
</para>
</note>
<para>
The AspectJ support in IntelliJ IDEA lacks some of the features. JetBrains is working on improving
the situation in their upcoming 10.5 release of their popular IDE. Their latest work is available

View File

@@ -114,19 +114,19 @@
</listitem>
</varlistentry>
<varlistentry>
<term>Executes the given query, replacing the first %d with the node-id and returning the results converted to the target type.</term>
<term>Executes the given query, replacing <code>%start</code> with the node-id and returning the results converted to the target type.</term>
<listitem>
<para><code>&lt;T&gt; Iterable&lt;T&gt; NodeBacked.findAllByQuery(final String query, final Class&lt;T&gt; targetType)</code></para>
</listitem>
</varlistentry>
<varlistentry>
<term>Executes the given query, replacing the first %d with the node-id and returning the original result, but with nodes and relationships replaced by their appropriate entities.</term>
<term>Executes the given query, replacing <code>%start</code> with the node-id and returning the original result, but with nodes and relationships replaced by their appropriate entities.</term>
<listitem>
<para><code>Iterable&lt;Map&lt;String,Object&gt;&gt; NodeBacked.findAllByQuery(final String query)</code></para>
</listitem>
</varlistentry>
<varlistentry>
<term>Executes the given query, replacing the first %d with the node-id and returns a single result converted to the target type.</term>
<term>Executes the given query, replacing <code>%start</code> with the node-id and returns a single result converted to the target type.</term>
<listitem>
<para><code>&lt;T&gt; T NodeBacked.findByQuery(final String query, final Class&lt;T&gt; targetType)</code></para>
</listitem>

View File

@@ -72,28 +72,33 @@ public class Movie {
</section>
<section>
<title>@GraphQuery: fields as query result views</title>
<title>@Query: fields as query result views</title>
<para>
The <code>@GraphQuery</code> annotation leverages the delegation infrastructure used by the
The <code>@Query</code> annotation leverages the delegation infrastructure used by the
Spring Data Graph aspects. It provides dynamic fields which, when accessed, return the values
selected by the provided query language expression. The provided query must contain a placeholder
for the id of the current entity <code>start n=(%d) match n-[:FRIEND]->friend return friend</code>
As graph queries can return variable number of entities the annotation can be put onto fields
with a single value, an Iterable of a type or an Iterable of <code>Map&lt;String,Object&gt;</code>.
The class of the resulting node entities must right now provided with the <code>elementClass</code> attribute.
Additional parameters are added to the query with Java's String.format substitution.
selected by the provided query language expression. The provided query must contain a placeholder named <code>%start</code>
for the id of the current entity. For instance <code>start n=(%start) match n-[:FRIEND]->friend return friend</code>.
Graph queries can return variable number of entities. That's why annotation can be put onto fields
with a single value, an Iterable of a concrete type or an Iterable of <code>Map&lt;String,Object&gt;</code>.
Additional parameters are taken from the params attribute of the <code>@Query</code> annotation.
The tuples form key-value pairs that are provided to the query at execution time.
</para>
<example>
<title>@GraphQuery from a node entity</title>
<title>@Graph on a node entity field</title>
<programlisting language="java"><![CDATA[@NodeEntity
public class Group {
@GraphQuery(value = "start n=(%d) match (n)-[:%s]->(friend) return friend",
elementClass = Person.class, params = "FRIEND")
@Query(value = "start n=(%start) match (n)-[:%relType]->(friend) return friend",
params = {"relType", "FRIEND"})
private Iterable<Person> friends;
}
]]></programlisting>
</example>
<para>
<note>
Please note that this annotation can also be used on repository methods.
</note>
</para>
</section>
<section>
<title>@GraphTraversal: fields as traversal result views</title>

View File

@@ -132,13 +132,16 @@
<section>
<title>Annotated Queries</title>
<para>
Queries for the graph-query language cypher can be supplied with the <code>@GraphQuery</code> annotation.
That means every method annotated with <code>@GraphQuery("start n=(%d) match (n)-->(m) return m")</code>
will use the query string. The String-format parameters are replaced by the actual method parameters in order,
whereby Node and Relationship-Entities are resolved to their respective id's and all other parameters are
Queries for the cypher graph-query language can be supplied with the <code>@Query</code> annotation.
That means every method annotated with <code>@Query("start n=(%node) match (n)-->(m) return m")</code>
will use the query string. The named parameter <code>%node</code> will be replaced by the actual method parameters.
Node and Relationship-Entities are resolved to their respective id's and all other parameters are
replaced directly (i.e. Strings, Longs, etc). There is special support for the <code>Sort</code> and <code>Pageable</code>
parameters from Spring Data Commons, which are supported to add programmatic paging and sorting (alternatively
static paging and sorting can be supplied in the query string itself).
For using the named parameters you have to either annotate the parameters of the method with the
<code>@Param("node")</code> annotation or enable debug symbols.
</para>
</section>
@@ -146,8 +149,9 @@
<title>Named Queries</title>
<para>Spring Data Graph also supports the notion of named queries which are externalized in property-config-files
(<code>META-INF/graph-named-queries.properties</code>). Those files have the format:
<code>Entity.finderName=query</code> (e.g. <code>Person.findBoss=start p=(%d) match (p)&lt;-[:BOSS]-(boss) return boss</code>).
Otherwise named queries support the same parameters as annotated queries.
<code>Entity.finderName=query</code> (e.g. <code>Person.findBoss=start p=(%person) match (p)&lt;-[:BOSS]-(boss) return boss</code>).
Otherwise named queries support the same parameters as annotated queries. For using the named parameters you have to either
annotate the parameters of the method with the <code>@Param("person")</code> annotation or enable debug symbols.
</para>
</section>
<section>