diff --git a/.gitignore b/.gitignore index 7a864b998..b7809264e 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,4 @@ src/ant/.ant-targets-upload-dist.xml *.ipr *.iws spring-data-neo4j-examples/imdb/log.roo - +src/docbkx/snippets diff --git a/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/domain/Movie.java b/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/domain/Movie.java index e0b903613..fdad46ad6 100644 --- a/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/domain/Movie.java +++ b/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/domain/Movie.java @@ -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 actors; @RelatedToVia(elementClass = Role.class, type = "ACTS_IN", direction = INCOMING) diff --git a/spring-data-neo4j-rest/src/main/java/org/springframework/data/neo4j/rest/SpringEndResult.java b/spring-data-neo4j-rest/src/main/java/org/springframework/data/neo4j/rest/SpringEndResult.java index 67dfc2447..592dd6688 100644 --- a/spring-data-neo4j-rest/src/main/java/org/springframework/data/neo4j/rest/SpringEndResult.java +++ b/spring-data-neo4j-rest/src/main/java/org/springframework/data/neo4j/rest/SpringEndResult.java @@ -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 implements EndResult { return result.single(); } + @Override + public R singleOrNull() { + return IteratorUtil.singleOrNull(result); + } + @Override public void handle(final org.springframework.data.neo4j.conversion.Handler rHandler) { result.handle(new SpringHandler(rHandler)); diff --git a/spring-data-neo4j-rest/src/main/java/org/springframework/data/neo4j/rest/SpringRestResult.java b/spring-data-neo4j-rest/src/main/java/org/springframework/data/neo4j/rest/SpringRestResult.java index 46e559237..8778de9fb 100644 --- a/spring-data-neo4j-rest/src/main/java/org/springframework/data/neo4j/rest/SpringRestResult.java +++ b/spring-data-neo4j-rest/src/main/java/org/springframework/data/neo4j/rest/SpringRestResult.java @@ -55,6 +55,11 @@ class SpringRestResult implements Result { } + @SuppressWarnings({"unchecked"}) + @Override + public T singleOrNull() { + return (T) to(Object.class).singleOrNull(); + } @SuppressWarnings("unchecked") @Override diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/conversion/EndResult.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/conversion/EndResult.java index 2ab12804f..398f23d93 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/conversion/EndResult.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/conversion/EndResult.java @@ -22,5 +22,7 @@ package org.springframework.data.neo4j.conversion; */ public interface EndResult extends Iterable { R single(); + R singleOrNull(); void handle(Handler handler); + } diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/conversion/QueryResultBuilder.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/conversion/QueryResultBuilder.java index 4c515f580..039c9cfb8 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/conversion/QueryResultBuilder.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/conversion/QueryResultBuilder.java @@ -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 implements Result { @Override public T single() { try { - final Iterator 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 implements Result { @Override public R single() { try { - final Iterator 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(); diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/mapping/IndexInfo.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/mapping/IndexInfo.java index 6a15460eb..f666b9b33 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/mapping/IndexInfo.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/mapping/IndexInfo.java @@ -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; + } } diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/mapping/Neo4jPersistentProperty.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/mapping/Neo4jPersistentProperty.java index c86af389e..5d9dd4c09 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/mapping/Neo4jPersistentProperty.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/mapping/Neo4jPersistentProperty.java @@ -73,4 +73,6 @@ public interface Neo4jPersistentProperty extends PersistentProperty getOwner(); + + String getIndexKey(); } diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/AbstractGraphRepository.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/AbstractGraphRepository.java index 42c115e68..e585147d0 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/AbstractGraphRepository.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/AbstractGraphRepository.java @@ -293,16 +293,7 @@ public abstract class AbstractGraphRepository 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 diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/GraphRepositoryFactory.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/GraphRepositoryFactory.java index 975286018..e05de551e 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/GraphRepositoryFactory.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/GraphRepositoryFactory.java @@ -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, 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, 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 resolveParams(Object[] parameters, Neo4jTemplate template) { - Map params = new HashMap(); - 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 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 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 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 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 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 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); - } - } } \ No newline at end of file diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/GraphRepositoryFactoryBean.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/GraphRepositoryFactoryBean.java index 8275fe159..e871ac1f7 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/GraphRepositoryFactoryBean.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/GraphRepositoryFactoryBean.java @@ -34,7 +34,7 @@ public class GraphRepositoryFactoryBean { private Neo4jTemplate template; - private MappingContext, Neo4jPersistentProperty> mappingContext; + private Neo4jMappingContext mappingContext; public void setNeo4jTemplate(Neo4jTemplate template) { this.template = template; @@ -44,7 +44,7 @@ TransactionalRepositoryFactoryBeanSupport { * @param mappingContext the mappingContext to set */ public void setMappingContext( - MappingContext, Neo4jPersistentProperty> mappingContext) { + Neo4jMappingContext mappingContext) { this.mappingContext = mappingContext; } diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/CypherGraphRepositoryQuery.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/CypherGraphRepositoryQuery.java new file mode 100644 index 000000000..f190cd4a3 --- /dev/null +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/CypherGraphRepositoryQuery.java @@ -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 queryEngine; + + public CypherGraphRepositoryQuery(GraphQueryMethod queryMethod, final Neo4jTemplate template) { + super(queryMethod, template); + } + + @Override + protected QueryEngine 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; + } +} diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/CypherQueryBuilder.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/CypherQueryBuilder.java index 0357884a0..009ce59c6 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/CypherQueryBuilder.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/CypherQueryBuilder.java @@ -76,14 +76,15 @@ class CypherQueryBuilder implements CypherQueryDefinition { PersistentPropertyPath 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() { diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/CypherQueryDefinition.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/CypherQueryDefinition.java index 0188d49d5..0f698ffb4 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/CypherQueryDefinition.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/CypherQueryDefinition.java @@ -49,4 +49,6 @@ interface CypherQueryDefinition { * @return */ String toString(Pageable pageable); + + PartInfo getPartInfo(int index); } diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/DerivedCypherRepositoryQuery.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/DerivedCypherRepositoryQuery.java index 25d9573d7..725bfb0f7 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/DerivedCypherRepositoryQuery.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/DerivedCypherRepositoryQuery.java @@ -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, 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 paramMap = new HashMap(); - 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) { diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/GraphQueryMethod.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/GraphQueryMethod.java new file mode 100644 index 000000000..d204e8d6f --- /dev/null +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/GraphQueryMethod.java @@ -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 resolveParams(ParameterAccessor accessor, ParameterResolver parameterResolver) { + Map params = new HashMap(); + 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"); + } + } +} diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/GraphRepositoryQuery.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/GraphRepositoryQuery.java new file mode 100644 index 000000000..8a853aec1 --- /dev/null +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/GraphRepositoryQuery.java @@ -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 params = resolveParams(accessor); + final String queryString = createQueryWithPagingAndSorting(accessor); + return dispatchQuery(queryString, params, accessor); + } + + protected Map resolveParams(ParameterAccessor accessor) { + return queryMethod.resolveParams(accessor, this); + } + + protected String createQueryWithPagingAndSorting(ParameterAccessor accessor) { + return queryMethod.getQueryString(); + } + + @SuppressWarnings("unchecked") + protected Object dispatchQuery(String queryString, Map 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(); +} diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/GremlinGraphRepositoryQuery.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/GremlinGraphRepositoryQuery.java new file mode 100644 index 000000000..de25601f5 --- /dev/null +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/GremlinGraphRepositoryQuery.java @@ -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; + } +} diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/ParameterResolver.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/ParameterResolver.java new file mode 100644 index 000000000..2f22babee --- /dev/null +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/ParameterResolver.java @@ -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); +} diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/PartInfo.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/PartInfo.java new file mode 100644 index 000000000..44c668ef9 --- /dev/null +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/PartInfo.java @@ -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 path; + private final String variable; + private final Part part; + private final int index; + + public PartInfo(PersistentPropertyPath 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(); + } +} diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/QueryTemplates.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/QueryTemplates.java index d62a4ce45..0fdc77e67 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/QueryTemplates.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/QueryTemplates.java @@ -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"; diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/StartClause.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/StartClause.java index df739e281..9d6184c98 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/StartClause.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/StartClause.java @@ -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 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 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; } } \ No newline at end of file diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/WhereClause.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/WhereClause.java index 6880eaec0..143a223f5 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/WhereClause.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/WhereClause.java @@ -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 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 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 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; } } diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/Neo4jTemplate.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/Neo4jTemplate.java index a3f1b582d..9512670f5 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/Neo4jTemplate.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/Neo4jTemplate.java @@ -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 Index getIndex(Neo4jPersistentProperty property, final Class instanceType) { return getIndexProvider().getIndex(property, instanceType); diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/index/IndexProvider.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/index/IndexProvider.java index bb7f8b368..fb8b2ed2e 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/index/IndexProvider.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/index/IndexProvider.java @@ -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 Index getIndex(Neo4jPersistentProperty property, final Class instanceType) { final Indexed indexedAnnotation = property.getAnnotation(Indexed.class); final Class declaringType = property.getOwner().getType(); diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/mapping/Neo4JPersistentPropertyImpl.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/mapping/Neo4JPersistentPropertyImpl.java index e6c0f5aea..dafd8f922 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/mapping/Neo4JPersistentPropertyImpl.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/mapping/Neo4JPersistentPropertyImpl.java @@ -214,4 +214,8 @@ class Neo4jPersistentPropertyImpl extends AbstractPersistentProperty persons = new HashSet(); @@ -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; diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/GraphRepositoryTest.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/GraphRepositoryTest.java index 9c8ab4df1..35fd67098 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/GraphRepositoryTest.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/GraphRepositoryTest.java @@ -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 teamMembers = personRepository.findAllTeamMembers(testTeam.sdg); assertThat( asCollection( teamMembers ), hasItems( testTeam.michael, testTeam.david, testTeam.emil ) ); } - @Test + @Test @Transactional public void testFindIterableOfPersonWithQueryAnnotationSpatial() { Iterable teamMembers = personRepository.findWithinBoundingBox("personLayer", 55, 15, 57, 17); assertThat(asCollection(teamMembers), hasItems(testTeam.michael, testTeam.david)); } - @Test + @Test @Transactional public void testFindIterableOfPersonWithQueryAnnotationAndGremlin() { Iterable 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> 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 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 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 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 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 teamMembers = personRepository.findAllTeamMembersSorted(testTeam.sdg, sort); assertEquals(asList(testTeam.michael, testTeam.emil, testTeam.david), asCollection(teamMembers)); } - @Test + @Test @Transactional public void testFindSortedNull() { Iterable 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 findByName = personRepository.findByName(testTeam.michael.getName()); assertThat(findByName, hasItem(testTeam.michael)); } - - @Test( expected = NoSuchColumnFoundException.class) + @Test( expected = NoSuchColumnFoundException.class) @Transactional public void missingColumnIsReportedNicely() { Iterable 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 groups = groupRepository.findByFullTextNameLike("te*"); + assertThat(groups, hasItem(testTeam.sdg)); + } + @Test @Transactional + public void findPageByName() { + final Iterable groups = groupRepository.findByName(testTeam.sdg.getName(), new PageRequest(0, 1)); + assertThat(groups, hasItem(testTeam.sdg)); + } } diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/GroupRepository.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/GroupRepository.java index 84f881f15..749b7edb1 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/GroupRepository.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/GroupRepository.java @@ -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, NamedIndexRepository { + Iterable findByFullTextNameLike(String name); + Page findByName(String name, Pageable page); } diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/PersonRepository.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/PersonRepository.java index f89dd0960..e73c9d052 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/PersonRepository.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/PersonRepository.java @@ -51,6 +51,8 @@ public interface PersonRepository extends GraphRepository, NamedIndexRep @Query("start member=node({p_person}) match team-[:persons]->member<-[?:boss]-boss return member") Iterable 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 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); diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/CypherQueryBuilderUnitTests.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/CypherQueryBuilderUnitTests.java index 1c13df331..efa859f13 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/CypherQueryBuilderUnitTests.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/CypherQueryBuilderUnitTests.java @@ -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")); } diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/Person.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/Person.java index 535260768..f6a1560da 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/Person.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/Person.java @@ -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) diff --git a/src/docbkx/tutorial/neo4j-server.xml b/src/docbkx/tutorial/neo4j-server.xml index 64dc1b112..6a7473c04 100644 --- a/src/docbkx/tutorial/neo4j-server.xml +++ b/src/docbkx/tutorial/neo4j-server.xml @@ -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. - - We decided to have a look to be at least knowledgeable about this deployment scenario. We were aware of the + + 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.