DATAGRAPH-181 fix for creating unique entities with numeric unique fields, unique relationships

This commit is contained in:
Michael Hunger
2012-04-12 09:44:19 +02:00
parent d9b39e306e
commit 9347687cba
6 changed files with 167 additions and 28 deletions

View File

@@ -20,12 +20,15 @@ import org.neo4j.graphdb.PropertyContainer;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.RelationshipType;
import org.neo4j.graphdb.index.Index;
import org.neo4j.graphdb.index.RelationshipIndex;
import org.neo4j.graphdb.traversal.TraversalDescription;
import org.neo4j.helpers.collection.MapUtil;
import org.neo4j.rest.graphdb.ExecutingRestRequest;
import org.neo4j.rest.graphdb.RequestResult;
import org.neo4j.rest.graphdb.RestAPI;
import org.neo4j.rest.graphdb.RestRequest;
import org.neo4j.rest.graphdb.entity.RestNode;
import org.neo4j.rest.graphdb.index.RestIndex;
import org.neo4j.rest.graphdb.index.RestIndexManager;
import org.neo4j.rest.graphdb.query.RestCypherQueryEngine;
import org.neo4j.rest.graphdb.query.RestGremlinQueryEngine;
@@ -66,21 +69,23 @@ public class SpringRestGraphDatabase extends org.neo4j.rest.graphdb.RestGraphDat
return super.getRestAPI().createNode(props);
}
// TODO move to RestAPI
public Node getOrCreateNode(String index, String key, Object value, final Map<String,Object> properties) {
if (index==null || key == null || value==null) throw new IllegalArgumentException("Unique index "+index+" key "+key+" value must not be null");
final RequestResult result = getRestAPI().getRestRequest().post("index/node/" + index + "?unique", MapUtil.map("key",key,"value",value,"properties",properties));
if (result.statusIs(Response.Status.CREATED) || result.statusIs(Response.Status.OK)) {
return (Node)getRestAPI().createExtractor().convertFromRepresentation(result);
}
throw new RuntimeException(String.format("Error retrieving or creating node for key %s and value %s with index %s", key, value, index));
@Override
public Node getOrCreateNode(String indexName, String key, Object value, final Map<String,Object> properties) {
if (indexName ==null || key == null || value==null) throw new IllegalArgumentException("Unique index "+ indexName +" key "+key+" value must not be null");
final RestIndex<Node> nodeIndex = index().forNodes(indexName);
return getRestAPI().getOrCreateNode(nodeIndex, key, value, properties);
}
@Override
public Relationship getOrCreateRelationship(String indexName, String key, Object value, Node startNode, Node endNode, String type, Map<String, Object> properties) {
@SuppressWarnings("unchecked") final RestIndex<Relationship> relIndex = (RestIndex<Relationship>) index().forRelationships(indexName);
return getRestAPI().getOrCreateRelationship(relIndex,key,value,(RestNode) startNode,(RestNode) endNode,type, properties);
}
@Override
public Relationship createRelationship(Node startNode, Node endNode, RelationshipType type, Map<String, Object> props) {
return super.getRestAPI().createRelationship(startNode, endNode, type, props);
public Relationship createRelationship(Node startNode, Node endNode, RelationshipType type, Map<String, Object> properties) {
return super.getRestAPI().createRelationship(startNode, endNode, type, properties);
}
@Override

View File

@@ -53,7 +53,7 @@ public interface GraphDatabase {
* creates the node uniquely or returns an existing node with the same index-key-value combination.
* properties are used to initialize the node.
*/
Node getOrCreateNode(String index, String key, Object value, final Map<String,Object> properties);
Node getOrCreateNode(String indexName, String key, Object value, final Map<String,Object> properties);
/**
* @param id relationship id
@@ -62,7 +62,27 @@ public interface GraphDatabase {
*/
Relationship getRelationshipById(long id);
Relationship createRelationship(Node startNode, Node endNode, RelationshipType type, Map<String, Object> props);
/**
* creates the relationship between the startNode, endNode with the given type which will be populated with the provided properties
*/
Relationship createRelationship(Node startNode, Node endNode, RelationshipType type, Map<String, Object> properties);
/**
* Creates the relationship uniquely, uses the given index,key,value to achieve that.
* If the relationship for this combination already existed it is returned otherwise created and populated with the provided properties.
*/
Relationship getOrCreateRelationship(String indexName, String key, Object value, Node startNode, Node endNode, String type, Map<String, Object> properties);
/**
* deletes the Node and its index entries
*/
void remove(Node node);
/**
* deletes the relationship and its index entries
*/
void remove(Relationship relationship);
/**
* @param indexName existing index name, not null
@@ -86,18 +106,28 @@ public interface GraphDatabase {
*/
TraversalDescription traversalDescription();
/**
* returns a query engine for the provided type (Cypher or Gremlin) which is initialized with the default result converter
*/
<T> QueryEngine<T> queryEngineFor(QueryType type);
void setConversionService(ConversionService conversionService);
/**
* returns a query engine for the provided type (Cypher or Gremlin) which is initialized with the provided result converter
*/
<T> QueryEngine<T> queryEngineFor(QueryType type, ResultConverter resultConverter);
boolean transactionIsRunning();
void remove(Node node);
void remove(Relationship relationship);
/**
* @param conversionService the conversion service to be used for the default result converter of this database
*/
void setConversionService(ConversionService conversionService);
/**
* @param resultConverter the default result converter to be used with this database
*/
void setResultConverter(ResultConverter resultConverter);
/**
* @return true if a transaction is currently running
*/
boolean transactionIsRunning();
}

View File

@@ -16,6 +16,7 @@
package org.springframework.data.neo4j.support;
import org.neo4j.graphdb.DynamicRelationshipType;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.PropertyContainer;
@@ -25,6 +26,7 @@ import org.neo4j.graphdb.index.Index;
import org.neo4j.graphdb.index.IndexManager;
import org.neo4j.graphdb.index.UniqueFactory;
import org.neo4j.graphdb.traversal.TraversalDescription;
import org.neo4j.index.lucene.ValueContext;
import org.neo4j.kernel.AbstractGraphDatabase;
import org.neo4j.kernel.GraphDatabaseAPI;
import org.neo4j.kernel.Traversal;
@@ -122,8 +124,8 @@ public class DelegatingGraphDatabase implements GraphDatabase {
}
@Override
public Relationship createRelationship(Node startNode, Node endNode, RelationshipType type, Map<String, Object> props) {
return setProperties(startNode.createRelationshipTo(endNode,type),props);
public Relationship createRelationship(Node startNode, Node endNode, RelationshipType type, Map<String, Object> properties) {
return setProperties(startNode.createRelationshipTo(endNode,type), properties);
}
@SuppressWarnings("unchecked")
@@ -263,13 +265,29 @@ public class DelegatingGraphDatabase implements GraphDatabase {
}
}
public Node getOrCreateNode(String index, String key, Object value, final Map<String,Object> nodeProperties) {
if (index==null || key == null || value==null) throw new IllegalArgumentException("Unique index "+index+" key "+key+" value must not be null");
UniqueFactory.UniqueNodeFactory factory = new UniqueFactory.UniqueNodeFactory(delegate, index) {
public Node getOrCreateNode(String indexName, String key, Object value, final Map<String,Object> nodeProperties) {
if (indexName ==null || key == null || value==null) throw new IllegalArgumentException("Unique index "+ indexName +" key "+key+" value must not be null");
if (value instanceof Number) value= ValueContext.numeric((Number)value);
UniqueFactory.UniqueNodeFactory factory = new UniqueFactory.UniqueNodeFactory(delegate, indexName) {
protected void initialize(Node node, Map<String, Object> _) {
setProperties(node,nodeProperties);
}
};
return factory.getOrCreate(key, value);
}
@Override
public Relationship getOrCreateRelationship(String indexName, String key, Object value, final Node startNode, final Node endNode, final String type, final Map<String, Object> properties) {
if (indexName ==null || key == null || value==null) throw new IllegalArgumentException("Unique index "+ indexName +" key "+key+" value must not be null");
if (startNode ==null || endNode == null || type==null) throw new IllegalArgumentException("StartNode "+ startNode +" EndNode "+ endNode +" and type "+type+" must not be null");
if (value instanceof Number) value= ValueContext.numeric((Number)value);
UniqueFactory.UniqueRelationshipFactory factory = new UniqueFactory.UniqueRelationshipFactory(delegate, indexName) {
@Override
protected Relationship create(Map<String, Object> _) {
final Relationship relationship = startNode.createRelationshipTo(endNode, DynamicRelationshipType.withName(type));
return setProperties(relationship, properties);
}
};
return factory.getOrCreate(key, value);
}
}

View File

@@ -238,7 +238,7 @@ public class Neo4jTemplate implements Neo4jOperations, EntityPersister {
*/
@Override
public Node createNode() {
return infrastructure.getGraphDatabase().createNode(null);
return getGraphDatabase().createNode(null);
}
/**
@@ -247,7 +247,7 @@ public class Neo4jTemplate implements Neo4jOperations, EntityPersister {
*/
@Override
public Node createNode(final Map<String, Object> properties) {
return infrastructure.getGraphDatabase().createNode(properties);
return getGraphDatabase().createNode(properties);
}
/**
@@ -256,7 +256,7 @@ public class Neo4jTemplate implements Neo4jOperations, EntityPersister {
*/
@Override
public Node getOrCreateNode(String index, String key, Object value, final Map<String, Object> properties) {
return infrastructure.getGraphDatabase().getOrCreateNode(index, key, value, properties);
return getGraphDatabase().getOrCreateNode(index, key, value, properties);
}
@Override
@@ -385,6 +385,11 @@ public class Neo4jTemplate implements Neo4jOperations, EntityPersister {
});
}
@Override
public Relationship getOrCreateRelationship(String indexName, String key, Object value, Node startNode, Node endNode, String type, Map<String, Object> properties) {
return getGraphDatabase().getOrCreateRelationship(indexName,key,value,startNode,endNode,type,properties);
}
private final Neo4jExceptionTranslator exceptionTranslator = new Neo4jExceptionTranslator();
public DataAccessException translateExceptionIfPossible(Exception ex) {

View File

@@ -103,7 +103,13 @@ public interface Neo4jOperations {
/**
* Creates a relationship with the given initial properties.
*/
Relationship createRelationshipBetween(Node startNode, Node endNode, String type, Map<String, Object> props);
Relationship createRelationshipBetween(Node startNode, Node endNode, String type, Map<String, Object> properties);
/**
* Creates the relationship uniquely, uses the given index,key,value to achieve that.
* If the relationship for this combination already existed it is returned otherwise created and populated with the provided properties.
*/
Relationship getOrCreateRelationship(String indexName, String key, Object value, Node startNode, Node endNode, String type, Map<String, Object> properties);
/**
* Retrieves a single relationship entity between two node entities with the given relationship type projected to the provided

View File

@@ -0,0 +1,75 @@
/**
* 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;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.Transaction;
import org.neo4j.helpers.collection.MapUtil;
import org.neo4j.test.ImpermanentGraphDatabase;
import static org.junit.Assert.assertEquals;
import static org.neo4j.helpers.collection.MapUtil.map;
/**
* @author mh
* @since 11.04.12
*/
public class DelegatingGraphDatabaseTest {
private DelegatingGraphDatabase graphDatabase;
private ImpermanentGraphDatabase gdb;
@Before
public void setUp() throws Exception {
gdb = new ImpermanentGraphDatabase();
graphDatabase = new DelegatingGraphDatabase(gdb);
}
@After
public void tearDown() throws Exception {
graphDatabase.shutdown();
}
@Test
public void testGetOrCreateNode() throws Exception {
final Node node = graphDatabase.getOrCreateNode("user", "name", "David", map("name", "David"));
final Node node2 = graphDatabase.getOrCreateNode("user", "name", "David", map("name", "David"));
assertEquals("David",node.getProperty("name"));
assertEquals(node,node2);
assertEquals(node,gdb.index().forNodes("user").get("name","David").getSingle());
}
@Test
public void testGetOrCreateRelationship() throws Exception {
final Transaction tx = gdb.beginTx();
final Node david = graphDatabase.createNode(map("name", "David"));
final Node michael = graphDatabase.createNode(map("name", "Michael"));
final Relationship rel1 = graphDatabase.getOrCreateRelationship("knows", "whom", "david_michael", david, michael, "KNOWS", map("whom", "david_michael"));
final Relationship rel2 = graphDatabase.getOrCreateRelationship("knows", "whom", "david_michael", david, michael, "KNOWS", map("whom", "david_michael"));
assertEquals("david_michael",rel1.getProperty("whom"));
assertEquals("KNOWS",rel1.getType().name());
assertEquals(david,rel1.getStartNode());
assertEquals(michael,rel1.getEndNode());
assertEquals(rel1,rel2);
assertEquals(rel1,gdb.index().forRelationships("knows").get("whom","david_michael").getSingle());
tx.success();
tx.finish();
}
}