DATAGRAPH-387 : Minimal changes required to get LabelNodeTypeRepresentationStrategy working across the board (including REST)

This commit is contained in:
Nicki Watt
2013-10-01 18:42:53 +01:00
committed by Michael Hunger
parent b40ea309ae
commit 5e90f6b5b3
27 changed files with 960 additions and 383 deletions

View File

@@ -33,8 +33,6 @@ import org.springframework.data.neo4j.annotation.QueryType;
import org.springframework.data.neo4j.conversion.DefaultConverter;
import org.springframework.data.neo4j.conversion.ResultConverter;
import org.springframework.data.neo4j.core.GraphDatabase;
import org.springframework.data.neo4j.core.GraphDatabaseGlobalOperations;
import org.springframework.data.neo4j.support.DelegatingGraphDatabaseGlobalOperations;
import org.springframework.data.neo4j.support.index.NoSuchIndexException;
import org.springframework.data.neo4j.support.query.ConversionServiceQueryResultConverter;
import org.springframework.data.neo4j.support.query.QueryEngine;
@@ -48,7 +46,6 @@ public class SpringRestGraphDatabase extends org.neo4j.rest.graphdb.RestGraphDat
}
private ConversionService conversionService;
private ResultConverter resultConverter;
private GraphDatabaseGlobalOperations globalOperations;
public SpringRestGraphDatabase( RestAPI api){
super(api);
@@ -62,14 +59,6 @@ public class SpringRestGraphDatabase extends org.neo4j.rest.graphdb.RestGraphDat
this(new RestAPIFacade( uri, user, password ));
}
@Override
public GraphDatabaseGlobalOperations getGlobalGraphOperations() {
if (this.globalOperations == null) {
this.globalOperations = new DelegatingGraphDatabaseGlobalOperations(this);
}
return globalOperations;
}
@Override
public Node createNode(Map<String, Object> props) {
return super.getRestAPI().createNode(props);

View File

@@ -6,4 +6,5 @@
<bean id="graphDatabaseService" class="org.springframework.data.neo4j.rest.SpringRestGraphDatabase" scope="singleton">
<constructor-arg index="0" value="http://localhost:7470/db/data" />
</bean>
<alias name="graphDatabaseService" alias="graphDatabase"/>
</beans>

View File

@@ -30,13 +30,6 @@ import java.util.Map;
public interface GraphDatabase {
/**
* @return an object which is able to provide global graph
* type operations
*/
GraphDatabaseGlobalOperations getGlobalGraphOperations();
/**
* @return the reference node of the underlying graph database
*/

View File

@@ -15,13 +15,11 @@
*/
package org.springframework.data.neo4j.fieldaccess;
import org.neo4j.graphdb.Direction;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.RelationshipType;
import org.neo4j.graphdb.*;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.neo4j.mapping.MappingPolicy;
import org.springframework.data.neo4j.support.Neo4jTemplate;
import org.springframework.data.neo4j.support.typerepresentation.LabelBasedNodeTypeRepresentationStrategy;
import org.springframework.util.Assert;
import java.util.HashSet;
@@ -75,8 +73,13 @@ public class RelationshipHelper {
for ( Relationship relationship : node.getRelationships( type, direction ) ) {
if ( !targetNodes.remove( relationship.getOtherNode( node ) ) ) {
if ( targetType != null ) {
Object actualTargetType = relationship.getOtherNode( node ).getProperty( "__type__" );
Object actualTargetType = tryDetermineTypeAssumingIndexBasedStrategy(relationship, node);
if (actualTargetType == null) {
actualTargetType = tryDetermineTypeAssumingLabelBasedStrategy(relationship, node);
}
if (actualTargetType == null) {
throw new RuntimeException("Neither a property or Label could be found to work out what the type of the node is at the other end of the relationship ");
}
try {
if (! targetType.isAssignableFrom(Class.forName((String) actualTargetType))) {
continue;
@@ -91,6 +94,28 @@ public class RelationshipHelper {
}
}
private Object tryDetermineTypeAssumingLabelBasedStrategy(Relationship relationship,Node node) {
ResourceIterable<Label> labels = relationship.getOtherNode(node).getLabels();
ResourceIterator<Label> iterator = labels.iterator();
try {
while (iterator.hasNext()) {
Label l = iterator.next();
if (l.name().startsWith(LabelBasedNodeTypeRepresentationStrategy.LABELSTRATEGY_PREFIX)) {
return l.name().substring(LabelBasedNodeTypeRepresentationStrategy.LABELSTRATEGY_PREFIX.length());
}
}
} finally {
iterator.close();
}
return null;
}
private Object tryDetermineTypeAssumingIndexBasedStrategy(Relationship relationship,Node node) {
return relationship.getOtherNode( node ).getProperty( "__type__" , null);
}
protected void createAddedRelationships(Node node, Set<Node> targetNodes) {
for (Node targetNode : targetNodes) {
createSingleRelationship(node, targetNode);

View File

@@ -39,6 +39,7 @@ import org.springframework.data.neo4j.support.Neo4jTemplate;
import org.springframework.data.neo4j.support.index.NoSuchIndexException;
import org.springframework.data.neo4j.support.index.NullReadableIndex;
import org.springframework.data.neo4j.support.query.QueryEngine;
import org.springframework.data.neo4j.support.typerepresentation.LabelBasedNodeTypeRepresentationStrategy;
import java.util.*;
@@ -347,7 +348,9 @@ public abstract class AbstractGraphRepository<S extends PropertyContainer, T> im
@Override
public EndResult<T> findAll(Sort sort) {
CypherQuery cq = new CypherQuery(template.getEntityType(clazz).getEntity(),template);
// TODO : Do nicer mechanism for working out if labels are in play
boolean useLabels = template.getInfrastructure().getNodeTypeRepresentationStrategy() instanceof LabelBasedNodeTypeRepresentationStrategy;
CypherQuery cq = new CypherQuery(template.getEntityType(clazz).getEntity(),template,useLabels);
return query(cq.toQueryString(sort), Collections.EMPTY_MAP);
}

View File

@@ -40,19 +40,28 @@ public class CypherQuery implements CypherQueryDefinition {
private final Neo4jPersistentEntity<?> entity;
private final Neo4jTemplate template;
private boolean isCountQuery = false;
private boolean useLabels = false;
public CypherQuery(final Neo4jPersistentEntity<?> entity, Neo4jTemplate template) {
public CypherQuery(final Neo4jPersistentEntity<?> entity, Neo4jTemplate template, boolean useLabels) {
this.entity = entity;
this.template = template;
this.useLabels = useLabels;
}
private String getEntityName(Neo4jPersistentEntity<?> entity) {
return variableContext.getVariableFor(entity);
}
private String defaultStartClause(Neo4jPersistentEntity<?> entity) {
return String.format(QueryTemplates.DEFAULT_START_CLAUSE, getEntityName(entity), entity
.getEntityType().getAlias());
private String defaultLegacyStartClause(Neo4jPersistentEntity<?> entity) {
return String.format(QueryTemplates.DEFAULT_INDEXBASED_START_CLAUSE,
getEntityName(entity),
entity.getEntityType().getAlias());
}
private String defaultMatchBasedStartClause(Neo4jPersistentEntity<?> entity) {
return String.format(QueryTemplates.DEFAULT_LABELBASED_MATCH_START_CLAUSE,
getEntityName(entity),
entity.getEntityType().getAlias());
}
public void addPart(Part part, PersistentPropertyPath<Neo4jPersistentProperty> path) {
@@ -69,10 +78,18 @@ public class CypherQuery implements CypherQueryDefinition {
}
} else if (leafProperty.isRelationship()) {
startClauses.add(new NodeEntityMatchingStartClause(partInfo));
whereClauses.add(new TypeRestrictingWhereClause(new PartInfo(path, variableContext.getVariableFor(entity), part, -1), entity, template));
if (useLabels) {
whereClauses.add(new LabelBasedTypeRestrictingWhereClause(new PartInfo(path, variableContext.getVariableFor(entity), part, -1), entity, template));
} else {
whereClauses.add(new IndexBasedTypeRestrictingWhereClause(new PartInfo(path, variableContext.getVariableFor(entity), part, -1), entity, template));
}
} else if (leafProperty.isIdProperty()) {
startClauses.add(new NodeEntityMatchingStartClause(partInfo));
whereClauses.add(new TypeRestrictingWhereClause(new PartInfo(path, variableContext.getVariableFor(entity), part, -1), entity, template));
if (useLabels) {
whereClauses.add(new LabelBasedTypeRestrictingWhereClause(new PartInfo(path, variableContext.getVariableFor(entity), part, -1), entity, template));
} else {
whereClauses.add(new IndexBasedTypeRestrictingWhereClause(new PartInfo(path, variableContext.getVariableFor(entity), part, -1), entity, template));
}
} else {
throw new IllegalStateException("Error "+part+" points neither to a primitive nor a entity property of "+entity);
}
@@ -154,20 +171,21 @@ public class CypherQuery implements CypherQueryDefinition {
}
private String render() {
String startClauses = collectionToDelimitedString(this.startClauses, ", ");
String legacyStartClauses = collectionToDelimitedString(this.startClauses, ", ");
String matchClauses = toQueryString(this.matchClauses);
String whereClauses = collectionToDelimitedString(this.whereClauses, " AND ");
StringBuilder builder = new StringBuilder("START ");
StringBuilder builder = new StringBuilder("");
if (hasText(startClauses)) {
builder.append(startClauses);
} else {
builder.append(defaultStartClause(entity));
boolean matchKeyWordUsed = false;
boolean legacyStartClauseUsed = buildInLegacyStartClauses(builder,legacyStartClauses);
if (!legacyStartClauseUsed && useLabels) {
matchKeyWordUsed = true;
builder.append(" MATCH ").append(defaultMatchBasedStartClause(entity));
}
if (hasText(matchClauses)) {
builder.append(" MATCH ").append(matchClauses);
builder.append(matchKeyWordUsed ? " , " : " MATCH ");
builder.append(matchClauses);
}
if (hasText(whereClauses)) {
@@ -183,6 +201,22 @@ public class CypherQuery implements CypherQueryDefinition {
return builder.toString();
}
/**
* Note: This will change to get rid of the start clauses completely but
* for now we just get it to work!
*/
private boolean buildInLegacyStartClauses(StringBuilder builder, String legacyStartClauses) {
if (hasText(legacyStartClauses)) {
builder.append("START ").append(legacyStartClauses);
return true;
} else if (!useLabels) {
// TODO: Need to change index based stuff to also not use START
builder.append("START ").append(defaultLegacyStartClause(entity));
return true;
}
return false;
}
private String addSorts(Sort sort) {
final List<String> sorts = formatSorts(sort);

View File

@@ -20,6 +20,7 @@ import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.neo4j.mapping.Neo4jPersistentEntity;
import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty;
import org.springframework.data.neo4j.support.Neo4jTemplate;
import org.springframework.data.neo4j.support.typerepresentation.LabelBasedNodeTypeRepresentationStrategy;
import org.springframework.data.repository.query.parser.Part;
/**
@@ -35,7 +36,9 @@ class CypherQueryBuilder {
public CypherQueryBuilder(MappingContext<? extends Neo4jPersistentEntity<?>, Neo4jPersistentProperty> context, Class<?> type, Neo4jTemplate template) {
this.context = context;
Neo4jPersistentEntity<?> entity = context.getPersistentEntity(type);
this.query = new CypherQuery(entity, template);
// TODO : Do nicer mechanism for working out if labels are in play
boolean useLabels = template.getInfrastructure().getNodeTypeRepresentationStrategy() instanceof LabelBasedNodeTypeRepresentationStrategy;
this.query = new CypherQuery(entity, template, useLabels);
}
public CypherQueryBuilder asCountQuery() {

View File

@@ -22,20 +22,29 @@ import org.springframework.data.neo4j.support.mapping.StoredEntityType;
import java.util.HashSet;
import java.util.Set;
import static org.springframework.data.neo4j.repository.query.QueryTemplates.WHERE_TYPE_CHECK;
import static org.springframework.data.neo4j.repository.query.QueryTemplates.INDEXBASED_WHERE_TYPE_CHECK;
import static org.springframework.util.StringUtils.collectionToCommaDelimitedString;
public class TypeRestrictingWhereClause extends WhereClause {
/**
* Representation of a Cypher {@literal where} clause specifically for
* use to narrow the results based on particular entity types, where
* those entities can be identified via the special __type__ property
* present on all nodes when the Index Based Type Representation
* Strategy is in use
*
* @author Nicki Watt
*/
public class IndexBasedTypeRestrictingWhereClause extends WhereClause {
private String aliases;
public TypeRestrictingWhereClause(PartInfo partInfo, Neo4jPersistentEntity entity, Neo4jTemplate template) {
public IndexBasedTypeRestrictingWhereClause(PartInfo partInfo, Neo4jPersistentEntity entity, Neo4jTemplate template) {
super(partInfo, template);
aliases = collectionToCommaDelimitedString(collectAliases(entity.getEntityType()));
}
@Override
public String toString() {
return String.format(WHERE_TYPE_CHECK, partInfo.getIdentifier(), aliases);
return String.format(INDEXBASED_WHERE_TYPE_CHECK, partInfo.getIdentifier(), aliases);
}
private Set<String> collectAliases(StoredEntityType entityType) {

View File

@@ -25,6 +25,6 @@ public class IndexRestrictingStartClause extends StartClause {
@Override
public String toString() {
return String.format(QueryTemplates.DEFAULT_START_CLAUSE, getPartInfo().getIdentifier(), className);
return String.format(QueryTemplates.DEFAULT_INDEXBASED_START_CLAUSE, getPartInfo().getIdentifier(), className);
}
}

View File

@@ -0,0 +1,59 @@
/**
* 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.mapping.Neo4jPersistentEntity;
import org.springframework.data.neo4j.support.Neo4jTemplate;
import org.springframework.data.neo4j.support.mapping.StoredEntityType;
import java.util.HashSet;
import java.util.Set;
import static org.springframework.data.neo4j.repository.query.QueryTemplates.INDEXBASED_WHERE_TYPE_CHECK;
import static org.springframework.data.neo4j.repository.query.QueryTemplates.LABELBASED_WHERE_TYPE_CHECK;
import static org.springframework.util.StringUtils.*;
/**
* Representation of a Cypher {@literal where} clause specifically for
* use to narrow the results based on particular entity types, where
* those entities can be identified via specific Labels (as per the
* Label Based Type Representation Strategy)
*
* @author Nicki Watt
*/
public class LabelBasedTypeRestrictingWhereClause extends WhereClause {
private String aliases;
public LabelBasedTypeRestrictingWhereClause(PartInfo partInfo, Neo4jPersistentEntity entity, Neo4jTemplate template) {
super(partInfo, template);
Object[] aliasAsArray = collectAliases(entity.getEntityType()).toArray();
aliases = arrayToDelimitedString(aliasAsArray, ":");
}
@Override
public String toString() {
return String.format(LABELBASED_WHERE_TYPE_CHECK, partInfo.getIdentifier(), aliases);
}
private Set<String> collectAliases(StoredEntityType entityType) {
Set<String> aliases = new HashSet<String>();
aliases.add("`"+entityType.getAlias().toString()+"`");
for (StoredEntityType superType : entityType.getSuperTypes()) {
aliases.addAll(collectAliases(superType));
}
return aliases;
}
}

View File

@@ -45,13 +45,16 @@ public abstract class QueryTemplates {
static final String MATCH_CLAUSE = "`%s`%s`%s`";
static final String MATCH_CLAUSE2 = "%s%s`%s`";
static final String DEFAULT_START_CLAUSE = "`%s`=node:__types__(className=\"%s\")";
static final String DEFAULT_INDEXBASED_START_CLAUSE = "`%s`=node:__types__(className=\"%s\")";
static final String DEFAULT_LABELBASED_MATCH_START_CLAUSE = "`%s`:`%s`";
public static final String START_NODE_LOOKUP = "`%s`=node({%d})";
static final String SKIP_LIMIT = " SKIP %d LIMIT %d";
static final String START_CLAUSE_INDEX_LOOKUP = "`%s`=node:`%s`(`%s`=" + PLACEHOLDER + ")";
static final String START_CLAUSE_INDEX_QUERY = "`%s`=node:`%s`(" + PLACEHOLDER + ")";
static final String WHERE_CLAUSE_1 = "`%1$s`.`%2$s` %3$s {%4$d}";
static final String WHERE_TYPE_CHECK = "`%1$s`.__type__ IN [%2$s]";
static final String INDEXBASED_WHERE_TYPE_CHECK = "`%1$s`.__type__ IN [%2$s]";
static final String LABELBASED_WHERE_TYPE_CHECK = "`%1$s`:%2$s";
static final String WHERE_CLAUSE_0 = "`%1$s`.`%2$s` %3$s ";
static final String SORT_CLAUSE = "%s %s";
static final String ORDER_BY_CLAUSE = " ORDER BY %s";

View File

@@ -33,7 +33,6 @@ import org.springframework.data.neo4j.conversion.DefaultConverter;
import org.springframework.data.neo4j.conversion.Result;
import org.springframework.data.neo4j.conversion.ResultConverter;
import org.springframework.data.neo4j.core.GraphDatabase;
import org.springframework.data.neo4j.core.GraphDatabaseGlobalOperations;
import org.springframework.data.neo4j.support.index.IndexType;
import org.springframework.data.neo4j.support.index.NoSuchIndexException;
import org.springframework.data.neo4j.support.query.ConversionServiceQueryResultConverter;
@@ -56,7 +55,6 @@ public class DelegatingGraphDatabase implements GraphDatabase {
private static final Logger log = LoggerFactory.getLogger(DelegatingGraphDatabase.class);
protected GraphDatabaseGlobalOperations globalOperations;
protected GraphDatabaseService delegate;
private ConversionService conversionService;
private ResultConverter resultConverter;
@@ -69,21 +67,30 @@ public class DelegatingGraphDatabase implements GraphDatabase {
public DelegatingGraphDatabase(final GraphDatabaseService delegate, ResultConverter resultConverter) {
this.delegate = delegate;
this.resultConverter = resultConverter;
this.globalOperations = new DelegatingGraphDatabaseGlobalOperations(delegate);
}
public void setConversionService(ConversionService conversionService) {
this.conversionService = conversionService;
}
@Override
public GraphDatabaseGlobalOperations getGlobalGraphOperations() {
return globalOperations;
}
@Override
public void setResultConverter(ResultConverter resultConverter) {
this.resultConverter = resultConverter;
// At present, the current config may result in the scenario where
// the query engine was requested very early on in the lifecycle
// (for Type Representation Strategy) and at that stage, only the
// default ResultConverter was available, and thus used to create
// the query engines. (TODO - Try change ordering if possible)
//
// In this case we re-initialise it to ensure it uses this latest
// result Converter (as it is currently cached)
reinitQueryEngines();
}
private void reinitQueryEngines() {
if (cypherQueryEngine != null) this.cypherQueryEngine = queryEngineFor(QueryType.Cypher, resultConverter, true);
if (gremlinQueryEngine != null) this.gremlinQueryEngine = queryEngineFor(QueryType.Gremlin, resultConverter, true);
}
@Override
@@ -195,19 +202,19 @@ public class DelegatingGraphDatabase implements GraphDatabase {
}
@SuppressWarnings("unchecked")
public <T> QueryEngine<T> queryEngineFor(QueryType type,ResultConverter resultConverter) {
private <T> QueryEngine<T> queryEngineFor(QueryType type,ResultConverter resultConverter,boolean reinit) {
switch (type) {
case Cypher: {
if (cypherQueryEngine==null)
if (reinit || cypherQueryEngine==null)
synchronized (this) {
if (cypherQueryEngine==null) cypherQueryEngine = createCypherQueryEngine(resultConverter);
if (reinit || cypherQueryEngine==null) cypherQueryEngine = createCypherQueryEngine(resultConverter);
}
return (QueryEngine<T>) cypherQueryEngine;
}
case Gremlin: {
if (gremlinQueryEngine==null) {
if (reinit || gremlinQueryEngine==null) {
synchronized (this) {
if (gremlinQueryEngine==null) gremlinQueryEngine=createGremlinQueryEngine(resultConverter);
if (reinit || gremlinQueryEngine==null) gremlinQueryEngine=createGremlinQueryEngine(resultConverter);
}
}
return (QueryEngine<T>) gremlinQueryEngine;
@@ -216,6 +223,11 @@ public class DelegatingGraphDatabase implements GraphDatabase {
throw new IllegalArgumentException("Unknown Query Engine Type "+type);
}
@SuppressWarnings("unchecked")
public <T> QueryEngine<T> queryEngineFor(QueryType type,ResultConverter resultConverter) {
return queryEngineFor(type,resultConverter,false);
}
private <T> QueryEngine<T> createGremlinQueryEngine(ResultConverter resultConverter) {
if (!ClassUtils.isPresent("com.tinkerpop.blueprints.pgm.impls.neo4j.Neo4jGraph", getClass().getClassLoader())) {
return new FailingQueryEngine<T>("Gremlin");

View File

@@ -42,6 +42,10 @@ public class CypherQueryEngine implements QueryEngine<Map<String,Object>> {
this.executionEngine = new ExecutionEngine(graphDatabaseService);
}
public ResultConverter getResultConverter() {
return resultConverter;
}
@SuppressWarnings("unchecked")
@Override
public Result<Map<String, Object>> query(String statement, Map<String, Object> params) {

View File

@@ -1,121 +0,0 @@
/**
* Copyright 2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.neo4j.support.typerepresentation;
import org.neo4j.graphdb.DynamicLabel;
import org.neo4j.graphdb.Label;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.ResourceIterable;
import org.neo4j.helpers.collection.ClosableIterable;
import org.springframework.data.neo4j.core.GraphDatabase;
import org.springframework.data.neo4j.core.GraphDatabaseGlobalOperations;
import org.springframework.data.neo4j.core.NodeTypeRepresentationStrategy;
import org.springframework.data.neo4j.support.mapping.ResourceIterableClosableIterable;
import org.springframework.data.neo4j.support.mapping.StoredEntityType;
/**
* Provides a Node Type Representation Strategy which makes use of Labels, and specifically
* uses the Core API as the mechanism for dealing with this. (This is inline with how
* the original Node Type Representation Strategies used to work - moving forward only
* the Cypher based one will be used, however this exists for comparison purposes at this
* point in time in the development)
*
* TODO - Delete me!
*
* @author Nicki Watt
* @since 24-09-2013
*/
public class CoreAPIBasedLabelingNodeTypeRepresentationStrategy implements NodeTypeRepresentationStrategy {
// option A : store type in entity
// public static final String TYPE_PROPERTY_NAME = "__type__";
// option B : add special type label
public static final String TYPE_LABEL_PREFIX = "__TYPE__";
protected GraphDatabase graphDb;
protected final Class<Node> clazz;
public CoreAPIBasedLabelingNodeTypeRepresentationStrategy(GraphDatabase graphDb) {
this.graphDb = graphDb;
this.clazz = Node.class;
}
@Override
public void writeTypeTo(Node state, StoredEntityType type) {
if (type == null || !type.isNodeEntity()) return;
ResourceIterable<Label> labels = state.getLabels();
if (labels.iterator().hasNext()) {
return; // already there
}
addLabel(state,type,true);
for (StoredEntityType superType : type.getSuperTypes()) {
addLabel(state, superType, false);
}
}
private void addLabel(Node state, StoredEntityType type, boolean isPrimary) {
String alias = type.getAlias().toString();
state.addLabel(DynamicLabel.label(alias));
if (isPrimary) {
// option A : store type in entity
// state.setProperty(TYPE_PROPERTY_NAME,alias);
// option B : add special type label
state.addLabel(DynamicLabel.label(TYPE_LABEL_PREFIX + alias));
}
}
@Override
public <U> ClosableIterable<Node> findAll(StoredEntityType type) {
GraphDatabaseGlobalOperations globalOps = graphDb.getGlobalGraphOperations();
final ResourceIterable<Node> rin = globalOps.getAllNodesWithLabel(DynamicLabel.label(type.getAlias().toString()));
return new ResourceIterableClosableIterable(rin);
}
@Override
public long count(StoredEntityType type) {
long count = 0;
GraphDatabaseGlobalOperations globalOps = graphDb.getGlobalGraphOperations();
for (Node n : globalOps.getAllNodesWithLabel(DynamicLabel.label(type.getAlias().toString()))) {
count++;
}
return count;
}
@Override
public Object readAliasFrom(Node state) {
if (state == null)
throw new IllegalArgumentException("Node is null");
// Option A: Derive alias from property
// return state.getProperty(TYPE_PROPERTY_NAME);
// Option B: Derive alias from special Label
for (Label label: state.getLabels()) {
if (label.name().startsWith(TYPE_LABEL_PREFIX)) {
return label.name().substring(TYPE_LABEL_PREFIX.length());
}
}
throw new IllegalStateException("No primary SDN label exists .. (i.e one with SDN_) ");
}
@Override
public void preEntityRemoval(Node state) {
// don't think we need to do anything here!
}
}

View File

@@ -1,6 +1,7 @@
package org.springframework.data.neo4j.support.typerepresentation;
import org.neo4j.graphdb.Node;
import org.springframework.data.neo4j.conversion.Result;
import org.springframework.data.neo4j.repository.query.CypherQuery;
import org.springframework.data.neo4j.support.query.QueryEngine;
@@ -45,7 +46,8 @@ public class LabelBasedStrategyCypherHelper {
public boolean doesNodeHaveLabel(Long nodeId, String label) {
String query = String.format(CYPHER_COUNT_LABELS_ON_NODE, label);
long labelCount = queryEngine.query(query, getParamsWithNodeId(nodeId)).to(Long.class).single();
Result<CypherQuery> result = queryEngine.query(query, getParamsWithNodeId(nodeId));
long labelCount = result.to(Number.class).single().longValue();
return labelCount > 0;
}
@@ -72,7 +74,8 @@ public class LabelBasedStrategyCypherHelper {
public long countNodesWithLabel(String label) {
String query = String.format(CYPHER_RETURN_COUNT_OF_NODES_WITH_LABEL, label);
return queryEngine.query(query, Collections.EMPTY_MAP).to(Long.class).single();
Result<CypherQuery> result = queryEngine.query(query, Collections.EMPTY_MAP);
return result.to(Number.class).single().longValue();
}
public Iterable<String> getLabelsForNode(long nodeId) {

View File

@@ -56,7 +56,7 @@ public class TypeRepresentationStrategyFactory {
if (SubReferenceNodeTypeRepresentationStrategy.isStrategyAlreadyInUse(graphDatabaseService)) return Strategy.SubRef;
if (LabelBasedNodeTypeRepresentationStrategy.isStrategyAlreadyInUse(graphDatabaseService)) return Strategy.Labeled;
tx.success();
return Strategy.Indexed;
return Strategy.Labeled;
}
}
@@ -92,7 +92,7 @@ public class TypeRepresentationStrategyFactory {
@Override
public RelationshipTypeRepresentationStrategy getRelationshipTypeRepresentationStrategy(GraphDatabase graphDatabaseService, IndexProvider indexProvider) {
return new NoopRelationshipTypeRepresentationStrategy();
return new IndexBasedRelationshipTypeRepresentationStrategy(graphDatabaseService, indexProvider);
}
},
Indexed {

View File

@@ -0,0 +1,253 @@
/**
* 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.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.neo4j.core.NodeTypeRepresentationStrategy;
import org.springframework.data.neo4j.repository.query.CypherQueryBuilder;
import org.springframework.data.neo4j.repository.query.Person;
import org.springframework.data.neo4j.support.Infrastructure;
import org.springframework.data.neo4j.support.Neo4jTemplate;
import org.springframework.data.neo4j.support.mapping.Neo4jMappingContext;
import org.springframework.data.neo4j.support.typerepresentation.IndexBasedNodeTypeRepresentationStrategy;
import org.springframework.data.repository.query.parser.Part;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.when;
/**
* Base Unit test definitions for {@link org.springframework.data.neo4j.repository.query.CypherQueryBuilder}.
* Depending on which Type Representation Strategy is in use, the queries will look
* different, thus there are currently two subclasses which deal with each case.
* TODO - There is some consolidation which can still be done here
*
* @author Oliver Gierke & Nicki Watt
*/
public abstract class AbstractCypherQueryBuilderTestBase {
CypherQueryBuilder query;
String queryString;
final static String CLASS_NAME = Person.class.getSimpleName();
@Before
public void setUp() {
Neo4jMappingContext context = new Neo4jMappingContext();
Neo4jTemplate template = Mockito.mock(Neo4jTemplate.class);
Infrastructure inf = Mockito.mock(Infrastructure.class);
when (template.getInfrastructure()).thenReturn(inf);
when (inf.getNodeTypeRepresentationStrategy()).thenReturn(getNodeTypeRepresentationStrategy());
query = new CypherQueryBuilder(context, Person.class, template);
queryString = null;
}
abstract NodeTypeRepresentationStrategy getNodeTypeRepresentationStrategy();
/**
* To be used in conjunction with
* buildQueryForCreatesQueryForSimplePropertyReference()
*/
@Test
public abstract void createsQueryForSimplePropertyReference();
protected void buildQueryForCreatesQueryForSimplePropertyReference()
{
Part part = new Part("name", Person.class);
query.addRestriction(part);
queryString = query.toString();
}
/**
* To be used in conjunction with
* buildQueryForCreatesQueryForLikePropertyIndex()
*/
@Test
public abstract void createsQueryForLikePropertyIndex();
protected void buildQueryForCreatesQueryForLikePropertyIndex() {
Part part = new Part("titleLike", Person.class);
query.addRestriction(part);
queryString = query.toString();
}
/**
* To be used in conjunction with
* buildQueryForCreatesQueryForLikeProperty()
*/
@Test
public abstract void createsQueryForLikeProperty();
public void buildQueryForCreatesQueryForLikeProperty() {
Part part = new Part("infoLike", Person.class);
query.addRestriction(part);
queryString = query.toString();
}
/**
* To be used in conjunction with
* buildQueryForCreatesQueryForGreaterThanPropertyReference()
*/
@Test
public abstract void createsQueryForGreaterThanPropertyReference();
public void buildQueryForCreatesQueryForGreaterThanPropertyReference() {
Part part = new Part("ageGreaterThan", Person.class);
query.addRestriction(part);
queryString = query.toString();
}
/**
* To be used in conjunction with
* buildQueryForCreatesQueryForTwoPropertyExpressions()
*/
@Test
public abstract void createsQueryForTwoPropertyExpressions();
public void buildQueryForCreatesQueryForTwoPropertyExpressions() {
query.addRestriction(new Part("ageGreaterThan", Person.class));
query.addRestriction(new Part("info", Person.class));
queryString = query.toString();
}
/**
* To be used in conjunction with
* buildQueryForCreatesQueryForIsNullPropertyReference()
*/
@Test
public abstract void createsQueryForIsNullPropertyReference();
public void buildQueryForCreatesQueryForIsNullPropertyReference() {
Part part = new Part("ageIsNull", Person.class);
query.addRestriction(part);
queryString = query.toString();
}
/**
* To be used in conjunction with
* buildQueryForCreatesQueryForPropertyOnRelationShipReference()
*/
@Test
public abstract void createsQueryForPropertyOnRelationShipReference();
public void buildQueryForCreatesQueryForPropertyOnRelationShipReference() {
Part part = new Part("group.name", Person.class);
query.addRestriction(part);
queryString = query.toString();
}
/**
* To be used in conjunction with
* buildQueryForCreatesQueryForMultipleStartClauses()
*/
@Test
public abstract void createsQueryForMultipleStartClauses();
public void buildQueryForCreatesQueryForMultipleStartClauses() {
query.addRestriction(new Part("name", Person.class));
query.addRestriction(new Part("group.name", Person.class));
queryString = query.toString();
}
/**
* To be used in conjunction with
* buildQueryForCreatesSimpleWhereClauseCorrectly()
*/
@Test
public abstract void createsSimpleWhereClauseCorrectly();
public void buildQueryForCreatesSimpleWhereClauseCorrectly() {
query.addRestriction(new Part("age", Person.class));
queryString = query.toString();
}
/**
* To be used in conjunction with
* buildQueryForCreatesSimpleTraversalClauseCorrectly()
*/
@Test
public abstract void createsSimpleTraversalClauseCorrectly();
public void buildQueryForCreatesSimpleTraversalClauseCorrectly() {
query.addRestriction(new Part("group", Person.class));
queryString = query.toString();
}
/**
* To be used in conjunction with
* buildQueryForBuildsComplexQueryCorrectly()
*/
@Test
public abstract void buildsComplexQueryCorrectly();
public void buildQueryForBuildsComplexQueryCorrectly() {
query.addRestriction(new Part("name", Person.class));
query.addRestriction(new Part("groupName", Person.class));
query.addRestriction(new Part("ageGreaterThan", Person.class));
query.addRestriction(new Part("groupMembersAge", Person.class));
queryString = query.toString();
}
/**
* To be used in conjunction with
* buildQueryForBuildsQueryWithSort()
*/
@Test
public abstract void buildsQueryWithSort();
public void buildQueryForBuildsQueryWithSort() {
query.addRestriction(new Part("name",Person.class));
queryString = query.buildQuery(new Sort("person.name")).toQueryString();
}
/**
* To be used in conjunction with
* buildQueryForBuildsQueryWithSort()
*/
@Test
public abstract void buildsQueryWithTwoSorts();
public void buildQueryForBuildsQueryWithTwoSorts() {
query.addRestriction(new Part("name",Person.class));
Sort sort = new Sort(new Sort.Order("person.name"),new Sort.Order(Sort.Direction.DESC, "person.age"));
queryString = query.buildQuery(sort).toQueryString();
}
/**
* To be used in conjunction with
* buildQueryForBuildsQueryWithSort()
*/
@Test
public abstract void buildsQueryWithPage();
public void buildQueryForBuildsQueryWithPage() {
query.addRestriction(new Part("name",Person.class));
Pageable pageable = new PageRequest(3,10,new Sort("person.name"));
queryString = query.buildQuery().toQueryString(pageable);
}
/**
* To be used in conjunction with
* buildQueryForShouldFindByNodeEntityForIncomingRelationship()
*/
@Test
public abstract void shouldFindByNodeEntity() throws Exception;
public void buildQueryForShouldFindByNodeEntity() throws Exception {
query.addRestriction(new Part("pet", Person.class));
queryString = query.toString();
}
/**
* To be used in conjunction with
* buildQueryForShouldFindByNodeEntityForIncomingRelationship()
*/
@Test
public abstract void shouldFindByNodeEntityForIncomingRelationship();
public void buildQueryForShouldFindByNodeEntityForIncomingRelationship() {
query.addRestriction(new Part("group", Person.class));
queryString = query.toString();
}
}

View File

@@ -17,6 +17,7 @@ package org.springframework.data.neo4j.repository.query;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.neo4j.index.lucene.ValueContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.neo4j.annotation.GraphId;
import org.springframework.data.neo4j.annotation.GraphProperty;
@@ -47,12 +48,7 @@ import java.util.concurrent.TimeUnit;
import static java.util.Arrays.asList;
import static org.junit.Assert.assertEquals;
import org.neo4j.index.lucene.ValueContext;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@TestExecutionListeners({CleanContextCacheTestExecutionListener.class, DependencyInjectionTestExecutionListener.class, TransactionalTestExecutionListener.class})
public class DerivedFinderMethodTests {
public abstract class AbstractDerivedFinderMethodTestBase {
@NodeEntity
public static class Thing {
@@ -110,11 +106,7 @@ public class DerivedFinderMethodTests {
}
@Test
public void testQueryWithEntityGraphId() throws Exception {
assertRepositoryQueryMethod(ThingRepository.class, "findByOwnerId",new Object[]{123},
"START `thing_owner`=node({0}) MATCH `thing`-[:`owner`]->`thing_owner` WHERE `thing`.__type__ IN ['org.springframework.data.neo4j.repository.query.DerivedFinderMethodTests$Thing'] ",
123);
}
public abstract void testQueryWithEntityGraphId() throws Exception;
@Test
public void testIndexQueryWithTwoParams() throws Exception {
@@ -311,7 +303,7 @@ public class DerivedFinderMethodTests {
ValueContext.numeric(10));
}
private void assertRepositoryQueryMethod(Class<ThingRepository> repositoryClass, String methodName, Object[] paramValues, String expectedQuery, Object...expectedParam) {
protected void assertRepositoryQueryMethod(Class<ThingRepository> repositoryClass, String methodName, Object[] paramValues, String expectedQuery, Object...expectedParam) {
Method method = methodFor(repositoryClass, methodName);
DerivedCypherRepositoryQuery derivedCypherRepositoryQuery = new DerivedCypherRepositoryQuery(ctx, new GraphQueryMethod(method, new DefaultRepositoryMetadata(repositoryClass), null, ctx), template);
Parameters<?, ?> parameters = new DefaultParameters(method);

View File

@@ -0,0 +1,166 @@
/**
* 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.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import org.springframework.data.neo4j.core.NodeTypeRepresentationStrategy;
import org.springframework.data.neo4j.support.typerepresentation.IndexBasedNodeTypeRepresentationStrategy;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
/**
* Unit tests for {@link org.springframework.data.neo4j.repository.query.CypherQueryBuilder}
* Specifically where the Index Based Type Representation Strategy is being used.
*
* @author Oliver Gierke & Nicki Watt
*/
public class CypherQueryBuilderForIndexBasedTRSUnitTests extends AbstractCypherQueryBuilderTestBase {
private final static String DEFAULT_START_CLAUSE = "START `person`=node:__types__(className=\"" + CLASS_NAME + "\")";
@Before
public void setUp() {
super.setUp();
}
protected NodeTypeRepresentationStrategy getNodeTypeRepresentationStrategy() {
return Mockito.mock(IndexBasedNodeTypeRepresentationStrategy.class);
}
@Override
@Test
public void createsQueryForSimplePropertyReference() {
buildQueryForCreatesQueryForSimplePropertyReference();
assertThat(queryString,
is("START `person`=node:`Person`(`name`={0}) RETURN `person`"));
}
@Override
@Test
public void createsQueryForLikePropertyIndex() {
buildQueryForCreatesQueryForLikePropertyIndex();
assertThat(queryString, is("START `person`=node:`title`({0}) RETURN `person`"));
}
@Override
@Test
public void createsQueryForLikeProperty() {
buildQueryForCreatesQueryForLikeProperty();
assertThat(queryString, is(DEFAULT_START_CLAUSE+" WHERE `person`.`info` =~ {0} RETURN `person`"));
}
@Override
@Test
public void createsQueryForGreaterThanPropertyReference() {
buildQueryForCreatesQueryForGreaterThanPropertyReference();
assertThat(queryString, is(DEFAULT_START_CLAUSE+" WHERE `person`.`age` > {0} RETURN `person`"));
}
@Override
@Test
public void createsQueryForTwoPropertyExpressions() {
buildQueryForCreatesQueryForTwoPropertyExpressions();
assertThat(queryString, is(DEFAULT_START_CLAUSE+" WHERE `person`.`age` > {0} AND `person`.`info` = {1} RETURN `person`"));
}
@Override
@Test
public void createsQueryForIsNullPropertyReference() {
buildQueryForCreatesQueryForIsNullPropertyReference();
assertThat(queryString, is(DEFAULT_START_CLAUSE+" WHERE `person`.`age` is null RETURN `person`"));
}
@Override
@Test
public void createsQueryForPropertyOnRelationShipReference() {
buildQueryForCreatesQueryForPropertyOnRelationShipReference();
assertThat(queryString, is("START `person_group`=node:`Group`(`name`={0}) MATCH `person`<-[:`members`]-`person_group` RETURN `person`"));
}
@Override
@Test
public void createsQueryForMultipleStartClauses() {
buildQueryForCreatesQueryForMultipleStartClauses();
assertThat(queryString,
is("START `person`=node:`Person`(`name`={0}), `person_group`=node:`Group`(`name`={1}) MATCH `person`<-[:`members`]-`person_group` RETURN `person`"));
}
@Override
@Test
public void createsSimpleWhereClauseCorrectly() {
buildQueryForCreatesSimpleWhereClauseCorrectly();
assertThat(queryString, is(DEFAULT_START_CLAUSE +" WHERE `person`.`age` = {0} RETURN `person`"));
}
@Override
@Test
public void createsSimpleTraversalClauseCorrectly() {
buildQueryForCreatesSimpleTraversalClauseCorrectly();
assertThat(queryString, is("START `person_group`=node({0}) MATCH `person`<-[:`members`]-`person_group` WHERE `person`.__type__ IN ['Person'] RETURN `person`"));
}
@Override
@Test
public void buildsComplexQueryCorrectly() {
buildQueryForBuildsComplexQueryCorrectly();
assertThat(queryString, is(
"START `person`=node:`Person`(`name`={0}), `person_group`=node:`Group`(`name`={1}) " +
"MATCH `person`<-[:`members`]-`person_group`, `person`<-[:`members`]-`person_group`-[:`members`]->`person_group_members` " +
"WHERE `person`.`age` > {2} AND `person_group_members`.`age` = {3} " +
"RETURN `person`"
));
}
@Override
@Test
public void buildsQueryWithSort() {
buildQueryForBuildsQueryWithSort();
assertThat(queryString, is("START `person`=node:`Person`(`name`={0}) RETURN `person` ORDER BY person.name ASC"));
}
@Override
@Test
public void buildsQueryWithTwoSorts() {
buildQueryForBuildsQueryWithTwoSorts();
assertThat(queryString, is("START `person`=node:`Person`(`name`={0}) RETURN `person` ORDER BY person.name ASC,person.age DESC"));
}
@Override
@Test
public void buildsQueryWithPage() {
buildQueryForBuildsQueryWithPage();
assertThat(queryString, is("START `person`=node:`Person`(`name`={0}) RETURN `person` ORDER BY person.name ASC SKIP 30 LIMIT 10"));
}
@Override
@Test
public void shouldFindByNodeEntity() throws Exception {
buildQueryForShouldFindByNodeEntity();
assertThat(queryString, is("START `person_pet`=node({0}) MATCH `person`-[:`owns`]->`person_pet` WHERE `person`.__type__ IN ['Person'] RETURN `person`"));
}
@Override
@Test
public void shouldFindByNodeEntityForIncomingRelationship() {
buildQueryForShouldFindByNodeEntityForIncomingRelationship();
assertThat(queryString, is("START `person_group`=node({0}) MATCH `person`<-[:`members`]-`person_group` WHERE `person`.__type__ IN ['Person'] RETURN `person`"));
}
}

View File

@@ -0,0 +1,169 @@
/**
* 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.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import org.springframework.data.neo4j.core.NodeTypeRepresentationStrategy;
import org.springframework.data.neo4j.support.typerepresentation.LabelBasedNodeTypeRepresentationStrategy;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
/**
* Unit tests for {@link org.springframework.data.neo4j.repository.query.CypherQueryBuilder}
* Specifically where the Label Type Representation Strategy is being used.
*
* @author Oliver Gierke & Nicki Watt
*/
public class CypherQueryBuilderForLabelBasedTRSUnitTests extends AbstractCypherQueryBuilderTestBase {
private final static String DEFAULT_START_CLAUSE = " MATCH `person`:`" + CLASS_NAME + "`";
@Before
public void setUp() {
super.setUp();
}
protected NodeTypeRepresentationStrategy getNodeTypeRepresentationStrategy() {
return Mockito.mock(LabelBasedNodeTypeRepresentationStrategy.class);
}
@Override
@Test
public void createsQueryForSimplePropertyReference() {
buildQueryForCreatesQueryForSimplePropertyReference();
assertThat(queryString,
is("START `person`=node:`Person`(`name`={0}) RETURN `person`"));
}
@Override
@Test
public void createsQueryForLikePropertyIndex() {
buildQueryForCreatesQueryForLikePropertyIndex();
assertThat(queryString, is("START `person`=node:`title`({0}) RETURN `person`"));
}
@Override
@Test
public void createsQueryForLikeProperty() {
buildQueryForCreatesQueryForLikeProperty();
assertThat(queryString, is(DEFAULT_START_CLAUSE+" WHERE `person`.`info` =~ {0} RETURN `person`"));
}
@Override
@Test
public void createsQueryForGreaterThanPropertyReference() {
buildQueryForCreatesQueryForGreaterThanPropertyReference();
assertThat(queryString, is(DEFAULT_START_CLAUSE+" WHERE `person`.`age` > {0} RETURN `person`"));
}
@Override
@Test
public void createsQueryForTwoPropertyExpressions() {
buildQueryForCreatesQueryForTwoPropertyExpressions();
assertThat(queryString, is(DEFAULT_START_CLAUSE+" WHERE `person`.`age` > {0} AND `person`.`info` = {1} RETURN `person`"));
}
@Override
@Test
public void createsQueryForIsNullPropertyReference() {
buildQueryForCreatesQueryForIsNullPropertyReference();
assertThat(queryString, is(DEFAULT_START_CLAUSE+" WHERE `person`.`age` is null RETURN `person`"));
}
@Override
@Test
public void createsQueryForPropertyOnRelationShipReference() {
buildQueryForCreatesQueryForPropertyOnRelationShipReference();
assertThat(queryString, is("START `person_group`=node:`Group`(`name`={0}) MATCH `person`<-[:`members`]-`person_group` RETURN `person`"));
}
@Override
@Test
public void createsQueryForMultipleStartClauses() {
buildQueryForCreatesQueryForMultipleStartClauses();
assertThat(queryString,
is("START `person`=node:`Person`(" +
"`name`={0}), " +
"`person_group`=node:" +
"`Group`(`name`={1}) " +
"MATCH `person`<-[:`members`]-`person_group` " +
"RETURN `person`"));
}
@Override
@Test
public void createsSimpleWhereClauseCorrectly() {
buildQueryForCreatesSimpleWhereClauseCorrectly();
assertThat(queryString, is(DEFAULT_START_CLAUSE +" WHERE `person`.`age` = {0} RETURN `person`"));
}
@Override
@Test
public void createsSimpleTraversalClauseCorrectly() {
buildQueryForCreatesSimpleTraversalClauseCorrectly();
assertThat(queryString, is("START `person_group`=node({0}) MATCH `person`<-[:`members`]-`person_group` WHERE `person`:`Person` RETURN `person`"));
}
@Override
@Test
public void buildsComplexQueryCorrectly() {
buildQueryForBuildsComplexQueryCorrectly();
assertThat(queryString, is(
"START `person`=node:`Person`(`name`={0}), `person_group`=node:`Group`(`name`={1}) " +
"MATCH `person`<-[:`members`]-`person_group`, `person`<-[:`members`]-`person_group`-[:`members`]->`person_group_members` " +
"WHERE `person`.`age` > {2} AND `person_group_members`.`age` = {3} " +
"RETURN `person`"
));
}
@Override
@Test
public void buildsQueryWithSort() {
buildQueryForBuildsQueryWithSort();
assertThat(queryString, is("START `person`=node:`Person`(`name`={0}) RETURN `person` ORDER BY person.name ASC"));
}
@Override
@Test
public void buildsQueryWithTwoSorts() {
buildQueryForBuildsQueryWithTwoSorts();
assertThat(queryString, is("START `person`=node:`Person`(`name`={0}) RETURN `person` ORDER BY person.name ASC,person.age DESC"));
}
@Override
@Test
public void buildsQueryWithPage() {
buildQueryForBuildsQueryWithPage();
assertThat(queryString, is("START `person`=node:`Person`(`name`={0}) RETURN `person` ORDER BY person.name ASC SKIP 30 LIMIT 10"));
}
@Override
@Test
public void shouldFindByNodeEntity() throws Exception {
buildQueryForShouldFindByNodeEntity();
assertThat(queryString, is("START `person_pet`=node({0}) MATCH `person`-[:`owns`]->`person_pet` WHERE `person`:`Person` RETURN `person`"));
}
@Override
@Test
public void shouldFindByNodeEntityForIncomingRelationship() {
buildQueryForShouldFindByNodeEntityForIncomingRelationship();
assertThat(queryString, is("START `person_group`=node({0}) MATCH `person`<-[:`members`]-`person_group` WHERE `person`:`Person` RETURN `person`"));
}
}

View File

@@ -1,180 +0,0 @@
/**
* Copyright 2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.neo4j.repository.query;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.neo4j.support.Neo4jTemplate;
import org.springframework.data.neo4j.support.mapping.Neo4jMappingContext;
import org.springframework.data.repository.query.parser.Part;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
/**
* Unit tests for {@link CypherQueryBuilder}.
*
* @author Oliver Gierke
*/
public class CypherQueryBuilderUnitTests {
CypherQueryBuilder query;
private final static String CLASS_NAME = Person.class.getSimpleName();
private final static String DEFAULT_START_CLAUSE = "START `person`=node:__types__(className=\"" + CLASS_NAME + "\")";
@Before
public void setUp() {
Neo4jMappingContext context = new Neo4jMappingContext();
Neo4jTemplate template = Mockito.mock(Neo4jTemplate.class);
query = new CypherQueryBuilder(context, Person.class, template);
}
@Test
public void createsQueryForSimplePropertyReference() {
Part part = new Part("name", Person.class);
query.addRestriction(part);
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 createsQueryForTwoPropertyExpressions() {
query.addRestriction(new Part("ageGreaterThan", Person.class));
query.addRestriction(new Part("info", Person.class));
assertThat(query.toString(), is(DEFAULT_START_CLAUSE+" WHERE `person`.`age` > {0} AND `person`.`info` = {1} 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() {
Part part = new Part("group.name", Person.class);
query.addRestriction(part);
assertThat(query.toString(), is("START `person_group`=node:`Group`(`name`={0}) MATCH `person`<-[:`members`]-`person_group` RETURN `person`"));
}
@Test
public void createsQueryForMultipleStartClauses() {
query.addRestriction(new Part("name", Person.class));
query.addRestriction(new Part("group.name", Person.class));
assertThat(query.toString(),
is("START `person`=node:`Person`(`name`={0}), `person_group`=node:`Group`(`name`={1}) MATCH `person`<-[:`members`]-`person_group` RETURN `person`"));
}
@Test
public void createsSimpleWhereClauseCorrectly() {
query.addRestriction(new Part("age", Person.class));
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_group`=node({0}) MATCH `person`<-[:`members`]-`person_group` WHERE `person`.__type__ IN ['Person'] RETURN `person`"));
}
@Test
public void buildsComplexQueryCorrectly() {
query.addRestriction(new Part("name", Person.class));
query.addRestriction(new Part("groupName", Person.class));
query.addRestriction(new Part("ageGreaterThan", Person.class));
query.addRestriction(new Part("groupMembersAge", Person.class));
assertThat(query.toString(), is(
"START `person`=node:`Person`(`name`={0}), `person_group`=node:`Group`(`name`={1}) " +
"MATCH `person`<-[:`members`]-`person_group`, `person`<-[:`members`]-`person_group`-[:`members`]->`person_group_members` " +
"WHERE `person`.`age` > {2} AND `person_group_members`.`age` = {3} " +
"RETURN `person`"
));
}
@Test
public void buildsQueryWithSort() {
query.addRestriction(new Part("name",Person.class));
assertThat(query.buildQuery(new Sort("person.name")).toQueryString(), is("START `person`=node:`Person`(`name`={0}) RETURN `person` ORDER BY person.name ASC"));
}
@Test
public void buildsQueryWithTwoSorts() {
query.addRestriction(new Part("name",Person.class));
Sort sort = new Sort(new Sort.Order("person.name"),new Sort.Order(Sort.Direction.DESC, "person.age"));
assertThat(query.buildQuery(sort).toQueryString(), is("START `person`=node:`Person`(`name`={0}) RETURN `person` ORDER BY person.name ASC,person.age DESC"));
}
@Test
public void buildsQueryWithPage() {
query.addRestriction(new Part("name",Person.class));
Pageable pageable = new PageRequest(3,10,new Sort("person.name"));
assertThat(query.buildQuery().toQueryString(pageable), is("START `person`=node:`Person`(`name`={0}) RETURN `person` ORDER BY person.name ASC SKIP 30 LIMIT 10"));
}
@Test
public void shouldFindByNodeEntity() throws Exception {
query.addRestriction(new Part("pet", Person.class));
assertThat(query.toString(), is("START `person_pet`=node({0}) MATCH `person`-[:`owns`]->`person_pet` WHERE `person`.__type__ IN ['Person'] RETURN `person`"));
}
@Test
public void shouldFindByNodeEntityForIncomingRelationship() {
query.addRestriction(new Part("group", Person.class));
assertThat(query.toString(), is("START `person_group`=node({0}) MATCH `person`<-[:`members`]-`person_group` WHERE `person`.__type__ IN ['Person'] RETURN `person`"));
}
}

View File

@@ -0,0 +1,63 @@
/**
* 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.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.neo4j.core.NodeTypeRepresentationStrategy;
import org.springframework.data.neo4j.support.typerepresentation.IndexBasedNodeTypeRepresentationStrategy;
import org.springframework.test.context.CleanContextCacheTestExecutionListener;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.instanceOf;
/**
* Tests for the various finder method based scenarios
* , specifically where the Index Type Representation Strategy is being used.
*
* @author Oliver Gierke & Nicki Watt
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@TestExecutionListeners({CleanContextCacheTestExecutionListener.class, DependencyInjectionTestExecutionListener.class, TransactionalTestExecutionListener.class})
public class DerivedFinderMethodForIndexedBasedTRSTests extends AbstractDerivedFinderMethodTestBase {
@Autowired
NodeTypeRepresentationStrategy strategy;
@Before
public void setup() {
assertThat("The tests in this class should be configured to use the Label " +
"based Type Representation Strategy, however it is not ... ",
strategy, instanceOf(IndexBasedNodeTypeRepresentationStrategy.class));
}
@Test
@Override
public void testQueryWithEntityGraphId() throws Exception {
assertRepositoryQueryMethod(ThingRepository.class, "findByOwnerId",new Object[]{123},
"START `thing_owner`=node({0}) MATCH `thing`-[:`owner`]->`thing_owner` WHERE `thing`.__type__ IN ['org.springframework.data.neo4j.repository.query.AbstractDerivedFinderMethodTestBase$Thing'] ",
123);
}
}

View File

@@ -0,0 +1,62 @@
/**
* 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.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.neo4j.core.NodeTypeRepresentationStrategy;
import org.springframework.data.neo4j.support.typerepresentation.LabelBasedNodeTypeRepresentationStrategy;
import org.springframework.test.context.CleanContextCacheTestExecutionListener;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.instanceOf;
/**
* Tests for the various finder method based scenarios
* , specifically where the Label Type Representation Strategy is being used.
*
* @author Oliver Gierke & Nicki Watt
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@TestExecutionListeners({CleanContextCacheTestExecutionListener.class, DependencyInjectionTestExecutionListener.class, TransactionalTestExecutionListener.class})
public class DerivedFinderMethodForLabelBasedTRSTests extends AbstractDerivedFinderMethodTestBase {
@Autowired
NodeTypeRepresentationStrategy strategy;
@Before
public void setup() {
assertThat("The tests in this class should be configured to use the Label " +
"based Type Representation Strategy, however it is not ... ",
strategy, instanceOf(LabelBasedNodeTypeRepresentationStrategy.class));
}
@Test
@Override
public void testQueryWithEntityGraphId() throws Exception {
assertRepositoryQueryMethod(ThingRepository.class, "findByOwnerId",new Object[]{123},
"START `thing_owner`=node({0}) MATCH `thing`-[:`owner`]->`thing_owner` WHERE `thing`:`org.springframework.data.neo4j.repository.query.AbstractDerivedFinderMethodTestBase$Thing` ",
123);
}
}

View File

@@ -20,9 +20,9 @@ import org.springframework.data.neo4j.repository.GraphRepository;
import java.util.Collection;
import java.util.Date;
import static org.springframework.data.neo4j.repository.query.DerivedFinderMethodTests.Thing;
import static org.springframework.data.neo4j.repository.query.AbstractDerivedFinderMethodTestBase.Thing;
public interface ThingRepository extends GraphRepository<DerivedFinderMethodTests.Thing> {
public interface ThingRepository extends GraphRepository<AbstractDerivedFinderMethodTestBase.Thing> {
Thing findByFirstNameAndLastName(String firstName, String lastName);
Thing findByFirstName(String firstName);
Thing findByDescription(String firstName);

View File

@@ -26,8 +26,12 @@ import org.springframework.data.neo4j.conversion.ResultConverter;
import org.springframework.data.neo4j.core.GraphDatabase;
import org.springframework.data.neo4j.support.Neo4jTemplate;
import org.springframework.data.test.DocumentingTestBase;
import org.springframework.test.context.CleanContextCacheTestExecutionListener;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
import org.springframework.transaction.annotation.Transactional;
import static java.util.Arrays.asList;
@@ -35,10 +39,16 @@ import static org.junit.Assert.assertEquals;
import static org.neo4j.helpers.collection.MapUtil.map;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath*:DocumentingTests-context.xml"})
@ContextConfiguration(locations = {"classpath:DocumentingTests-context.xml"})
@TestExecutionListeners({
CleanContextCacheTestExecutionListener.class,
DependencyInjectionTestExecutionListener.class,
TransactionalTestExecutionListener.class})
public class SnippetNeo4jTemplateMethodsTests extends DocumentingTestBase {
@Autowired
private GraphDatabase graphDatabase;
private Neo4jTemplate neo;
private static final RelationshipType WORKS_WITH = DynamicRelationshipType.withName("WORKS_WITH");
@Test
@@ -54,8 +64,8 @@ public class SnippetNeo4jTemplateMethodsTests extends DocumentingTestBase {
snippet = "template";
// SNIPPET template
// TODO auto-post-construct !!
final Neo4jTemplate neo = new Neo4jTemplate(graphDatabase);
// Injected Neo4jTemplate to make this test work. Impact for snippet?
Node mark = neo.createNode(map("name", "Mark"));
Node thomas = neo.createNode(map("name", "Thomas"));

View File

@@ -7,6 +7,12 @@
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/data/neo4j http://www.springframework.org/schema/data/neo4j/spring-neo4j.xsd">
<context:annotation-config/>
<bean id="typeRepresentationStrategyFactory" class="org.springframework.data.neo4j.support.typerepresentation.TypeRepresentationStrategyFactory">
<constructor-arg index="0" ref="graphDatabase"/>
<constructor-arg index="1" value="Indexed"/>
</bean>
<neo4j:config graphDatabaseService="graphDatabaseService"/>
<neo4j:repositories base-package="org.springframework.data.neo4j.repository.query"/>
<bean id="graphDatabaseService" class="org.neo4j.test.ImpermanentGraphDatabase" destroy-method="shutdown"/>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:neo4j="http://www.springframework.org/schema/data/neo4j"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/data/neo4j http://www.springframework.org/schema/data/neo4j/spring-neo4j.xsd">
<context:annotation-config/>
<bean id="typeRepresentationStrategyFactory" class="org.springframework.data.neo4j.support.typerepresentation.TypeRepresentationStrategyFactory">
<constructor-arg index="0" ref="graphDatabase"/>
<constructor-arg index="1" value="Labeled"/>
</bean>
<neo4j:config graphDatabaseService="graphDatabaseService"/>
<neo4j:repositories base-package="org.springframework.data.neo4j.repository.query"/>
<bean id="graphDatabaseService" class="org.neo4j.test.ImpermanentGraphDatabase" destroy-method="shutdown"/>
</beans>