diff --git a/arebac-core/src/main/java/io/github/danthe1st/arebac/data/commongraph/attributed/AttributedGraph.java b/arebac-core/src/main/java/io/github/danthe1st/arebac/data/commongraph/attributed/AttributedGraph.java index 4715118..3944f9a 100644 --- a/arebac-core/src/main/java/io/github/danthe1st/arebac/data/commongraph/attributed/AttributedGraph.java +++ b/arebac-core/src/main/java/io/github/danthe1st/arebac/data/commongraph/attributed/AttributedGraph.java @@ -14,4 +14,30 @@ public interface AttributedGraph findOutgoingEdges(N node); Collection findIncomingEdges(N node); + + /** + * Checks whether an attribute is unique for a specified node type. + * + * Returning {@code false} is also allowed if there is no concept of uniqueness, the uniqueness is uncertain or if {@link AttributedGraph#getNodeByUniqueAttribute(String, String, AttributeValue)} is not implemented + * @param key the key of the attribute to check + * @param nodeType the node type + * @return {@code true} if the attribute is unique, else {@code false} + * @see AttributedGraph#getNodeByUniqueAttribute(String, String, AttributeValue) + */ + default boolean isAttributeUniqueForNodeType(String key, String nodeType) { + return false; + } + + /** + * Get a node by a unique attribute value as specified by {@link AttributedGraph#isAttributeUniqueForNodeType(String, String)}. + * @param nodeType the node type where the attribute is unique + * @param key the key of the unique attribute + * @param value the value of the unique attribute + * @return the node associated with the unique attribute value + * @throws UnsupportedOperationException if the attribute is not actually unique + * @see AttributedGraph#isAttributeUniqueForNodeType(String, String) + */ + default N getNodeByUniqueAttribute(String nodeType, String key, AttributeValue value) { + throw new UnsupportedOperationException(); + } } diff --git a/arebac-core/src/main/java/io/github/danthe1st/arebac/gpeval/GPEval.java b/arebac-core/src/main/java/io/github/danthe1st/arebac/gpeval/GPEval.java index 349c718..2ed88bc 100644 --- a/arebac-core/src/main/java/io/github/danthe1st/arebac/gpeval/GPEval.java +++ b/arebac-core/src/main/java/io/github/danthe1st/arebac/gpeval/GPEval.java @@ -23,6 +23,7 @@ import io.github.danthe1st.arebac.data.graph_pattern.GPNode; import io.github.danthe1st.arebac.data.graph_pattern.GraphPattern; import io.github.danthe1st.arebac.data.graph_pattern.constraints.AttributeRequirement; +import io.github.danthe1st.arebac.data.graph_pattern.constraints.AttributeRequirementOperator; import io.github.danthe1st.arebac.data.graph_pattern.constraints.MutualExclusionConstraint; /** @@ -109,6 +110,12 @@ private void setupFixedVertices() throws NoResultException { } N graphNode = graph.findNodeById(value); + if(graphNode == null){ + throw new NoResultException("Fixed node cannot be found"); + } + candidates.put(patternNode, new HashSet<>(Set.of(graphNode))); + }else if(graph.isAttributeUniqueForNodeType(requirement.key(), patternNode.nodeType()) && requirement.operator() == AttributeRequirementOperator.EQUAL){ + N graphNode = graph.getNodeByUniqueAttribute(patternNode.nodeType(), requirement.key(), requirement.value()); if(graphNode == null){ throw new NoResultException("Fixed node cannot be found"); } diff --git a/arebac-neo4j/src/main/java/io/github/danthe1st/arebac/neo4j/graph/Neo4jDB.java b/arebac-neo4j/src/main/java/io/github/danthe1st/arebac/neo4j/graph/Neo4jDB.java index 159acb1..7d0f883 100644 --- a/arebac-neo4j/src/main/java/io/github/danthe1st/arebac/neo4j/graph/Neo4jDB.java +++ b/arebac-neo4j/src/main/java/io/github/danthe1st/arebac/neo4j/graph/Neo4jDB.java @@ -3,10 +3,15 @@ import java.util.Collection; import java.util.stream.Stream; +import io.github.danthe1st.arebac.data.commongraph.attributed.AttributeValue; import io.github.danthe1st.arebac.data.commongraph.attributed.AttributedGraph; import org.neo4j.graphdb.Direction; +import org.neo4j.graphdb.Label; +import org.neo4j.graphdb.Node; import org.neo4j.graphdb.Relationship; import org.neo4j.graphdb.Transaction; +import org.neo4j.graphdb.schema.ConstraintDefinition; +import org.neo4j.graphdb.schema.ConstraintType; public class Neo4jDB implements AttributedGraph { private final Transaction tx; @@ -36,4 +41,23 @@ private Collection findEdges(Neo4jNode node, Direction direction) { } } + @Override + public boolean isAttributeUniqueForNodeType(String key, String nodeType) { + for(ConstraintDefinition constraint : tx.schema().getConstraints(Label.label(nodeType))){ + if(constraint.isConstraintType(ConstraintType.UNIQUENESS)){ + for(String constraintKey : constraint.getPropertyKeys()){ + if(constraintKey.equals(key)){ + return true; + } + } + } + } + return false; + } + + @Override + public Neo4jNode getNodeByUniqueAttribute(String nodeType, String key, AttributeValue value) { + Node node = tx.findNode(Label.label(nodeType), key, value.value()); + return new Neo4jNode(node); + } } diff --git a/arebac-neo4j/src/test/java/io/github/danthe1st/arebac/neo4j/tests/SOTest.java b/arebac-neo4j/src/test/java/io/github/danthe1st/arebac/neo4j/tests/SOTest.java index 964b40c..f2e3495 100644 --- a/arebac-neo4j/src/test/java/io/github/danthe1st/arebac/neo4j/tests/SOTest.java +++ b/arebac-neo4j/src/test/java/io/github/danthe1st/arebac/neo4j/tests/SOTest.java @@ -68,8 +68,7 @@ void testFindAnswersOfUser() { try(Transaction tx = database.beginTx()){ Neo4jDB dbAsGraph = new Neo4jDB(tx); Node someUserNode = tx.findNode(Label.label("User"), "uuid", 6309); - String someUserId = someUserNode.getElementId(); - GraphPattern pattern = createFindAnswersPattern(someUserId); + GraphPattern pattern = createFindAnswersPattern(6309); Set> results = GPEval.evaluate(dbAsGraph, pattern); assertNotEquals(0, results.size()); Set> expectedAnswers = new HashSet<>(); @@ -82,7 +81,7 @@ void testFindAnswersOfUser() { } } - private GraphPattern createFindAnswersPattern(String requestorId) { + private GraphPattern createFindAnswersPattern(int userUUID) { GPNode requestor = new GPNode("requestor", USER.name()); GPNode answer = new GPNode("answer", ANSWER.name()); return new GraphPattern( @@ -91,7 +90,7 @@ private GraphPattern createFindAnswersPattern(String requestorId) { List.of(new GPEdge(requestor, answer, null, PROVIDED.name())) ), List.of(), - Map.of(requestor, List.of(new AttributeRequirement(ID_KEY, EQUAL, attribute(requestorId)))), + Map.of(requestor, List.of(new AttributeRequirement("uuid", EQUAL, attribute(userUUID)))), Map.of(), List.of(answer), Map.of("requestor", requestor, "answer", answer) ); @@ -128,7 +127,7 @@ void testSimpleExampleWithInMemoryDB() { ) ); - Set> results = GPEval.evaluate(graph, createCommentsToSameQuestionInTagPattern(tag.id())); + Set> results = GPEval.evaluate(graph, createCommentsToSameQuestionInTagPattern(ID_KEY, tag.id())); assertEquals(4, results.size()); assertEquals( Set.of( @@ -159,8 +158,7 @@ void testFindCommentsFromSameUsersToQuestionsInTag() { expectedElementCount = testResult.stream().count(); } - Node tagNode = tx.findNode(TAG, "name", "neo4j"); - GraphPattern pattern = createCommentsToSameQuestionInTagPattern(tagNode.getElementId()); + GraphPattern pattern = createCommentsToSameQuestionInTagPattern("name", "neo4j"); Set> results = assertTimeout(Duration.ofSeconds(30), () -> GPEval.evaluate(new Neo4jDB(tx), pattern)); assertNotEquals(0, results.size()); assertEquals(expectedElementCount, results.size()); @@ -204,7 +202,7 @@ private Node getOtherNodeOfSingleRelationship(Neo4jNode first, RelType relations } } - private GraphPattern createCommentsToSameQuestionInTagPattern(String tagId) { + private GraphPattern createCommentsToSameQuestionInTagPattern(String tagKey, String tagValue) { GPNode tag = new GPNode("tag", TAG.name()); GPNode user1 = new GPNode("user1", USER.name()); @@ -244,14 +242,14 @@ private GraphPattern createCommentsToSameQuestionInTagPattern(String tagId) { new MutualExclusionConstraint(question1, question2) ), Map.of( - tag, List.of(new AttributeRequirement(ID_KEY, EQUAL, attribute(tagId))) + tag, List.of(new AttributeRequirement(tagKey, EQUAL, attribute(tagValue))) ), Map.of(), List.of(user1Comment1, user2Comment1, user1Comment2, user2Comment2), Map.of("firstUser", user1, "secondUser", user2) ); } - + @Test void testSelfAnswers() { try(Transaction tx = database.beginTx()){ @@ -271,14 +269,14 @@ void testSelfAnswers() { .map(List::of) .collect(Collectors.toSet()); } - GraphPattern pattern = assertTimeout(Duration.ofSeconds(1), () -> createSelfAnswerPattern(tx.findNode(TAG, "name", "neo4j").getElementId())); + GraphPattern pattern = assertTimeout(Duration.ofSeconds(1), () -> createSelfAnswerPattern("neo4j")); Set> result = GPEval.evaluate(new Neo4jDB(tx), pattern); assertNotEquals(0, result.size()); assertEquals(expectedResult, result); } } - - private GraphPattern createSelfAnswerPattern(String elementId) { + + private GraphPattern createSelfAnswerPattern(String tagName) { GPNode tagNode = new GPNode("tag", TAG.name()); GPNode userNode = new GPNode("user", USER.name()); GPNode questionNode = new GPNode("question", QUESTION.name()); @@ -292,15 +290,15 @@ private GraphPattern createSelfAnswerPattern(String elementId) { new GPEdge(userNode, answerNode, null, PROVIDED.name()) ) ); - + return new GraphPattern( graph, List.of(), - Map.of(tagNode, List.of(new AttributeRequirement(ID_KEY, EQUAL, attribute(elementId)))), + Map.of(tagNode, List.of(new AttributeRequirement("name", EQUAL, attribute(tagName)))), Map.of(), List.of(answerNode), Map.of() ); } - + }