refactored repository queries
added support for like, not null, property-exists and index-query lookups for derived queries fixed delete* methods in repositories added singleOrNull() method to fluent result API
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -14,4 +14,4 @@ src/ant/.ant-targets-upload-dist.xml
|
||||
*.ipr
|
||||
*.iws
|
||||
spring-data-neo4j-examples/imdb/log.roo
|
||||
|
||||
src/docbkx/snippets
|
||||
|
||||
@@ -31,7 +31,7 @@ public class Movie {
|
||||
@RelatedTo(type="DIRECTED", direction = INCOMING)
|
||||
Person director;
|
||||
|
||||
@RelatedTo(elementClass = Person.class, type = "ACTS_IN", direction = INCOMING)
|
||||
@RelatedTo(type = "ACTS_IN", direction = INCOMING)
|
||||
Set<Person> actors;
|
||||
|
||||
@RelatedToVia(elementClass = Role.class, type = "ACTS_IN", direction = INCOMING)
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package org.springframework.data.neo4j.rest;
|
||||
|
||||
import org.neo4j.helpers.collection.IteratorUtil;
|
||||
import org.neo4j.rest.graphdb.util.ConvertedResult;
|
||||
import org.springframework.data.neo4j.conversion.EndResult;
|
||||
|
||||
@@ -32,6 +33,11 @@ class SpringEndResult<R> implements EndResult<R> {
|
||||
return result.single();
|
||||
}
|
||||
|
||||
@Override
|
||||
public R singleOrNull() {
|
||||
return IteratorUtil.singleOrNull(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(final org.springframework.data.neo4j.conversion.Handler<R> rHandler) {
|
||||
result.handle(new SpringHandler<R>(rHandler));
|
||||
|
||||
@@ -55,6 +55,11 @@ class SpringRestResult<T> implements Result<T> {
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
@Override
|
||||
public T singleOrNull() {
|
||||
return (T) to(Object.class).singleOrNull();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
|
||||
@@ -22,5 +22,7 @@ package org.springframework.data.neo4j.conversion;
|
||||
*/
|
||||
public interface EndResult<R> extends Iterable<R> {
|
||||
R single();
|
||||
R singleOrNull();
|
||||
void handle(Handler<R> handler);
|
||||
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.springframework.data.neo4j.conversion;
|
||||
|
||||
import org.neo4j.graphdb.index.IndexHits;
|
||||
import org.neo4j.helpers.collection.ClosableIterable;
|
||||
import org.neo4j.helpers.collection.IteratorUtil;
|
||||
import org.neo4j.helpers.collection.IteratorWrapper;
|
||||
|
||||
import java.util.Iterator;
|
||||
@@ -52,12 +53,15 @@ public class QueryResultBuilder<T> implements Result<T> {
|
||||
@Override
|
||||
public T single() {
|
||||
try {
|
||||
final Iterator<T> it = result.iterator();
|
||||
if (!it.hasNext()) throw new IllegalStateException("Expected at least one result, got none.");
|
||||
final T value = it.next();
|
||||
if (it.hasNext())
|
||||
throw new IllegalStateException("Expected at least one result, got more than one.");
|
||||
return value;
|
||||
return IteratorUtil.single(result);
|
||||
} finally {
|
||||
closeIfNeeded();
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public T singleOrNull() {
|
||||
try {
|
||||
return IteratorUtil.singleOrNull(result);
|
||||
} finally {
|
||||
closeIfNeeded();
|
||||
}
|
||||
@@ -69,11 +73,16 @@ public class QueryResultBuilder<T> implements Result<T> {
|
||||
@Override
|
||||
public R single() {
|
||||
try {
|
||||
final Iterator<T> it = result.iterator();
|
||||
if (!it.hasNext()) throw new IllegalStateException("Expected at least one result, got none.");
|
||||
final T value = it.next();
|
||||
if (it.hasNext())
|
||||
throw new IllegalStateException("Expected at least one result, got more than one.");
|
||||
final T value = IteratorUtil.single(result);
|
||||
return resultConverter.convert(value, type);
|
||||
} finally {
|
||||
closeIfNeeded();
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public R singleOrNull() {
|
||||
try {
|
||||
final T value = IteratorUtil.singleOrNull(result);
|
||||
return resultConverter.convert(value, type);
|
||||
} finally {
|
||||
closeIfNeeded();
|
||||
|
||||
@@ -27,11 +27,13 @@ public class IndexInfo {
|
||||
private IndexType indexType;
|
||||
private final String fieldName;
|
||||
private final Indexed.Level level;
|
||||
private String indexKey;
|
||||
|
||||
public IndexInfo(Indexed annotation, Neo4jPersistentProperty property) {
|
||||
this.indexName = determineIndexName(annotation,property);
|
||||
this.indexType = annotation.indexType();
|
||||
fieldName = annotation.fieldName();
|
||||
this.indexKey = fieldName.isEmpty() ? property.getNeo4jPropertyName() : fieldName;
|
||||
level = annotation.level();
|
||||
}
|
||||
|
||||
@@ -50,4 +52,12 @@ public class IndexInfo {
|
||||
public IndexType getIndexType() {
|
||||
return indexType;
|
||||
}
|
||||
|
||||
public boolean isFullText() {
|
||||
return indexType == IndexType.FULLTEXT;
|
||||
}
|
||||
|
||||
public String getIndexKey() {
|
||||
return indexKey;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,4 +73,6 @@ public interface Neo4jPersistentProperty extends PersistentProperty<Neo4jPersist
|
||||
Object getValue(final Object entity);
|
||||
|
||||
Neo4jPersistentEntity<?> getOwner();
|
||||
|
||||
String getIndexKey();
|
||||
}
|
||||
|
||||
@@ -293,16 +293,7 @@ public abstract class AbstractGraphRepository<S extends PropertyContainer, T> im
|
||||
|
||||
@Override
|
||||
public void delete(T entity) {
|
||||
final PropertyContainer state = template.getPersistentState(entity);
|
||||
if (state instanceof Node) {
|
||||
Node node = (Node) state;
|
||||
node.delete();
|
||||
}
|
||||
if (state instanceof Relationship) {
|
||||
Relationship relationship = (Relationship) state;
|
||||
relationship.delete();
|
||||
}
|
||||
|
||||
template.delete(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -16,23 +16,10 @@
|
||||
|
||||
package org.springframework.data.neo4j.repository;
|
||||
|
||||
import org.neo4j.graphdb.Node;
|
||||
import org.neo4j.graphdb.Relationship;
|
||||
import org.neo4j.helpers.collection.IteratorUtil;
|
||||
import org.springframework.data.domain.PageImpl;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.neo4j.annotation.Query;
|
||||
import org.springframework.data.neo4j.annotation.QueryType;
|
||||
import org.springframework.data.neo4j.mapping.Neo4jPersistentEntity;
|
||||
import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty;
|
||||
import org.springframework.data.neo4j.repository.query.DerivedCypherRepositoryQuery;
|
||||
import org.springframework.data.neo4j.repository.query.QueryTemplates;
|
||||
import org.springframework.data.neo4j.support.GenericTypeExtractor;
|
||||
import org.springframework.data.neo4j.repository.query.GraphQueryMethod;
|
||||
import org.springframework.data.neo4j.support.Neo4jTemplate;
|
||||
import org.springframework.data.neo4j.support.query.CypherQueryExecutor;
|
||||
import org.springframework.data.neo4j.support.query.QueryEngine;
|
||||
import org.springframework.data.neo4j.support.mapping.Neo4jMappingContext;
|
||||
import org.springframework.data.repository.core.EntityInformation;
|
||||
import org.springframework.data.repository.core.NamedQueries;
|
||||
import org.springframework.data.repository.core.RepositoryMetadata;
|
||||
@@ -42,10 +29,6 @@ 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;
|
||||
|
||||
/**
|
||||
* @author mh
|
||||
@@ -54,7 +37,7 @@ import java.util.Map;
|
||||
public class GraphRepositoryFactory extends RepositoryFactorySupport {
|
||||
|
||||
private final Neo4jTemplate template;
|
||||
private final MappingContext<? extends Neo4jPersistentEntity<?>, Neo4jPersistentProperty> mappingContext;
|
||||
private final Neo4jMappingContext mappingContext;
|
||||
|
||||
/**
|
||||
* Creates a new {@link GraphRepositoryFactory} from the given {@link org.springframework.data.neo4j.support.Neo4jTemplate} and
|
||||
@@ -63,7 +46,7 @@ public class GraphRepositoryFactory extends RepositoryFactorySupport {
|
||||
* @param template must not be {@literal null}.
|
||||
* @param mappingContext must not be {@literal null}.
|
||||
*/
|
||||
public GraphRepositoryFactory(Neo4jTemplate template, MappingContext<? extends Neo4jPersistentEntity<?>, Neo4jPersistentProperty> mappingContext) {
|
||||
public GraphRepositoryFactory(Neo4jTemplate template, Neo4jMappingContext mappingContext) {
|
||||
|
||||
Assert.notNull(template);
|
||||
Assert.notNull(mappingContext);
|
||||
@@ -126,273 +109,11 @@ public class GraphRepositoryFactory extends RepositoryFactorySupport {
|
||||
return new QueryLookupStrategy() {
|
||||
@Override
|
||||
public RepositoryQuery resolveQuery(Method method, RepositoryMetadata repositoryMetadata, NamedQueries namedQueries) {
|
||||
final GraphQueryMethod queryMethod = new GraphQueryMethod(method, repositoryMetadata,namedQueries);
|
||||
|
||||
if (!queryMethod.hasAnnotation() && !namedQueries.hasQuery(queryMethod.getNamedQueryName())) {
|
||||
return new DerivedCypherRepositoryQuery(mappingContext, queryMethod, template);
|
||||
}
|
||||
|
||||
return queryMethod.createQuery(repositoryMetadata, GraphRepositoryFactory.this.template);
|
||||
final GraphQueryMethod queryMethod = new GraphQueryMethod(method,repositoryMetadata,namedQueries,mappingContext);
|
||||
return queryMethod.createQuery(GraphRepositoryFactory.this.template);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static class GraphQueryMethod extends QueryMethod {
|
||||
|
||||
private final Method method;
|
||||
private final Query queryAnnotation;
|
||||
private final String query;
|
||||
|
||||
public GraphQueryMethod(Method method, RepositoryMetadata metadata, NamedQueries namedQueries) {
|
||||
super(method, metadata);
|
||||
this.method = method;
|
||||
queryAnnotation = method.getAnnotation(Query.class);
|
||||
this.query = queryAnnotation != null ? queryAnnotation.value() : getNamedQuery(namedQueries);
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
return this.query!=null; // && this.compoundType != null
|
||||
}
|
||||
|
||||
private String getNamedQuery(NamedQueries namedQueries) {
|
||||
final String namedQueryName = getNamedQueryName();
|
||||
if (namedQueries.hasQuery(namedQueryName)) {
|
||||
return namedQueries.getQuery(namedQueryName);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Class<?> getReturnType() {
|
||||
return method.getReturnType();
|
||||
}
|
||||
|
||||
private String prepareQuery(Object[] args) {
|
||||
final Parameters parameters = getParameters();
|
||||
String queryString = this.query;
|
||||
if (parameters.hasSortParameter()) {
|
||||
queryString = addSorting(queryString, (Sort) args[parameters.getSortIndex()]);
|
||||
}
|
||||
if (parameters.hasPageableParameter()) {
|
||||
final Pageable pageable = getPageable(args);
|
||||
if (pageable!=null) {
|
||||
queryString = addSorting(queryString, pageable.getSort());
|
||||
queryString = addPaging(queryString, pageable);
|
||||
}
|
||||
}
|
||||
return queryString;
|
||||
}
|
||||
|
||||
private Map<String, Object> resolveParams(Object[] parameters, Neo4jTemplate template) {
|
||||
Map<String, Object> params = new HashMap<String, Object>();
|
||||
for (Parameter parameter : getParameters().getBindableParameters()) {
|
||||
final Object value = parameters[parameter.getIndex()];
|
||||
final String parameterName = parameter.getName();
|
||||
if (parameterName != null) params.put(parameterName, resolveParameter(value, template));
|
||||
else params.put(String.format(QueryTemplates.PARAMETER, parameter.getIndex()), resolveParameter(value, template));
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
private Pageable getPageable(Object[] args) {
|
||||
Parameters parameters = getParameters();
|
||||
if (parameters.hasPageableParameter()) {
|
||||
return (Pageable) args[parameters.getPageableIndex()];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String addPaging(String baseQuery, Pageable pageable) {
|
||||
if (pageable==null) {
|
||||
return baseQuery;
|
||||
}
|
||||
return baseQuery + " skip "+pageable.getOffset() + " limit " + pageable.getPageSize();
|
||||
}
|
||||
|
||||
private String addSorting(String baseQuery, Sort sort) {
|
||||
if (sort==null)
|
||||
{
|
||||
return baseQuery; // || sort.isEmpty()
|
||||
}
|
||||
final String sortOrder = getSortOrder(sort);
|
||||
if (sortOrder.isEmpty()) {
|
||||
return baseQuery;
|
||||
}
|
||||
return baseQuery + " order by " + sortOrder;
|
||||
}
|
||||
|
||||
private String getSortOrder(Sort sort) {
|
||||
String result = "";
|
||||
for (Sort.Order order : sort) {
|
||||
result += order.getProperty() + " " + order.getDirection();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Object resolveParameter(Object parameter, Neo4jTemplate template) {
|
||||
final Class<?> type = parameter.getClass();
|
||||
if (template.isNodeEntity(type)) {
|
||||
final Node state = template.getPersistentState(parameter);
|
||||
if (state != null) return state.getId();
|
||||
}
|
||||
if (template.isRelationshipEntity(type)) {
|
||||
final Relationship state = template.getPersistentState(parameter);
|
||||
if (state != null) return state.getId();
|
||||
}
|
||||
return parameter;
|
||||
}
|
||||
|
||||
private Class<?> getCompoundType() {
|
||||
final Class<?> elementClass = getElementClass();
|
||||
if (elementClass!=null) {
|
||||
return elementClass;
|
||||
}
|
||||
return GenericTypeExtractor.resolveReturnedType(method);
|
||||
}
|
||||
|
||||
private Class<?> getElementClass() {
|
||||
if (!hasAnnotation() || queryAnnotation.elementClass().equals(Object.class)) {
|
||||
return null;
|
||||
}
|
||||
return queryAnnotation.elementClass();
|
||||
}
|
||||
|
||||
public String getQueryString() {
|
||||
return this.query;
|
||||
}
|
||||
|
||||
public boolean hasAnnotation() {
|
||||
return queryAnnotation!=null;
|
||||
}
|
||||
|
||||
private boolean isIterableResult() {
|
||||
return Iterable.class.isAssignableFrom(getReturnType());
|
||||
}
|
||||
|
||||
private RepositoryQuery createQuery(RepositoryMetadata repositoryMetadata, final Neo4jTemplate context) {
|
||||
if (!isValid()) {
|
||||
return null;
|
||||
}
|
||||
if (queryAnnotation == null) {
|
||||
return new CypherGraphRepositoryQuery(this, repositoryMetadata, context); // cypher is default for named queries
|
||||
}
|
||||
switch (queryAnnotation.type()) {
|
||||
case Cypher:
|
||||
return new CypherGraphRepositoryQuery(this, repositoryMetadata, context);
|
||||
case Gremlin:
|
||||
return new GremlinGraphRepositoryQuery(this, repositoryMetadata, context);
|
||||
default:
|
||||
throw new IllegalStateException("@Query Annotation has to be configured as Cypher or Gremlin Query");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class CypherGraphRepositoryQuery extends GraphRepositoryQuery {
|
||||
|
||||
private CypherQueryExecutor queryExecutor;
|
||||
|
||||
public CypherGraphRepositoryQuery(GraphQueryMethod queryMethod, RepositoryMetadata metadata, final Neo4jTemplate template) {
|
||||
super(queryMethod, metadata, template);
|
||||
}
|
||||
|
||||
private CypherQueryExecutor getQueryExecutor() {
|
||||
if (this.queryExecutor!=null) return this.queryExecutor;
|
||||
this.queryExecutor = new CypherQueryExecutor(getTemplate().queryEngineFor(QueryType.Cypher));
|
||||
return this.queryExecutor;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object dispatchQuery(String queryString, Map<String, Object> params, Pageable pageable) {
|
||||
GraphQueryMethod queryMethod = getQueryMethod();
|
||||
final Class<?> compoundType = queryMethod.getCompoundType();
|
||||
if (queryMethod.isPageQuery()) {
|
||||
return queryPaged(queryString,params,pageable);
|
||||
}
|
||||
if (queryMethod.isIterableResult()) {
|
||||
if (compoundType.isAssignableFrom(Map.class)) {
|
||||
return getQueryExecutor().queryForList(queryString, params);
|
||||
}
|
||||
return getQueryExecutor().query(queryString, queryMethod.getCompoundType(), params);
|
||||
}
|
||||
return getQueryExecutor().queryForObject(queryString, queryMethod.getReturnType(), params);
|
||||
}
|
||||
private Object queryPaged(String queryString, Map<String, Object> params, Pageable pageable) {
|
||||
final Iterable<?> result = getQueryExecutor().query(queryString, getQueryMethod().getCompoundType(), params);
|
||||
return createPage(result, pageable);
|
||||
}
|
||||
}
|
||||
|
||||
private static class GremlinGraphRepositoryQuery extends GraphRepositoryQuery {
|
||||
|
||||
private QueryEngine queryEngine;
|
||||
|
||||
public GremlinGraphRepositoryQuery(GraphQueryMethod queryMethod, RepositoryMetadata metadata, final Neo4jTemplate template) {
|
||||
super(queryMethod, metadata, template);
|
||||
}
|
||||
|
||||
private QueryEngine getQueryEngine() {
|
||||
if (this.queryEngine !=null) return queryEngine;
|
||||
this.queryEngine = getTemplate().queryEngineFor(QueryType.Gremlin);
|
||||
return this.queryEngine;
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
protected Object dispatchQuery(String queryString, Map<String, Object> params, Pageable pageable) {
|
||||
GraphQueryMethod queryMethod = getQueryMethod();
|
||||
if (queryMethod.isPageQuery()) {
|
||||
return queryPaged(queryString,params,pageable);
|
||||
}
|
||||
if (queryMethod.isIterableResult()) {
|
||||
return getQueryEngine().query(queryString, params).to(queryMethod.getCompoundType());
|
||||
}
|
||||
return getQueryEngine().query(queryString, params).to(queryMethod.getReturnType()).single();
|
||||
}
|
||||
|
||||
private Object queryPaged(String queryString, Map<String, Object> params, Pageable pageable) {
|
||||
@SuppressWarnings("unchecked") final Iterable<?> result = getQueryEngine().query(queryString, params).to(getQueryMethod().getCompoundType());
|
||||
return createPage(result, pageable);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static abstract class GraphRepositoryQuery implements RepositoryQuery {
|
||||
private final GraphQueryMethod queryMethod;
|
||||
private final Neo4jTemplate template;
|
||||
|
||||
public GraphRepositoryQuery(GraphQueryMethod queryMethod, RepositoryMetadata metadata, final Neo4jTemplate template) {
|
||||
this.queryMethod = queryMethod;
|
||||
this.template = template;
|
||||
}
|
||||
|
||||
protected Neo4jTemplate getTemplate() {
|
||||
return template;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object execute(Object[] parameters) {
|
||||
Map<String, Object> params = queryMethod.resolveParams(parameters, template);
|
||||
final String queryString = queryMethod.prepareQuery(parameters);
|
||||
return dispatchQuery(queryString,params,queryMethod.getPageable(parameters));
|
||||
}
|
||||
|
||||
protected abstract Object dispatchQuery(String queryString, Map<String, Object> params, Pageable pageable);
|
||||
|
||||
@Override
|
||||
public GraphQueryMethod getQueryMethod() {
|
||||
return queryMethod;
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
protected Object createPage(Iterable<?> result, Pageable pageable) {
|
||||
final List resultList = IteratorUtil.addToCollection(result, new ArrayList());
|
||||
if (pageable==null) {
|
||||
return new PageImpl(resultList);
|
||||
}
|
||||
final int currentTotal = pageable.getOffset() + pageable.getPageSize();
|
||||
return new PageImpl(resultList, pageable, currentTotal);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,7 @@ public class GraphRepositoryFactoryBean<S extends PropertyContainer, R extends C
|
||||
TransactionalRepositoryFactoryBeanSupport<R, T, Long> {
|
||||
|
||||
private Neo4jTemplate template;
|
||||
private MappingContext<? extends Neo4jPersistentEntity<?>, Neo4jPersistentProperty> mappingContext;
|
||||
private Neo4jMappingContext mappingContext;
|
||||
|
||||
public void setNeo4jTemplate(Neo4jTemplate template) {
|
||||
this.template = template;
|
||||
@@ -44,7 +44,7 @@ TransactionalRepositoryFactoryBeanSupport<R, T, Long> {
|
||||
* @param mappingContext the mappingContext to set
|
||||
*/
|
||||
public void setMappingContext(
|
||||
MappingContext<Neo4jPersistentEntity<?>, Neo4jPersistentProperty> mappingContext) {
|
||||
Neo4jMappingContext mappingContext) {
|
||||
this.mappingContext = mappingContext;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* 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.repository.query;
|
||||
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.neo4j.annotation.QueryType;
|
||||
import org.springframework.data.neo4j.support.Neo4jTemplate;
|
||||
import org.springframework.data.neo4j.support.query.QueryEngine;
|
||||
import org.springframework.data.repository.query.ParameterAccessor;
|
||||
import org.springframework.data.repository.query.Parameters;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author mh
|
||||
* @since 31.10.11
|
||||
*/
|
||||
class CypherGraphRepositoryQuery extends GraphRepositoryQuery {
|
||||
|
||||
private QueryEngine<Object> queryEngine;
|
||||
|
||||
public CypherGraphRepositoryQuery(GraphQueryMethod queryMethod, final Neo4jTemplate template) {
|
||||
super(queryMethod, template);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected QueryEngine<Object> getQueryEngine() {
|
||||
if (this.queryEngine != null) return this.queryEngine;
|
||||
this.queryEngine = getTemplate().queryEngineFor(QueryType.Cypher);
|
||||
return this.queryEngine;
|
||||
}
|
||||
|
||||
private String addPaging(String baseQuery, Pageable pageable) {
|
||||
if (pageable==null) {
|
||||
return baseQuery;
|
||||
}
|
||||
return baseQuery + " skip "+pageable.getOffset() + " limit " + pageable.getPageSize();
|
||||
}
|
||||
|
||||
private String addSorting(String baseQuery, Sort sort) {
|
||||
if (sort==null)
|
||||
{
|
||||
return baseQuery; // || sort.isEmpty()
|
||||
}
|
||||
final String sortOrder = getSortOrder(sort);
|
||||
if (sortOrder.isEmpty()) {
|
||||
return baseQuery;
|
||||
}
|
||||
return baseQuery + " order by " + sortOrder;
|
||||
}
|
||||
|
||||
private String getSortOrder(Sort sort) {
|
||||
String result = "";
|
||||
for (Sort.Order order : sort) {
|
||||
result += order.getProperty() + " " + order.getDirection();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
protected String createQueryWithPagingAndSorting(final ParameterAccessor accessor) {
|
||||
final GraphQueryMethod queryMethod = getQueryMethod();
|
||||
final Parameters parameters = queryMethod.getParameters();
|
||||
String queryString = queryMethod.getQueryString();
|
||||
if (parameters.hasSortParameter()) {
|
||||
queryString = addSorting(queryString, accessor.getSort());
|
||||
}
|
||||
if (parameters.hasPageableParameter()) {
|
||||
final Pageable pageable = accessor.getPageable();
|
||||
if (pageable!=null) {
|
||||
queryString = addSorting(queryString, pageable.getSort());
|
||||
queryString = addPaging(queryString, pageable);
|
||||
}
|
||||
}
|
||||
return queryString;
|
||||
}
|
||||
}
|
||||
@@ -76,14 +76,15 @@ class CypherQueryBuilder implements CypherQueryDefinition {
|
||||
PersistentPropertyPath<Neo4jPersistentProperty> path = context.getPersistentPropertyPath(part.getProperty());
|
||||
String variable = variableContext.getVariableFor(path);
|
||||
|
||||
Neo4jPersistentProperty leafProperty = path.getLeafProperty();
|
||||
if (!leafProperty.isRelationship()) {
|
||||
if (leafProperty.isIndexed()) {
|
||||
startClauses.add(new StartClause(path, variable, index++));
|
||||
final PartInfo partInfo = new PartInfo(path, variable, part, index);
|
||||
if (partInfo.isPrimitiveProperty()) {
|
||||
if (partInfo.isIndexed()) {
|
||||
startClauses.add(new StartClause(partInfo));
|
||||
} else {
|
||||
whereClauses.add(new WhereClause(path, variable, part.getType(), index++));
|
||||
whereClauses.add(new WhereClause(path, variable, part.getType(), index, partInfo));
|
||||
}
|
||||
}
|
||||
index += 1;
|
||||
|
||||
MatchClause matchClause = new MatchClause(path);
|
||||
|
||||
@@ -94,10 +95,21 @@ class CypherQueryBuilder implements CypherQueryDefinition {
|
||||
return this;
|
||||
}
|
||||
|
||||
public PartInfo getPartInfo(int parameterIndex) {
|
||||
for (StartClause startClause : startClauses) {
|
||||
if (startClause.getPartInfo().getParameterIndex()==parameterIndex) return startClause.getPartInfo();
|
||||
}
|
||||
for (WhereClause whereClause : whereClauses) {
|
||||
if (whereClause.getPartInfo().getParameterIndex()==parameterIndex) return whereClause.getPartInfo();
|
||||
}
|
||||
throw new IllegalArgumentException("Index "+parameterIndex+" not valid");
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
|
||||
@@ -49,4 +49,6 @@ interface CypherQueryDefinition {
|
||||
* @return
|
||||
*/
|
||||
String toString(Pageable pageable);
|
||||
|
||||
PartInfo getPartInfo(int index);
|
||||
}
|
||||
|
||||
@@ -15,22 +15,13 @@
|
||||
*/
|
||||
package org.springframework.data.neo4j.repository.query;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.neo4j.annotation.QueryType;
|
||||
import org.springframework.data.neo4j.mapping.Neo4jPersistentEntity;
|
||||
import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty;
|
||||
import org.springframework.data.neo4j.repository.GraphRepositoryFactory.GraphQueryMethod;
|
||||
import org.springframework.data.neo4j.support.Neo4jTemplate;
|
||||
import org.springframework.data.neo4j.support.query.CypherQueryExecutor;
|
||||
import org.springframework.data.neo4j.support.mapping.Neo4jMappingContext;
|
||||
import org.springframework.data.repository.core.EntityMetadata;
|
||||
import org.springframework.data.repository.query.ParameterAccessor;
|
||||
import org.springframework.data.repository.query.ParametersParameterAccessor;
|
||||
import org.springframework.data.repository.query.QueryMethod;
|
||||
import org.springframework.data.repository.query.RepositoryQuery;
|
||||
import org.springframework.data.repository.query.parser.PartTree;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -40,78 +31,45 @@ import org.springframework.util.Assert;
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class DerivedCypherRepositoryQuery implements RepositoryQuery {
|
||||
public class DerivedCypherRepositoryQuery extends CypherGraphRepositoryQuery {
|
||||
|
||||
private final GraphQueryMethod method;
|
||||
private final CypherQueryExecutor executor;
|
||||
private final CypherQueryDefinition query;
|
||||
|
||||
/**
|
||||
* Creates a new {@link DerivedCypherRepositoryQuery} from the given {@link MappingContext},
|
||||
* {@link GraphQueryMethod} and {@link org.springframework.data.neo4j.support.Neo4jTemplate}.
|
||||
*
|
||||
* @param context must not be {@literal null}.
|
||||
* @param method must not be {@literal null}.
|
||||
* @param database must not be {@literal null}.
|
||||
* @param mappingContext must not be {@literal null}.
|
||||
* @param queryMethod must not be {@literal null}.
|
||||
* @param template must not be {@literal null}.
|
||||
*/
|
||||
public DerivedCypherRepositoryQuery(MappingContext<? extends Neo4jPersistentEntity<?>, Neo4jPersistentProperty> context, GraphQueryMethod method, Neo4jTemplate database) {
|
||||
public DerivedCypherRepositoryQuery(Neo4jMappingContext mappingContext, GraphQueryMethod queryMethod, Neo4jTemplate template) {
|
||||
super(queryMethod, template);
|
||||
Assert.notNull(mappingContext);
|
||||
|
||||
Assert.notNull(context);
|
||||
Assert.notNull(method);
|
||||
Assert.notNull(database);
|
||||
EntityMetadata<?> info = queryMethod.getEntityInformation();
|
||||
PartTree tree = new PartTree(queryMethod.getName(), info.getJavaType());
|
||||
|
||||
EntityMetadata<?> info = method.getEntityInformation();
|
||||
PartTree tree = new PartTree(method.getName(), info.getJavaType());
|
||||
|
||||
this.query = new CypherQueryCreator(tree, context, info.getJavaType()).createQuery();
|
||||
this.method = method;
|
||||
this.executor = new CypherQueryExecutor(database.queryEngineFor(QueryType.Cypher));
|
||||
this.query = new CypherQueryCreator(tree, mappingContext, info.getJavaType()).createQuery();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.RepositoryQuery#execute(java.lang.Object[])
|
||||
*/
|
||||
@Override
|
||||
public Object execute(Object[] parameters) {
|
||||
|
||||
ParameterAccessor accessor = new ParametersParameterAccessor(method.getParameters(), parameters);
|
||||
|
||||
Map<String, Object> paramMap = new HashMap<String, Object>();
|
||||
int counter = 0;
|
||||
|
||||
for (Object parameter : accessor) {
|
||||
paramMap.put(String.format(QueryTemplates.PARAMETER, counter++), parameter);
|
||||
public Object resolveParameter(Object value, String parameterName, int index) {
|
||||
final Object newValue = super.resolveParameter(value, parameterName, index);
|
||||
PartInfo info = query.getPartInfo(index);
|
||||
if (info.isFullText()) {
|
||||
return String.format(QueryTemplates.PARAMETER_INDEX_QUERY,info.getIndexKey(),newValue);
|
||||
}
|
||||
|
||||
Class<?> type = method.getEntityInformation().getJavaType();
|
||||
String query = getQuery(this.query, accessor);
|
||||
|
||||
if (method.isCollectionQuery()) {
|
||||
return executor.query(query, type, paramMap);
|
||||
} else {
|
||||
return executor.queryForObject(query, type, paramMap);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.RepositoryQuery#getQueryMethod()
|
||||
*/
|
||||
@Override
|
||||
public QueryMethod getQueryMethod() {
|
||||
return method;
|
||||
return newValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the actual Cypher query applying {@link Pageable} or {@link Sort} instances.
|
||||
*
|
||||
* @param query
|
||||
* @param accessor
|
||||
* @return
|
||||
* @param accessor parameters
|
||||
* @return query string
|
||||
*/
|
||||
private String getQuery(CypherQueryDefinition query, ParameterAccessor accessor) {
|
||||
|
||||
protected String createQueryWithPagingAndSorting(ParameterAccessor accessor) {
|
||||
if (accessor.getPageable() != null) {
|
||||
return query.toString(accessor.getPageable());
|
||||
} else if (accessor.getSort() != null) {
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
/**
|
||||
* 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.repository.query;
|
||||
|
||||
import org.springframework.data.neo4j.annotation.Query;
|
||||
import org.springframework.data.neo4j.support.GenericTypeExtractor;
|
||||
import org.springframework.data.neo4j.support.Neo4jTemplate;
|
||||
import org.springframework.data.neo4j.support.mapping.Neo4jMappingContext;
|
||||
import org.springframework.data.repository.core.NamedQueries;
|
||||
import org.springframework.data.repository.core.RepositoryMetadata;
|
||||
import org.springframework.data.repository.query.*;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author mh
|
||||
* @since 31.10.11
|
||||
*/
|
||||
public class GraphQueryMethod extends QueryMethod {
|
||||
|
||||
private final Method method;
|
||||
private final NamedQueries namedQueries;
|
||||
private final Neo4jMappingContext mappingContext;
|
||||
private final Query queryAnnotation;
|
||||
|
||||
public GraphQueryMethod(Method method, RepositoryMetadata metadata, NamedQueries namedQueries, Neo4jMappingContext mappingContext) {
|
||||
super(method, metadata);
|
||||
this.method = method;
|
||||
this.namedQueries = namedQueries;
|
||||
this.mappingContext = mappingContext;
|
||||
this.queryAnnotation = method.getAnnotation(Query.class);
|
||||
}
|
||||
|
||||
public String getQueryString() {
|
||||
return queryAnnotation != null ? queryAnnotation.value() : getNamedQuery();
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
return this.getQueryString() != null; // && this.compoundType != null
|
||||
}
|
||||
|
||||
private String getNamedQuery() {
|
||||
final String namedQueryName = getNamedQueryName();
|
||||
if (namedQueries.hasQuery(namedQueryName)) {
|
||||
return namedQueries.getQuery(namedQueryName);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Class<?> getReturnType() {
|
||||
return method.getReturnType();
|
||||
}
|
||||
|
||||
protected Map<String, Object> resolveParams(ParameterAccessor accessor, ParameterResolver parameterResolver) {
|
||||
Map<String, Object> params = new HashMap<String, Object>();
|
||||
for (Parameter parameter : getParameters().getBindableParameters()) {
|
||||
final Object value = accessor.getBindableValue(parameter.getIndex());
|
||||
final String parameterName = getParameterName(parameter);
|
||||
|
||||
params.put(parameterName, parameterResolver.resolveParameter(value, parameterName, parameter.getIndex()));
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
private String getParameterName(Parameter parameter) {
|
||||
final String parameterName = parameter.getName();
|
||||
if (parameterName != null) {
|
||||
return parameterName;
|
||||
}
|
||||
return String.format(QueryTemplates.PARAMETER, parameter.getIndex());
|
||||
}
|
||||
|
||||
Class<?> getCompoundType() {
|
||||
final Class<?> elementClass = getElementClass();
|
||||
if (elementClass!=null) {
|
||||
return elementClass;
|
||||
}
|
||||
return GenericTypeExtractor.resolveReturnedType(method);
|
||||
}
|
||||
|
||||
private Class<?> getElementClass() {
|
||||
if (!hasAnnotation() || queryAnnotation.elementClass().equals(Object.class)) {
|
||||
return null;
|
||||
}
|
||||
return queryAnnotation.elementClass();
|
||||
}
|
||||
|
||||
public boolean hasAnnotation() {
|
||||
return queryAnnotation!=null;
|
||||
}
|
||||
|
||||
boolean isIterableResult() {
|
||||
return Iterable.class.isAssignableFrom(getReturnType());
|
||||
}
|
||||
|
||||
public RepositoryQuery createQuery(final Neo4jTemplate template) {
|
||||
if (queryAnnotation == null) {
|
||||
if (namedQueries.hasQuery(getNamedQueryName())) {
|
||||
return new CypherGraphRepositoryQuery(this, template); // cypher is default for named queries
|
||||
} else {
|
||||
return new DerivedCypherRepositoryQuery(mappingContext, this, template);
|
||||
}
|
||||
}
|
||||
switch (queryAnnotation.type()) {
|
||||
case Cypher:
|
||||
return new CypherGraphRepositoryQuery(this, template);
|
||||
case Gremlin:
|
||||
return new GremlinGraphRepositoryQuery(this, template);
|
||||
default:
|
||||
throw new IllegalStateException("@Query Annotation has to be configured as Cypher or Gremlin Query");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
/**
|
||||
* 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.repository.query;
|
||||
|
||||
import org.neo4j.graphdb.Node;
|
||||
import org.neo4j.graphdb.Relationship;
|
||||
import org.neo4j.helpers.collection.IteratorUtil;
|
||||
import org.springframework.data.domain.PageImpl;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.neo4j.support.Neo4jTemplate;
|
||||
import org.springframework.data.neo4j.support.query.QueryEngine;
|
||||
import org.springframework.data.repository.query.ParameterAccessor;
|
||||
import org.springframework.data.repository.query.ParametersParameterAccessor;
|
||||
import org.springframework.data.repository.query.RepositoryQuery;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author mh
|
||||
* @since 31.10.11
|
||||
*/
|
||||
abstract class GraphRepositoryQuery implements RepositoryQuery, ParameterResolver {
|
||||
private final GraphQueryMethod queryMethod;
|
||||
private final Neo4jTemplate template;
|
||||
|
||||
public GraphRepositoryQuery(GraphQueryMethod queryMethod, final Neo4jTemplate template) {
|
||||
Assert.notNull(queryMethod);
|
||||
Assert.notNull(template);
|
||||
this.queryMethod = queryMethod;
|
||||
this.template = template;
|
||||
}
|
||||
|
||||
protected Neo4jTemplate getTemplate() {
|
||||
return template;
|
||||
}
|
||||
|
||||
public Object resolveParameter(Object value, String parameterName, int index) {
|
||||
final Class<?> type = value.getClass();
|
||||
if (template.isNodeEntity(type)) {
|
||||
final Node state = template.getPersistentState(value);
|
||||
if (state != null) return state.getId();
|
||||
}
|
||||
if (template.isRelationshipEntity(type)) {
|
||||
final Relationship state = template.getPersistentState(value);
|
||||
if (state != null) return state.getId();
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object execute(Object[] parameters) {
|
||||
final ParameterAccessor accessor = new ParametersParameterAccessor(queryMethod.getParameters(), parameters);
|
||||
Map<String, Object> params = resolveParams(accessor);
|
||||
final String queryString = createQueryWithPagingAndSorting(accessor);
|
||||
return dispatchQuery(queryString, params, accessor);
|
||||
}
|
||||
|
||||
protected Map<String, Object> resolveParams(ParameterAccessor accessor) {
|
||||
return queryMethod.resolveParams(accessor, this);
|
||||
}
|
||||
|
||||
protected String createQueryWithPagingAndSorting(ParameterAccessor accessor) {
|
||||
return queryMethod.getQueryString();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected Object dispatchQuery(String queryString, Map<String, Object> params, ParameterAccessor accessor) {
|
||||
GraphQueryMethod queryMethod = getQueryMethod();
|
||||
final QueryEngine<?> queryEngine = getQueryEngine();
|
||||
final Class<?> compoundType = queryMethod.getCompoundType();
|
||||
if (queryMethod.isPageQuery()) {
|
||||
@SuppressWarnings("unchecked") final Iterable<?> result = queryEngine.query(queryString, params).to(compoundType);
|
||||
return createPage(result, accessor.getPageable());
|
||||
}
|
||||
if (queryMethod.isIterableResult()) {
|
||||
return queryEngine.query(queryString, params).to(compoundType);
|
||||
}
|
||||
return queryEngine.query(queryString, params).to(queryMethod.getReturnType()).singleOrNull();
|
||||
}
|
||||
|
||||
@Override
|
||||
public GraphQueryMethod getQueryMethod() {
|
||||
return queryMethod;
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
protected Object createPage(Iterable<?> result, Pageable pageable) {
|
||||
final List resultList = IteratorUtil.addToCollection(result, new ArrayList());
|
||||
if (pageable==null) {
|
||||
return new PageImpl(resultList);
|
||||
}
|
||||
final int currentTotal = pageable.getOffset() + pageable.getPageSize();
|
||||
return new PageImpl(resultList, pageable, currentTotal);
|
||||
}
|
||||
|
||||
protected abstract QueryEngine<?> getQueryEngine();
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* 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.repository.query;
|
||||
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.neo4j.annotation.QueryType;
|
||||
import org.springframework.data.neo4j.support.Neo4jTemplate;
|
||||
import org.springframework.data.neo4j.support.query.QueryEngine;
|
||||
import org.springframework.data.repository.core.RepositoryMetadata;
|
||||
import org.springframework.data.repository.query.ParameterAccessor;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author mh
|
||||
* @since 31.10.11
|
||||
*/
|
||||
class GremlinGraphRepositoryQuery extends GraphRepositoryQuery {
|
||||
|
||||
private QueryEngine queryEngine;
|
||||
|
||||
public GremlinGraphRepositoryQuery(GraphQueryMethod queryMethod, final Neo4jTemplate template) {
|
||||
super(queryMethod, template);
|
||||
}
|
||||
|
||||
protected QueryEngine getQueryEngine() {
|
||||
if (this.queryEngine !=null) return queryEngine;
|
||||
this.queryEngine = getTemplate().queryEngineFor(QueryType.Gremlin);
|
||||
return this.queryEngine;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* 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.repository.query;
|
||||
|
||||
/**
|
||||
* @author mh
|
||||
* @since 31.10.11
|
||||
*/
|
||||
public interface ParameterResolver {
|
||||
Object resolveParameter(Object value, String parameterName, int index);
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* 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.repository.query;
|
||||
|
||||
import org.springframework.data.mapping.context.PersistentPropertyPath;
|
||||
import org.springframework.data.neo4j.mapping.IndexInfo;
|
||||
import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty;
|
||||
import org.springframework.data.repository.query.parser.Part;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* @author mh
|
||||
* @since 31.10.11
|
||||
*/
|
||||
public class PartInfo {
|
||||
private final PersistentPropertyPath<Neo4jPersistentProperty> path;
|
||||
private final String variable;
|
||||
private final Part part;
|
||||
private final int index;
|
||||
|
||||
public PartInfo(PersistentPropertyPath<Neo4jPersistentProperty> path, String variable, Part part, int index) {
|
||||
Assert.notNull(path);
|
||||
Assert.hasText(variable);
|
||||
|
||||
this.path = path;
|
||||
this.variable = variable;
|
||||
this.part = part;
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
protected Part.Type getType() {
|
||||
return this.part.getType();
|
||||
}
|
||||
|
||||
Neo4jPersistentProperty getLeafProperty() {
|
||||
return path.getLeafProperty();
|
||||
}
|
||||
|
||||
public boolean isPrimitiveProperty() {
|
||||
return !isRelationship();
|
||||
}
|
||||
|
||||
private boolean isRelationship() {
|
||||
return getLeafProperty().isRelationship();
|
||||
}
|
||||
|
||||
|
||||
public boolean isIndexed() {
|
||||
return getLeafProperty().isIndexed();
|
||||
}
|
||||
|
||||
public String getVariable() {
|
||||
return variable;
|
||||
}
|
||||
|
||||
public int getParameterIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
public String getIndexName() {
|
||||
return getIndexInfo().getIndexName();
|
||||
}
|
||||
|
||||
public boolean isFullText() {
|
||||
return isIndexed() && getIndexInfo().isFullText();
|
||||
}
|
||||
|
||||
private IndexInfo getIndexInfo() {
|
||||
return getLeafProperty().getIndexInfo();
|
||||
}
|
||||
|
||||
String getNeo4jPropertyName() {
|
||||
Neo4jPersistentProperty leafProperty = getLeafProperty();
|
||||
return leafProperty.getNeo4jPropertyName();
|
||||
}
|
||||
|
||||
public String getIndexKey() {
|
||||
return getIndexInfo().getIndexKey();
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,7 @@ import org.springframework.data.neo4j.mapping.RelationshipInfo;
|
||||
public abstract class QueryTemplates {
|
||||
|
||||
public static final String PARAMETER = "_%d";
|
||||
public static final String PARAMETER_INDEX_QUERY = "%s:%s";
|
||||
|
||||
public static final String PLACEHOLDER = String.format("{%s}", PARAMETER);
|
||||
private static final String DIRECTION_INCOMING = "<-[:%s]-";
|
||||
@@ -35,7 +36,9 @@ public abstract class QueryTemplates {
|
||||
static final String DEFAULT_START_CLAUSE = "%s=node:__types__(className=\"%s\")";
|
||||
static final String SKIP_LIMIT = " skip %d limit %d";
|
||||
static final String START_CLAUSE = "%s=node:%s(%s=" + PLACEHOLDER + ")";
|
||||
static final String WHERE_CLAUSE = "%s.%s %s " + PLACEHOLDER;
|
||||
static final String START_CLAUSE_FULLTEXT = "%s=node:%s(" + PLACEHOLDER + ")";
|
||||
static final String WHERE_CLAUSE_1 = "%s.%s %s " + PLACEHOLDER;
|
||||
static final String WHERE_CLAUSE_0 = "%s.%s %s ";
|
||||
static final String SORT_CLAUSE = "%s %s";
|
||||
static final String ORDER_BY_CLAUSE = " order by %s";
|
||||
|
||||
|
||||
@@ -15,9 +15,7 @@
|
||||
*/
|
||||
package org.springframework.data.neo4j.repository.query;
|
||||
|
||||
import org.springframework.data.mapping.context.PersistentPropertyPath;
|
||||
import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Representation of a Cypher {@literal start} clause.
|
||||
@@ -26,26 +24,16 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
class StartClause {
|
||||
|
||||
private final PersistentPropertyPath<Neo4jPersistentProperty> path;
|
||||
private final String variable;
|
||||
private final int index;
|
||||
private final PartInfo partInfo;
|
||||
|
||||
/**
|
||||
* Creates a new {@link StartClause} from the given {@link Neo4jPersistentProperty}, variable and the given
|
||||
* parameter index.
|
||||
*
|
||||
* @param property must not be {@literal null}.
|
||||
* @param variable must not be {@literal null} or empty.
|
||||
* @param index
|
||||
*
|
||||
* @param partInfo
|
||||
*/
|
||||
public StartClause(PersistentPropertyPath<Neo4jPersistentProperty> property, String variable, int index) {
|
||||
|
||||
Assert.notNull(property);
|
||||
Assert.hasText(variable);
|
||||
|
||||
this.path = property;
|
||||
this.variable = variable;
|
||||
this.index = index;
|
||||
public StartClause(PartInfo partInfo) {
|
||||
this.partInfo = partInfo;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -54,11 +42,16 @@ class StartClause {
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
final String variable = partInfo.getVariable();
|
||||
final String indexName = partInfo.getIndexName();
|
||||
final int parameterIndex = partInfo.getParameterIndex();
|
||||
if (partInfo.isFullText()) {
|
||||
return String.format(QueryTemplates.START_CLAUSE_FULLTEXT, variable, indexName, parameterIndex);
|
||||
}
|
||||
return String.format(QueryTemplates.START_CLAUSE, variable, indexName, partInfo.getNeo4jPropertyName(), parameterIndex);
|
||||
}
|
||||
|
||||
Neo4jPersistentProperty leafProperty = path.getLeafProperty();
|
||||
String indexName = leafProperty.getIndexInfo().getIndexName();
|
||||
String propertyName = leafProperty.getNeo4jPropertyName();
|
||||
|
||||
return String.format(QueryTemplates.START_CLAUSE, variable, indexName, propertyName, index);
|
||||
public PartInfo getPartInfo() {
|
||||
return partInfo;
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,9 @@ import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty;
|
||||
import org.springframework.data.repository.query.parser.Part.Type;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import static org.springframework.data.neo4j.repository.query.QueryTemplates.WHERE_CLAUSE_0;
|
||||
import static org.springframework.data.neo4j.repository.query.QueryTemplates.WHERE_CLAUSE_1;
|
||||
|
||||
/**
|
||||
* Representation of a Cypher {@literal where} clause.
|
||||
*
|
||||
@@ -42,34 +45,24 @@ class WhereClause {
|
||||
symbols.put(Type.LESS_THAN_EQUAL, "<=");
|
||||
symbols.put(Type.NEGATING_SIMPLE_PROPERTY, "!=");
|
||||
symbols.put(Type.SIMPLE_PROPERTY, "=");
|
||||
|
||||
symbols.put(Type.LIKE, "=~"); // n.name =~ /Tob.*/
|
||||
//symbols.put(Type.EXISTS, ""); // property exists n.name
|
||||
// compare when exists WHERE n.belt? = 'white'
|
||||
symbols.put(Type.IS_NULL, "is null"); // WHERE r is null
|
||||
SYMBOLS = Collections.unmodifiableMap(symbols);
|
||||
}
|
||||
|
||||
private final PersistentPropertyPath<Neo4jPersistentProperty> path;
|
||||
private final String variable;
|
||||
private final Type type;
|
||||
private final int index;
|
||||
private final PartInfo partInfo;
|
||||
|
||||
/**
|
||||
* Creates a new {@link WhereClause} for the given {@link Neo4jPersistentProperty}, variable, type and parameter
|
||||
* index.
|
||||
*
|
||||
* @param path must not be {@literal null}.
|
||||
* @param variable must not be {@literal null} or empty.
|
||||
* @param type must not be {@literal null}.
|
||||
* @param index
|
||||
*
|
||||
* @param partInfo
|
||||
*/
|
||||
public WhereClause(PersistentPropertyPath<Neo4jPersistentProperty> path, String variable, Type type, int index) {
|
||||
|
||||
Assert.notNull(path);
|
||||
Assert.hasText(variable);
|
||||
Assert.notNull(type);
|
||||
|
||||
this.path = path;
|
||||
this.variable = variable;
|
||||
this.type = type;
|
||||
this.index = index;
|
||||
public WhereClause(PersistentPropertyPath<Neo4jPersistentProperty> path, String variable, Type type, int index, PartInfo partInfo) {
|
||||
Assert.notNull(partInfo.getType());
|
||||
this.partInfo = partInfo;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -78,7 +71,17 @@ class WhereClause {
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format(QueryTemplates.WHERE_CLAUSE, variable, path.getLeafProperty().getNeo4jPropertyName(),
|
||||
SYMBOLS.get(type), index);
|
||||
final String propertyName = partInfo.getNeo4jPropertyName();
|
||||
final String operator = SYMBOLS.get(partInfo.getType());
|
||||
final String variable = partInfo.getVariable();
|
||||
|
||||
if (partInfo.getType().getNumberOfArguments()==0) {
|
||||
return String.format(WHERE_CLAUSE_0, variable, propertyName, operator);
|
||||
}
|
||||
return String.format(WHERE_CLAUSE_1, variable, propertyName, operator, partInfo.getParameterIndex());
|
||||
}
|
||||
|
||||
public PartInfo getPartInfo() {
|
||||
return partInfo;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,15 +38,11 @@ import org.springframework.data.neo4j.support.index.IndexType;
|
||||
import org.springframework.data.neo4j.support.mapping.EntityStateHandler;
|
||||
import org.springframework.data.neo4j.support.mapping.Neo4jPersistentEntityImpl;
|
||||
import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty;
|
||||
import org.springframework.data.neo4j.mapping.RelationshipResult;
|
||||
import org.springframework.data.neo4j.repository.GraphRepository;
|
||||
import org.springframework.data.neo4j.repository.NodeGraphRepository;
|
||||
import org.springframework.data.neo4j.repository.RelationshipGraphRepository;
|
||||
import org.springframework.data.neo4j.support.index.IndexProvider;
|
||||
import org.springframework.data.neo4j.support.index.IndexType;
|
||||
import org.springframework.data.neo4j.support.mapping.EntityCreatingClosableIterable;
|
||||
import org.springframework.data.neo4j.support.mapping.EntityStateHandler;
|
||||
import org.springframework.data.neo4j.support.mapping.Neo4jPersistentEntityImpl;
|
||||
import org.springframework.data.neo4j.support.query.QueryEngine;
|
||||
import org.springframework.data.neo4j.template.GraphCallback;
|
||||
import org.springframework.data.neo4j.template.Neo4jOperations;
|
||||
@@ -570,7 +566,7 @@ public class Neo4jTemplate implements Neo4jOperations, EntityPersister {
|
||||
}
|
||||
|
||||
public String getIndexKey(Neo4jPersistentProperty property) {
|
||||
return getIndexProvider().getIndexKey(property);
|
||||
return property.getIndexKey();
|
||||
}
|
||||
public <S extends PropertyContainer> Index<S> getIndex(Neo4jPersistentProperty property, final Class<?> instanceType) {
|
||||
return getIndexProvider().getIndex(property, instanceType);
|
||||
|
||||
@@ -88,12 +88,6 @@ public class IndexProvider {
|
||||
return graphDatabase.createIndex(type, indexName, fullText);
|
||||
}
|
||||
|
||||
public String getIndexKey(Neo4jPersistentProperty property) {
|
||||
Indexed indexed = property.getAnnotation(Indexed.class);
|
||||
if (indexed==null || indexed.fieldName().isEmpty()) return property.getNeo4jPropertyName();
|
||||
return indexed.fieldName();
|
||||
}
|
||||
|
||||
public <S extends PropertyContainer> Index<S> getIndex(Neo4jPersistentProperty property, final Class<?> instanceType) {
|
||||
final Indexed indexedAnnotation = property.getAnnotation(Indexed.class);
|
||||
final Class<?> declaringType = property.getOwner().getType();
|
||||
|
||||
@@ -214,4 +214,8 @@ class Neo4jPersistentPropertyImpl extends AbstractPersistentProperty<Neo4jPersis
|
||||
private boolean hasNodeEntityType() {
|
||||
return getType().isAnnotationPresent(NodeEntity.class);
|
||||
}
|
||||
|
||||
public String getIndexKey() {
|
||||
return getIndexInfo().getIndexKey();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,8 @@ import org.springframework.util.ObjectUtils;
|
||||
public class Group {
|
||||
|
||||
public final static String OTHER_NAME_INDEX = "other_name";
|
||||
public static final String SEARCH_GROUPS_INDEX = "search-groups";
|
||||
public static final String SEARCH_GROUPS_INDEX = "search_groups";
|
||||
public static final String SEARCH_GROUPS_INDEX_BUG = "search-groups";
|
||||
|
||||
@RelatedTo(direction = Direction.OUTGOING)
|
||||
private Collection<Person> persons = new HashSet<Person>();
|
||||
@@ -68,6 +69,10 @@ public class Group {
|
||||
@Indexed(indexName = SEARCH_GROUPS_INDEX, indexType = IndexType.FULLTEXT)
|
||||
private String fullTextName;
|
||||
|
||||
@GraphProperty
|
||||
@Indexed(indexName = SEARCH_GROUPS_INDEX_BUG, indexType = IndexType.FULLTEXT)
|
||||
private String fullTextNameBug;
|
||||
|
||||
@Indexed(fieldName = OTHER_NAME_INDEX)
|
||||
private String otherName;
|
||||
|
||||
@@ -80,6 +85,14 @@ public class Group {
|
||||
@Indexed(level = Indexed.Level.INSTANCE)
|
||||
private String indexLevelName;
|
||||
|
||||
public String getFullTextNameBug() {
|
||||
return fullTextNameBug;
|
||||
}
|
||||
|
||||
public void setFullTextNameBug(String fullTextNameBug) {
|
||||
this.fullTextNameBug = fullTextNameBug;
|
||||
}
|
||||
|
||||
@GraphId
|
||||
private Long id;
|
||||
|
||||
|
||||
@@ -18,7 +18,9 @@ package org.springframework.data.neo4j.repository;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.hibernate.ejb.criteria.expression.function.AggregationFunction;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -37,7 +39,11 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
|
||||
import org.springframework.test.context.transaction.BeforeTransaction;
|
||||
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.TransactionStatus;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
@@ -55,7 +61,6 @@ import static org.neo4j.helpers.collection.IteratorUtil.asCollection;
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration
|
||||
@TestExecutionListeners({CleanContextCacheTestExecutionListener.class, DependencyInjectionTestExecutionListener.class, TransactionalTestExecutionListener.class})
|
||||
@Transactional
|
||||
public class GraphRepositoryTest {
|
||||
|
||||
protected final Log log = LogFactory.getLog(getClass());
|
||||
@@ -63,6 +68,9 @@ public class GraphRepositoryTest {
|
||||
@Autowired
|
||||
private Neo4jTemplate neo4jTemplate;
|
||||
|
||||
@Autowired
|
||||
private PlatformTransactionManager transactionManager;
|
||||
|
||||
@Autowired
|
||||
private PersonRepository personRepository;
|
||||
@Autowired
|
||||
@@ -85,36 +93,82 @@ public class GraphRepositoryTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deleteAll() {
|
||||
assertThat(personRepository.count(), is(3L));
|
||||
new TransactionTemplate(transactionManager).execute(new TransactionCallbackWithoutResult() {
|
||||
@Override
|
||||
protected void doInTransactionWithoutResult(TransactionStatus status) {
|
||||
personRepository.deleteAll();
|
||||
}
|
||||
});
|
||||
assertThat(personRepository.count(), is(0L));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deleteCollection() {
|
||||
assertThat(personRepository.count(), is(3L));
|
||||
new TransactionTemplate(transactionManager).execute(new TransactionCallbackWithoutResult() {
|
||||
@Override
|
||||
protected void doInTransactionWithoutResult(TransactionStatus status) {
|
||||
personRepository.delete(asList(testTeam.michael,testTeam.david));
|
||||
}
|
||||
});
|
||||
assertThat(personRepository.count(), is(1L));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deleteById() {
|
||||
final Long id = testTeam.michael.getId();
|
||||
new TransactionTemplate(transactionManager).execute(new TransactionCallbackWithoutResult() {
|
||||
@Override
|
||||
protected void doInTransactionWithoutResult(TransactionStatus status) {
|
||||
personRepository.delete(id);
|
||||
}
|
||||
});
|
||||
assertThat(personRepository.exists(id), is(false));
|
||||
}
|
||||
@Test
|
||||
public void deleteSingle() {
|
||||
new TransactionTemplate(transactionManager).execute(new TransactionCallbackWithoutResult() {
|
||||
@Override
|
||||
protected void doInTransactionWithoutResult(TransactionStatus status) {
|
||||
personRepository.delete(testTeam.michael);
|
||||
}
|
||||
});
|
||||
assertThat(personRepository.exists(testTeam.michael.getId()), is(false));
|
||||
}
|
||||
|
||||
@Test @Transactional
|
||||
public void testFindIterableOfPersonWithQueryAnnotation() {
|
||||
Iterable<Person> teamMembers = personRepository.findAllTeamMembers(testTeam.sdg);
|
||||
assertThat( asCollection( teamMembers ), hasItems( testTeam.michael, testTeam.david, testTeam.emil ) );
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test @Transactional
|
||||
public void testFindIterableOfPersonWithQueryAnnotationSpatial() {
|
||||
Iterable<Person> teamMembers = personRepository.findWithinBoundingBox("personLayer", 55, 15, 57, 17);
|
||||
assertThat(asCollection(teamMembers), hasItems(testTeam.michael, testTeam.david));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test @Transactional
|
||||
public void testFindIterableOfPersonWithQueryAnnotationAndGremlin() {
|
||||
Iterable<Person> teamMembers = personRepository.findAllTeamMembersGremlin( testTeam.sdg );
|
||||
assertThat( asCollection( teamMembers ), hasItems( testTeam.michael, testTeam.david, testTeam.emil ) );
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test @Transactional
|
||||
public void testFindPersonWithQueryAnnotation() {
|
||||
Person boss = personRepository.findBoss( testTeam.michael );
|
||||
assertThat(boss, is( testTeam.emil ));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test @Transactional
|
||||
public void testFindPersonWithQueryAnnotationUsingLongAsParameter() {
|
||||
Person boss = personRepository.findBoss( testTeam.michael.getId() );
|
||||
assertThat(boss, is( testTeam.emil ));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test @Transactional
|
||||
public void shouldBeAbleToTurnQueryResultsToAMapResultInterface() throws Exception {
|
||||
MemberData first = personRepository.findMemberData(testTeam.michael).iterator().next();
|
||||
|
||||
@@ -122,20 +176,29 @@ public class GraphRepositoryTest {
|
||||
assertThat(asCollection(first.getTeams()), hasItem(testTeam.sdg));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test @Transactional
|
||||
public void testFindIterableMapsWithQueryAnnotation() {
|
||||
Iterable<Map<String, Object>> teamMembers = personRepository.findAllTeamMemberData(testTeam.sdg);
|
||||
assertThat(asCollection(teamMembers), hasItems(testTeam.simpleRowFor(testTeam.michael, "member"), testTeam.simpleRowFor(testTeam.david, "member"), testTeam.simpleRowFor(testTeam.emil, "member")));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test @Transactional
|
||||
@Ignore("untyil cypher supports parameters in path's and skip, limit")
|
||||
public void testFindWithMultipleParameters() {
|
||||
final int depth = 1;
|
||||
final int limit = 2;
|
||||
Iterable<Person> teamMembers = personRepository.findSomeTeamMembers(testTeam.sdg.getName(), 0, limit, depth);
|
||||
assertThat(asCollection(teamMembers), hasItems(testTeam.michael, testTeam.david));
|
||||
}
|
||||
|
||||
@Test @Transactional
|
||||
public void testFindPaged() {
|
||||
final PageRequest page = new PageRequest(0, 1, Sort.Direction.ASC, "member.name");
|
||||
Page<Person> teamMemberPage1 = personRepository.findAllTeamMembersPaged(testTeam.sdg, page);
|
||||
assertThat(teamMemberPage1, hasItem(testTeam.david));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test @Transactional
|
||||
public void testFindPagedDescending() {
|
||||
final PageRequest page = new PageRequest(0, 2, Sort.Direction.DESC, "member.name");
|
||||
Page<Person> teamMemberPage1 = personRepository.findAllTeamMembersPaged(testTeam.sdg, page);
|
||||
@@ -143,7 +206,7 @@ public class GraphRepositoryTest {
|
||||
assertThat(teamMemberPage1.isFirstPage(), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test @Transactional
|
||||
public void testFindPagedNull() {
|
||||
Page<Person> teamMemberPage1 = personRepository.findAllTeamMembersPaged(testTeam.sdg, null);
|
||||
assertEquals(new HashSet(asList(testTeam.david, testTeam.emil, testTeam.michael)), addToCollection(teamMemberPage1, new HashSet()));
|
||||
@@ -151,34 +214,46 @@ public class GraphRepositoryTest {
|
||||
assertThat(teamMemberPage1.isLastPage(), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test @Transactional
|
||||
public void testFindSortedDescending() {
|
||||
final Sort sort = new Sort(Sort.Direction.DESC, "member.name");
|
||||
Iterable<Person> teamMembers = personRepository.findAllTeamMembersSorted(testTeam.sdg, sort);
|
||||
assertEquals(asList(testTeam.michael, testTeam.emil, testTeam.david), asCollection(teamMembers));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test @Transactional
|
||||
public void testFindSortedNull() {
|
||||
Iterable<Person> teamMembers = personRepository.findAllTeamMembersSorted(testTeam.sdg, null);
|
||||
assertThat(teamMembers, hasItems(testTeam.michael, testTeam.emil, testTeam.david));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test @Transactional
|
||||
public void testFindByNamedQuery() {
|
||||
Group team = personRepository.findTeam(testTeam.michael);
|
||||
assertThat(team, is(testTeam.sdg));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test @Transactional
|
||||
public void findByName() {
|
||||
Iterable<Person> findByName = personRepository.findByName(testTeam.michael.getName());
|
||||
assertThat(findByName, hasItem(testTeam.michael));
|
||||
}
|
||||
|
||||
@Test( expected = NoSuchColumnFoundException.class)
|
||||
@Test( expected = NoSuchColumnFoundException.class) @Transactional
|
||||
public void missingColumnIsReportedNicely() {
|
||||
Iterable<MemberData> findByName = personRepository.nonWorkingQuery( testTeam.michael );
|
||||
Person boss = findByName.iterator().next().getBoss();
|
||||
}
|
||||
|
||||
@Test @Transactional
|
||||
public void findByFullTextName() {
|
||||
testTeam.sdg.setFullTextName("test");
|
||||
neo4jTemplate.save(testTeam.sdg);
|
||||
final Iterable<Group> groups = groupRepository.findByFullTextNameLike("te*");
|
||||
assertThat(groups, hasItem(testTeam.sdg));
|
||||
}
|
||||
@Test @Transactional
|
||||
public void findPageByName() {
|
||||
final Iterable<Group> groups = groupRepository.findByName(testTeam.sdg.getName(), new PageRequest(0, 1));
|
||||
assertThat(groups, hasItem(testTeam.sdg));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.data.neo4j.repository;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.neo4j.model.Group;
|
||||
|
||||
|
||||
@@ -24,4 +26,6 @@ import org.springframework.data.neo4j.model.Group;
|
||||
* @since 29.03.11
|
||||
*/
|
||||
public interface GroupRepository extends GraphRepository<Group>, NamedIndexRepository<Group> {
|
||||
Iterable<Group> findByFullTextNameLike(String name);
|
||||
Page<Group> findByName(String name, Pageable page);
|
||||
}
|
||||
|
||||
@@ -51,6 +51,8 @@ public interface PersonRepository extends GraphRepository<Person>, NamedIndexRep
|
||||
@Query("start member=node({p_person}) match team-[:persons]->member<-[?:boss]-boss return member")
|
||||
Iterable<MemberData> nonWorkingQuery(@Param("p_person") Person person);
|
||||
|
||||
@Query("start team=node:Group(name = {p_team}) match (team)-[:persons*1..1]->(member) return member.name,member.age skip {skip} limit {limit}")
|
||||
Iterable<Person> findSomeTeamMembers(@Param("p_team") String team, @Param("skip") Integer skip,@Param("limit") Integer limit,@Param("depth") Integer depth);
|
||||
|
||||
@Query("start person=node({p_person}) match (boss)-[:boss]->(person) return boss")
|
||||
Person findBoss(@Param("p_person") Person person);
|
||||
|
||||
@@ -34,7 +34,8 @@ import org.springframework.data.repository.query.parser.Part;
|
||||
public class CypherQueryBuilderUnitTests {
|
||||
|
||||
CypherQueryBuilder query;
|
||||
private final String className = Person.class.getName();
|
||||
private final String CLASS_NAME = Person.class.getName();
|
||||
private String DEFAULT_START_CLAUSE = "start person=node:__types__(className=\"" + CLASS_NAME + "\")";
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
@@ -51,6 +52,40 @@ public class CypherQueryBuilderUnitTests {
|
||||
assertThat(query.toString(), is("start person=node:Person(name={_0}) return person"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createsQueryForLikePropertyIndex() {
|
||||
|
||||
Part part = new Part("titleLike", Person.class);
|
||||
query.addRestriction(part);
|
||||
|
||||
assertThat(query.toString(), is("start person=node:title({_0}) return person"));
|
||||
}
|
||||
@Test
|
||||
public void createsQueryForLikeProperty() {
|
||||
|
||||
Part part = new Part("infoLike", Person.class);
|
||||
query.addRestriction(part);
|
||||
|
||||
assertThat(query.toString(), is(DEFAULT_START_CLAUSE+" where person.info =~ {_0} return person"));
|
||||
}
|
||||
@Test
|
||||
public void createsQueryForGreaterThanPropertyReference() {
|
||||
|
||||
Part part = new Part("ageGreaterThan", Person.class);
|
||||
query.addRestriction(part);
|
||||
|
||||
assertThat(query.toString(), is(DEFAULT_START_CLAUSE+" where person.age > {_0} return person"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createsQueryForIsNullPropertyReference() {
|
||||
|
||||
Part part = new Part("ageIsNull", Person.class);
|
||||
query.addRestriction(part);
|
||||
|
||||
assertThat(query.toString(), is(DEFAULT_START_CLAUSE+" where person.age is null return person"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createsQueryForPropertyOnRelationShipReference() {
|
||||
|
||||
@@ -76,13 +111,12 @@ public class CypherQueryBuilderUnitTests {
|
||||
query.addRestriction(new Part("age", Person.class));
|
||||
|
||||
final String className = Person.class.getName();
|
||||
assertThat(query.toString(), is("start person=node:__types__(className=\"" + className + "\") where person.age = {_0} return person"));
|
||||
assertThat(query.toString(), is(DEFAULT_START_CLAUSE +" where person.age = {_0} return person"));
|
||||
}
|
||||
@Test
|
||||
public void createsSimpleTraversalClauseCorrectly() {
|
||||
query.addRestriction(new Part("group", Person.class));
|
||||
|
||||
assertThat(query.toString(), is("start person=node:__types__(className=\"" + className + "\") match person<-[:members]-person_group return person"));
|
||||
assertThat(query.toString(), is(DEFAULT_START_CLAUSE + " match person<-[:members]-person_group return person"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import org.springframework.data.neo4j.annotation.GraphId;
|
||||
import org.springframework.data.neo4j.annotation.Indexed;
|
||||
import org.springframework.data.neo4j.annotation.NodeEntity;
|
||||
import org.springframework.data.neo4j.annotation.RelatedTo;
|
||||
import org.springframework.data.neo4j.support.index.IndexType;
|
||||
|
||||
@NodeEntity
|
||||
class Person {
|
||||
@@ -28,6 +29,12 @@ class Person {
|
||||
|
||||
@Indexed
|
||||
String name;
|
||||
|
||||
@Indexed(indexType = IndexType.FULLTEXT,indexName = "title")
|
||||
String title;
|
||||
|
||||
String info;
|
||||
|
||||
int age;
|
||||
|
||||
@RelatedTo(type = "members", direction = Direction.INCOMING)
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
to a remote database service instead. Neo4j can also run as a server. It exposes its operations via a HTTP
|
||||
based REST API.
|
||||
</para>
|
||||
<para>
|
||||
We decided to have a look to be at least knowledgeable about this deployment scenario. We were aware of the
|
||||
<para>
|
||||
We decided to have a look, to be at least knowledgeable about this deployment scenario. We were aware of the
|
||||
difference of local, in-memory calls and higher latency network hops. That would be something
|
||||
we would also look out for.
|
||||
</para>
|
||||
|
||||
Reference in New Issue
Block a user