From 56bb4feab97da640594bf0dc03622b62b3376e8c Mon Sep 17 00:00:00 2001 From: DvirDukhan Date: Thu, 31 Oct 2019 10:16:04 +0200 Subject: [PATCH 1/4] added path support --- pom.xml | 20 ++- .../redisgraph/graph_entities/Path.java | 118 ++++++++++++++++++ .../impl/resultset/ResultSetImpl.java | 14 ++- .../impl/resultset/ResultSetScalarTypes.java | 3 +- .../redisgraph/RedisGraphAPITest.java | 84 ++++++++++++- .../redisgraph/graph_entities/PathTest.java | 78 ++++++++++++ 6 files changed, 308 insertions(+), 9 deletions(-) create mode 100644 src/main/java/com/redislabs/redisgraph/graph_entities/Path.java create mode 100644 src/test/java/com/redislabs/redisgraph/graph_entities/PathTest.java diff --git a/pom.xml b/pom.xml index 2b2c133..c137ec9 100644 --- a/pom.xml +++ b/pom.xml @@ -75,7 +75,25 @@ 1.6.6 test - + + org.junit.jupiter + junit-jupiter + RELEASE + test + + + org.junit.jupiter + junit-jupiter + RELEASE + test + + + nl.jqno.equalsverifier + equalsverifier + 3.1.10 + test + + 8 8 diff --git a/src/main/java/com/redislabs/redisgraph/graph_entities/Path.java b/src/main/java/com/redislabs/redisgraph/graph_entities/Path.java new file mode 100644 index 0000000..fa86484 --- /dev/null +++ b/src/main/java/com/redislabs/redisgraph/graph_entities/Path.java @@ -0,0 +1,118 @@ +package com.redislabs.redisgraph.graph_entities; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * This class represents a path in the graph. + */ +public final class Path { + + private final List nodes; + private final List edges; + + + /** + * Parametrized constructor + * @param nodes - List of nodes. + * @param edges - List of edges. + */ + public Path(List nodes, List edges) { + this.nodes = nodes; + this.edges = edges; + } + + /** + * Returns the nodes of the path. + * @return List of nodes. + */ + public List getNodes() { + return nodes; + } + + /** + * Returns the edges of the path. + * @return List of edges. + */ + public List getEdges() { + return edges; + } + + /** + * Returns the length of the path - number of edges. + * @return Number of edges. + */ + public int length() { + return edges.size(); + } + + /** + * Return the number of nodes in the path. + * @return Number of nodes. + */ + public int nodeCount(){ + return nodes.size(); + } + + /** + * Returns the first node in the path. + * @return First nodes in the path. + * @throws IndexOutOfBoundsException if the path is empty. + */ + public Node firstNode(){ + return nodes.get(0); + } + + /** + * Returns the last node in the path. + * @return Last nodes in the path. + * @throws IndexOutOfBoundsException if the path is empty. + */ + public Node lastNode(){ + return nodes.get(nodes.size() - 1); + } + + /** + * Returns a node with specified index in the path. + * @return Node. + * @throws IndexOutOfBoundsException if the index is out of range + * ({@code index < 0 || index >= nodesCount()}) + */ + public Node getNode(int index){ + return nodes.get(index); + } + + /** + * Returns an edge with specified index in the path. + * @return Edge. + * @throws IndexOutOfBoundsException if the index is out of range + * ({@code index < 0 || index >= length()}) + */ + public Edge getEdge(int index){ + return edges.get(index); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Path path = (Path) o; + return Objects.equals(nodes, path.nodes) && + Objects.equals(edges, path.edges); + } + + @Override + public int hashCode() { + return Objects.hash(nodes, edges); + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("Path{"); + sb.append("nodes=").append(nodes); + sb.append(", edges=").append(edges); + sb.append('}'); + return sb.toString(); + } +} diff --git a/src/main/java/com/redislabs/redisgraph/impl/resultset/ResultSetImpl.java b/src/main/java/com/redislabs/redisgraph/impl/resultset/ResultSetImpl.java index fdcdf66..442573b 100644 --- a/src/main/java/com/redislabs/redisgraph/impl/resultset/ResultSetImpl.java +++ b/src/main/java/com/redislabs/redisgraph/impl/resultset/ResultSetImpl.java @@ -6,10 +6,7 @@ import com.redislabs.redisgraph.ResultSet; import com.redislabs.redisgraph.Statistics; import com.redislabs.redisgraph.exceptions.JRedisGraphRunTimeException; -import com.redislabs.redisgraph.graph_entities.Edge; -import com.redislabs.redisgraph.graph_entities.GraphEntity; -import com.redislabs.redisgraph.graph_entities.Node; -import com.redislabs.redisgraph.graph_entities.Property; +import com.redislabs.redisgraph.graph_entities.*; import com.redislabs.redisgraph.impl.graph_cache.GraphCache; import redis.clients.jedis.util.SafeEncoder; import redis.clients.jedis.exceptions.JedisDataException; @@ -245,12 +242,21 @@ private Object deserializeScalar(List rawScalarData) { return deserializeNode((List) obj); case VALUE_EDGE: return deserializeEdge((List) obj); + case VALUE_PATH: + return deserializePath(obj); case VALUE_UNKNOWN: default: return obj; } } + private Path deserializePath(Object rawScalarData) { + List> array = (List>) rawScalarData; + List nodes = (List) deserializeScalar(array.get(0)); + List edges = (List) deserializeScalar(array.get(1)); + return new Path(nodes, edges); + } + private List deserializeArray(Object rawScalarData) { List> array = (List>) rawScalarData; List res = new ArrayList<>(array.size()); diff --git a/src/main/java/com/redislabs/redisgraph/impl/resultset/ResultSetScalarTypes.java b/src/main/java/com/redislabs/redisgraph/impl/resultset/ResultSetScalarTypes.java index 5e3e193..8d40aff 100644 --- a/src/main/java/com/redislabs/redisgraph/impl/resultset/ResultSetScalarTypes.java +++ b/src/main/java/com/redislabs/redisgraph/impl/resultset/ResultSetScalarTypes.java @@ -11,7 +11,8 @@ enum ResultSetScalarTypes { VALUE_DOUBLE, VALUE_ARRAY, VALUE_EDGE, - VALUE_NODE; + VALUE_NODE, + VALUE_PATH; static ResultSetScalarTypes[] values = values(); diff --git a/src/test/java/com/redislabs/redisgraph/RedisGraphAPITest.java b/src/test/java/com/redislabs/redisgraph/RedisGraphAPITest.java index 22cdf28..830ff1b 100644 --- a/src/test/java/com/redislabs/redisgraph/RedisGraphAPITest.java +++ b/src/test/java/com/redislabs/redisgraph/RedisGraphAPITest.java @@ -1,14 +1,13 @@ package com.redislabs.redisgraph; -import java.util.Arrays; -import java.util.List; -import java.util.NoSuchElementException; +import java.util.*; import java.util.stream.Collectors; import java.util.stream.IntStream; import com.redislabs.redisgraph.graph_entities.Edge; import com.redislabs.redisgraph.graph_entities.Node; +import com.redislabs.redisgraph.graph_entities.Path; import com.redislabs.redisgraph.graph_entities.Property; import com.redislabs.redisgraph.impl.api.RedisGraph; import com.redislabs.redisgraph.impl.resultset.ResultSetImpl; @@ -22,6 +21,42 @@ import static com.redislabs.redisgraph.Header.ResultSetColumnTypes.*; public class RedisGraphAPITest { + + private class PathBuilder{ + List nodes; + List edges; + Class currentAppendClass; + + public PathBuilder(int nodesCount){ + nodes = new ArrayList<>(nodesCount); + edges = new ArrayList<>(nodesCount-1); + currentAppendClass = Node.class; + } + + public PathBuilder append(Object object){ + Class c = object.getClass(); + if(!currentAppendClass.equals(c)) throw new IllegalArgumentException(""); + if(c.equals(Node.class)) return appendNode((Node)object); + else return appendEdge((Edge)object); + } + + private PathBuilder appendEdge(Edge edge) { + edges.add(edge); + currentAppendClass = Node.class; + return this; + } + + public PathBuilder appendNode(Node node){ + nodes.add(node); + currentAppendClass = Edge.class; + return this; + } + + public Path build(){ + return new Path(nodes, edges); + } + } + private RedisGraphContextGenerator api; public RedisGraphAPITest() { @@ -878,4 +913,47 @@ record = resultSet.next(); } + @Test + public void testPath(){ + List nodes = new ArrayList<>(3); + for(int i =0; i < 3; i++){ + Node node = new Node(); + node.setId(i); + node.addLabel("L1"); + nodes.add(node); + } + + List edges = new ArrayList<>(2); + for(int i =0; i <2; i++){ + Edge edge = new Edge(); + edge.setId(i); + edge.setRelationshipType("R1"); + edge.setSource(i); + edge.setDestination(i + 1); + edges.add(edge); + } + + Set expectedPaths = new HashSet<>(); + + Path path01 = new Path(Arrays.asList(nodes.get(0), nodes.get(1)), Arrays.asList(edges.get(0))); + Path path12 = new Path(Arrays.asList(nodes.get(1), nodes.get(2)), Arrays.asList(edges.get(1))); + Path path02 = new Path(Arrays.asList(nodes.get(0), nodes.get(1), nodes.get(2)), Arrays.asList(edges.get(0), edges.get(1))); + + expectedPaths.add(path01); + expectedPaths.add(path12); + expectedPaths.add(path02); + + api.query("social", "CREATE (:L1)-[:R1]->(:L1)-[:R1]->(:L1)"); + + ResultSet resultSet = api.query("social", "MATCH p = (:L1)-[:R1*]->(:L1) RETURN p"); + + Assert.assertEquals(expectedPaths.size(), resultSet.size()); + for(int i =0; i < resultSet.size(); i++){ + Path p = resultSet.next().getValue("p"); + Assert.assertTrue(expectedPaths.contains(p)); + expectedPaths.remove(p); + } + + } + } diff --git a/src/test/java/com/redislabs/redisgraph/graph_entities/PathTest.java b/src/test/java/com/redislabs/redisgraph/graph_entities/PathTest.java new file mode 100644 index 0000000..74581b3 --- /dev/null +++ b/src/test/java/com/redislabs/redisgraph/graph_entities/PathTest.java @@ -0,0 +1,78 @@ +package com.redislabs.redisgraph.graph_entities; + +import nl.jqno.equalsverifier.EqualsVerifier; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import static org.junit.jupiter.api.Assertions.*; + +public class PathTest { + + private Node buildNode(int id){ + Node n = new Node(); + n.setId(0); + return n; + } + + private Edge buildEdge(int id, int src, int dst){ + Edge e = new Edge(); + e.setId(id); + e.setSource(src); + e.setDestination(dst); + return e; + } + + private List buildNodeArray(int size) { + List nodes = new ArrayList<>(); + return IntStream.range(0, size).mapToObj(i -> buildNode(i)).collect(Collectors.toList()); + } + + private List buildEdgeArray(int size){ + List nodes = new ArrayList<>(); + return IntStream.range(0, size).mapToObj(i -> buildEdge(i, i, i+1)).collect(Collectors.toList()); + } + + private Path buildPath(int nodeCount){ + return new Path(buildNodeArray(nodeCount), buildEdgeArray(nodeCount-1)); + } + + @Test + public void testEmptyPath(){ + Path path = buildPath(0); + assertEquals(0, path.length()); + assertEquals(0, path.nodeCount()); + assertThrows(IndexOutOfBoundsException.class, ()->path.getNode(0)); + assertThrows(IndexOutOfBoundsException.class, ()->path.getEdge(0)); + } + + @Test + public void testSingleNodePath(){ + Path path = buildPath(1); + assertEquals(0, path.length()); + assertEquals(1, path.nodeCount()); + Node n = new Node(); + n.setId(0); + assertEquals(n, path.firstNode()); + assertEquals(n, path.lastNode()); + assertEquals(n, path.getNode(0)); + } + + @Test + public void testRandomLengthPath(){ + int nodeCount = ThreadLocalRandom.current().nextInt(2, 100 + 1); + Path path = buildPath(nodeCount); + assertEquals(buildNodeArray(nodeCount), path.getNodes()); + assertEquals(buildEdgeArray(nodeCount-1), path.getEdges()); + assertDoesNotThrow(()->path.getEdge(0)); + } + + @Test + public void hashCodeEqualTest(){ + EqualsVerifier.forClass(Path.class).verify(); + } +} \ No newline at end of file From 823a74e1d7592627c6bd467b36ddfed8d9b10bd0 Mon Sep 17 00:00:00 2001 From: DvirDukhan Date: Thu, 31 Oct 2019 12:01:24 +0200 Subject: [PATCH 2/4] added path builder --- .../redisgraph/RedisGraphAPITest.java | 48 +++--------------- .../redisgraph/test/utils/PathBuilder.java | 50 +++++++++++++++++++ .../test/utils/PathBuilderTest.java | 28 +++++++++++ 3 files changed, 84 insertions(+), 42 deletions(-) create mode 100644 src/test/java/com/redislabs/redisgraph/test/utils/PathBuilder.java create mode 100644 src/test/java/com/redislabs/redisgraph/test/utils/PathBuilderTest.java diff --git a/src/test/java/com/redislabs/redisgraph/RedisGraphAPITest.java b/src/test/java/com/redislabs/redisgraph/RedisGraphAPITest.java index 830ff1b..abb78a9 100644 --- a/src/test/java/com/redislabs/redisgraph/RedisGraphAPITest.java +++ b/src/test/java/com/redislabs/redisgraph/RedisGraphAPITest.java @@ -11,52 +11,16 @@ import com.redislabs.redisgraph.graph_entities.Property; import com.redislabs.redisgraph.impl.api.RedisGraph; import com.redislabs.redisgraph.impl.resultset.ResultSetImpl; -import org.junit.After; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; +import com.redislabs.redisgraph.test.utils.PathBuilder; +import org.junit.*; import com.redislabs.redisgraph.Statistics.Label; +import org.junit.rules.ExpectedException; import static com.redislabs.redisgraph.Header.ResultSetColumnTypes.*; public class RedisGraphAPITest { - private class PathBuilder{ - List nodes; - List edges; - Class currentAppendClass; - - public PathBuilder(int nodesCount){ - nodes = new ArrayList<>(nodesCount); - edges = new ArrayList<>(nodesCount-1); - currentAppendClass = Node.class; - } - - public PathBuilder append(Object object){ - Class c = object.getClass(); - if(!currentAppendClass.equals(c)) throw new IllegalArgumentException(""); - if(c.equals(Node.class)) return appendNode((Node)object); - else return appendEdge((Edge)object); - } - - private PathBuilder appendEdge(Edge edge) { - edges.add(edge); - currentAppendClass = Node.class; - return this; - } - - public PathBuilder appendNode(Node node){ - nodes.add(node); - currentAppendClass = Edge.class; - return this; - } - - public Path build(){ - return new Path(nodes, edges); - } - } - private RedisGraphContextGenerator api; public RedisGraphAPITest() { @@ -935,9 +899,9 @@ public void testPath(){ Set expectedPaths = new HashSet<>(); - Path path01 = new Path(Arrays.asList(nodes.get(0), nodes.get(1)), Arrays.asList(edges.get(0))); - Path path12 = new Path(Arrays.asList(nodes.get(1), nodes.get(2)), Arrays.asList(edges.get(1))); - Path path02 = new Path(Arrays.asList(nodes.get(0), nodes.get(1), nodes.get(2)), Arrays.asList(edges.get(0), edges.get(1))); + Path path01 = new PathBuilder(2).append(nodes.get(0)).append(edges.get(0)).append(nodes.get(1)).build(); + Path path12 = new PathBuilder(2).append(nodes.get(1)).append(edges.get(1)).append(nodes.get(2)).build(); + Path path02 = new PathBuilder(3).append(nodes.get(0)).append(edges.get(0)).append(nodes.get(1)).append(edges.get(1)).append(nodes.get(2)).build(); expectedPaths.add(path01); expectedPaths.add(path12); diff --git a/src/test/java/com/redislabs/redisgraph/test/utils/PathBuilder.java b/src/test/java/com/redislabs/redisgraph/test/utils/PathBuilder.java new file mode 100644 index 0000000..3b1e2dc --- /dev/null +++ b/src/test/java/com/redislabs/redisgraph/test/utils/PathBuilder.java @@ -0,0 +1,50 @@ +package com.redislabs.redisgraph.test.utils; + +import com.redislabs.redisgraph.graph_entities.Edge; +import com.redislabs.redisgraph.graph_entities.Node; +import com.redislabs.redisgraph.graph_entities.Path; + +import java.util.ArrayList; +import java.util.List; + +public class PathBuilder{ + List nodes; + List edges; + Class currentAppendClass; + + public PathBuilder() { + this.nodes = new ArrayList<>(0); + this.edges = new ArrayList<>(0); + currentAppendClass = Node.class; + } + + public PathBuilder(int nodesCount){ + nodes = new ArrayList<>(nodesCount); + edges = new ArrayList<>(nodesCount-1 >= 0 ? nodesCount -1 : 0); + currentAppendClass = Node.class; + } + + public PathBuilder append(Object object){ + Class c = object.getClass(); + if(!currentAppendClass.equals(c)) throw new IllegalArgumentException("Path Builder expected " + currentAppendClass.getSimpleName() + " but was " + c.getSimpleName()); + if(c.equals(Node.class)) return appendNode((Node)object); + else return appendEdge((Edge)object); + } + + private PathBuilder appendEdge(Edge edge) { + edges.add(edge); + currentAppendClass = Node.class; + return this; + } + + private PathBuilder appendNode(Node node){ + nodes.add(node); + currentAppendClass = Edge.class; + return this; + } + + public Path build(){ + if(nodes.size() != edges.size() + 1) throw new IllegalArgumentException("Path builder nodes count should be edge count + 1"); + return new Path(nodes, edges); + } +} diff --git a/src/test/java/com/redislabs/redisgraph/test/utils/PathBuilderTest.java b/src/test/java/com/redislabs/redisgraph/test/utils/PathBuilderTest.java new file mode 100644 index 0000000..01751eb --- /dev/null +++ b/src/test/java/com/redislabs/redisgraph/test/utils/PathBuilderTest.java @@ -0,0 +1,28 @@ +package com.redislabs.redisgraph.test.utils; + +import com.redislabs.redisgraph.graph_entities.Edge; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +public class PathBuilderTest { + + @Rule + public ExpectedException exceptionRule = ExpectedException.none(); + + @Test + public void testPathBuilderSizeException(){ + exceptionRule.expect(IllegalArgumentException.class); + exceptionRule.expectMessage("Path builder nodes count should be edge count + 1"); + PathBuilder builder = new PathBuilder(0); + builder.build(); + } + + @Test + public void testPathBuilderArgumentsException(){ + exceptionRule.expect(IllegalArgumentException.class); + exceptionRule.expectMessage("Path Builder expected Node but was Edge"); + PathBuilder builder = new PathBuilder(0); + builder.append(new Edge()); + } +} From d3cb04681416c7dc3403888723277d7d7e782e41 Mon Sep 17 00:00:00 2001 From: DvirDukhan Date: Thu, 31 Oct 2019 15:46:56 +0200 Subject: [PATCH 3/4] changed access modifiers at path builder --- .../com/redislabs/redisgraph/test/utils/PathBuilder.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/test/java/com/redislabs/redisgraph/test/utils/PathBuilder.java b/src/test/java/com/redislabs/redisgraph/test/utils/PathBuilder.java index 3b1e2dc..64de9c7 100644 --- a/src/test/java/com/redislabs/redisgraph/test/utils/PathBuilder.java +++ b/src/test/java/com/redislabs/redisgraph/test/utils/PathBuilder.java @@ -7,10 +7,10 @@ import java.util.ArrayList; import java.util.List; -public class PathBuilder{ - List nodes; - List edges; - Class currentAppendClass; +public final class PathBuilder{ + private final List nodes; + private final List edges; + private Class currentAppendClass; public PathBuilder() { this.nodes = new ArrayList<>(0); From 06bae07e7ed835f3c74e7ca4b366c640f3cb2a8d Mon Sep 17 00:00:00 2001 From: DvirDukhan Date: Fri, 1 Nov 2019 01:10:33 +0200 Subject: [PATCH 4/4] added example to readme.md --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 0d3ea21..c2d3332 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,7 @@ package com.redislabs.redisgraph; import com.redislabs.redisgraph.graph_entities.Edge; import com.redislabs.redisgraph.graph_entities.Node; +import com.redislabs.redisgraph.graph_entities.Path; import com.redislabs.redisgraph.impl.api.RedisGraph; import java.util.List; @@ -115,6 +116,15 @@ public class RedisGraphExample { System.out.println(record.toString()); } + resultSet = graph.query("social", "MATCH p = (:person)-[:knows]->(:person) RETURN p"); + while(resultSet.hasNext()) { + Record record = resultSet.next(); + Path p = record.getValue("p"); + + // More path API at Javadoc. + System.out.println(p.nodeCount()); + } + // delete graph graph.deleteGraph("social");