From f0d772b3d3d7061c368466353b394cc04900c0ed Mon Sep 17 00:00:00 2001 From: Angelo Busato Date: Fri, 20 Jul 2018 14:31:49 +0200 Subject: [PATCH] fixes #699 - implement functions from pgsql (#860) --- docs/overview.adoc | 10 ++++- src/main/java/apoc/coll/Coll.java | 12 +++-- src/main/java/apoc/label/Label.java | 21 +++++++++ src/main/java/apoc/nodes/Nodes.java | 29 +++++++++++- src/test/java/apoc/coll/CollTest.java | 16 +++++++ src/test/java/apoc/label/LabelTest.java | 59 +++++++++++++++++++++++++ src/test/java/apoc/nodes/NodesTest.java | 30 +++++++++++-- 7 files changed, 168 insertions(+), 9 deletions(-) create mode 100644 src/main/java/apoc/label/Label.java create mode 100644 src/test/java/apoc/label/LabelTest.java diff --git a/docs/overview.adoc b/docs/overview.adoc index 58a71c50af..bcacd921dc 100644 --- a/docs/overview.adoc +++ b/docs/overview.adoc @@ -782,7 +782,7 @@ Sometimes type information gets lost, these functions help you to coerce an "Any | apoc.coll.insert(coll, index, value) | insert value at index | apoc.coll.insertAll(coll, index, values) | insert values at index | apoc.coll.remove(coll, index, [length=1]) | remove range of values from index to length - +| apoc.coll.different(values) | returns true if value are different |=== === Lookup and Manipulation Procedures @@ -805,6 +805,8 @@ Sometimes type information gets lost, these functions help you to coerce an "Any | apoc.node.relationship.types(node, rel-direction-pattern) | returns a list of distinct relationship types | apoc.node.degree(node, rel-direction-pattern) | returns total degrees of the given relationships in the pattern, can use `'>'` or `'<'` for all outgoing or incoming relationships | apoc.node.id(node) | returns id for (virtual) nodes +| apoc.node.degree.in(node, relationshipName) | returns total number of incoming relationship +| apoc.node.degree.out(node, relationshipName) | returns total number of outgoing relationship | apoc.node.labels(node) | returns labels for (virtual) nodes | apoc.rel.id(rel) | returns id for (virtual) relationships | apoc.rel.type(rel) | returns type for (virtual) relationships @@ -906,6 +908,12 @@ Example: `'FRIEND|MENTORS>| reverse(@Name("coll") List coll) { @UserFunction("apoc.coll.sortMulti") @Description("apoc.coll.sortMulti(coll, ['^name','age'],[limit],[skip]) - sort list of maps by several sort fields (ascending with ^ prefix) and optionally applies limit and skip") public List> sortMulti(@Name("coll") java.util.List> coll, - @Name(value="orderFields", defaultValue = "[]") java.util.List orderFields, - @Name(value="limit", defaultValue = "-1") long limit, - @Name(value="skip", defaultValue = "0") long skip) { + @Name(value="orderFields", defaultValue = "[]") java.util.List orderFields, + @Name(value="limit", defaultValue = "-1") long limit, + @Name(value="skip", defaultValue = "0") long skip) { List> result = new ArrayList<>(coll); if (orderFields != null && !orderFields.isEmpty()) { @@ -768,4 +768,10 @@ public List> combinations(@Name("coll") List coll, @Name(va return combinations; } + + @UserFunction + @Description("apoc.coll.different(values) - returns true if values are different") + public boolean different(@Name("values") List values) { + return new HashSet(values).size() == values.size(); + } } diff --git a/src/main/java/apoc/label/Label.java b/src/main/java/apoc/label/Label.java new file mode 100644 index 0000000000..8129bed1be --- /dev/null +++ b/src/main/java/apoc/label/Label.java @@ -0,0 +1,21 @@ +package apoc.label; + +import org.neo4j.graphdb.*; +import org.neo4j.procedure.Context; +import org.neo4j.procedure.Description; +import org.neo4j.procedure.Name; +import org.neo4j.procedure.UserFunction; + +public class Label { + + @Context public GraphDatabaseService db; + + @UserFunction("apoc.label.exists") + @Description("apoc.label.exists(element, label) - returns true or false related to label existance") + public boolean exists(@Name("node") Object element, @Name("label") String label) { + + return element instanceof Node ? ((Node) element).hasLabel(org.neo4j.graphdb.Label.label(label)) : + element instanceof Relationship ? ((Relationship) element).isType(RelationshipType.withName(label)) : false; + + } +} \ No newline at end of file diff --git a/src/main/java/apoc/nodes/Nodes.java b/src/main/java/apoc/nodes/Nodes.java index e05a0d9824..43534f22ed 100644 --- a/src/main/java/apoc/nodes/Nodes.java +++ b/src/main/java/apoc/nodes/Nodes.java @@ -53,7 +53,7 @@ public Stream delete(@Name("nodes") Object ids, @Name("batchSize") l while (it.hasNext()) { final List batch = Util.take(it, (int)batchSize); // count += Util.inTx(api,() -> batch.stream().peek( n -> {n.getRelationships().forEach(Relationship::delete);n.delete();}).count()); - count += Util.inTx(db,() -> {db.execute("FOREACH (n in {nodes} | DETACH DELETE n)",map("nodes",batch)).close();return batch.size();}); + count += Util.inTx(db,() -> {db.execute("FOREACH (n in {nodes} | DETACH DELETE n)",map("nodes",batch)).close();return batch.size();}); } return Stream.of(new LongResult(count)); } @@ -347,6 +347,31 @@ public long degree(@Name("node") Node node, @Name(value = "types",defaultValue = return degree; } + @UserFunction("apoc.node.degree.in") + @Description("apoc.node.degree.in(node, relationshipName) - returns total number number of incoming relationships") + public long degreeIn(@Name("node") Node node, @Name(value = "types",defaultValue = "") String type) { + + if (type==null || type.isEmpty()) { + return node.getDegree(Direction.INCOMING); + } + + return node.getDegree(RelationshipType.withName(type), Direction.INCOMING); + + } + + @UserFunction("apoc.node.degree.out") + @Description("apoc.node.degree.out(node, relationshipName) - returns total number number of outgoing relationships") + public long degreeOut(@Name("node") Node node, @Name(value = "types",defaultValue = "") String type) { + + if (type==null || type.isEmpty()) { + return node.getDegree(Direction.OUTGOING); + } + + return node.getDegree(RelationshipType.withName(type), Direction.OUTGOING); + + } + + @UserFunction("apoc.node.relationship.types") @Description("apoc.node.relationship.types(node, rel-direction-pattern) - returns a list of distinct relationship types") public List relationshipTypes(@Name("node") Node node, @Name(value = "types",defaultValue = "") String types) { @@ -386,4 +411,4 @@ private int getDegreeSafe(Node node, RelationshipType relType, Direction directi return node.getDegree(relType, direction); } -} +} \ No newline at end of file diff --git a/src/test/java/apoc/coll/CollTest.java b/src/test/java/apoc/coll/CollTest.java index 1d6922acd2..36d5547dad 100644 --- a/src/test/java/apoc/coll/CollTest.java +++ b/src/test/java/apoc/coll/CollTest.java @@ -688,4 +688,20 @@ public void testCombinationsWithMinAndMaxSelect() throws Exception { assertEquals(result, row.get("value")); }); } + + @Test + public void testVerifyAllValuesAreDifferent() throws Exception { + testCall(db, "RETURN apoc.coll.different([1, 2, 3]) as value", + (row) -> { + assertEquals(true, row.get("value")); + }); + testCall(db, "RETURN apoc.coll.different([1, 1, 1]) as value", + (row) -> { + assertEquals(false, row.get("value")); + }); + testCall(db, "RETURN apoc.coll.different([3, 3, 1]) as value", + (row) -> { + assertEquals(false, row.get("value")); + }); + } } diff --git a/src/test/java/apoc/label/LabelTest.java b/src/test/java/apoc/label/LabelTest.java new file mode 100644 index 0000000000..cfb26a0468 --- /dev/null +++ b/src/test/java/apoc/label/LabelTest.java @@ -0,0 +1,59 @@ +package apoc.label; + +import apoc.util.TestUtil; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.neo4j.graphdb.GraphDatabaseService; +import org.neo4j.test.TestGraphDatabaseFactory; + +import static apoc.util.TestUtil.testCall; +import static org.junit.Assert.assertEquals; + +public class LabelTest { + + private static GraphDatabaseService db; + + @BeforeClass + public static void setUp() throws Exception { + db = new TestGraphDatabaseFactory().newImpermanentDatabase(); + TestUtil.registerProcedure(db, Label.class); + } + + @AfterClass + public static void tearDown() { + db.shutdown(); + } + + @Test + public void testVerifyNodeLabelExistance() throws Exception { + + db.execute("create (a:Person{name:'Foo'})"); + + testCall(db, "MATCH (a) RETURN apoc.label.exists(a, 'Person') as value", + (row) -> { + assertEquals(true, row.get("value")); + }); + testCall(db, "MATCH (a) RETURN apoc.label.exists(a, 'Dog') as value", + (row) -> { + assertEquals(false, row.get("value")); + }); + } + + @Test + public void testVerifyRelTypeExistance() throws Exception { + + + db.execute("create (a:Person{name:'Foo'}), (b:Person{name:'Bar'}), (a)-[:LOVE{since:2010}]->(b)"); + + testCall(db, "MATCH ()-[a]->() RETURN apoc.label.exists(a, 'LOVE') as value", + (row) -> { + assertEquals(true, row.get("value")); + }); + testCall(db, "MATCH ()-[a]->() RETURN apoc.label.exists(a, 'LIVES_IN') as value", + (row) -> { + assertEquals(false, row.get("value")); + }); + + } +} \ No newline at end of file diff --git a/src/test/java/apoc/nodes/NodesTest.java b/src/test/java/apoc/nodes/NodesTest.java index 0c8702c44c..8ff4291688 100644 --- a/src/test/java/apoc/nodes/NodesTest.java +++ b/src/test/java/apoc/nodes/NodesTest.java @@ -104,8 +104,8 @@ public void testConnected() throws Exception { int relCount = 20; for (int rel=0;rel(et) " + - " WITH * UNWIND RANGE(1,{count}) AS id CREATE (st)-[:REL"+rel+"]->(ed)", + " CREATE (st)-[:REL"+rel+"]->(et) " + + " WITH * UNWIND RANGE(1,{count}) AS id CREATE (st)-[:REL"+rel+"]->(ed)", map("count",relCount-rel)).close(); } @@ -179,6 +179,30 @@ public void testDegreeDirectionOnly() { } + @Test + public void testDegreeInOutDirectionOnly() { + db.execute("CREATE (a:Person{name:'test'}) CREATE (b:Person) CREATE (c:Person) CREATE (d:Person) CREATE (a)-[:Rel1]->(b) CREATE (a)-[:Rel1]->(c) CREATE (a)-[:Rel2]->(d) CREATE (a)-[:Rel1]->(b) CREATE (a)<-[:Rel2]-(b) CREATE (a)<-[:Rel2]-(c) CREATE (a)<-[:Rel2]-(d) CREATE (a)<-[:Rel1]-(d)").close(); + + TestUtil.testCall(db, "MATCH (a:Person{name:'test'}) RETURN apoc.node.degree.in(a) as in, apoc.node.degree.out(a) as out", (r) -> { + assertEquals(4l, r.get("in")); + assertEquals(4l, r.get("out")); + }); + + } + + @Test + public void testDegreeInOutType() { + db.execute("CREATE (a:Person{name:'test'}) CREATE (b:Person) CREATE (c:Person) CREATE (d:Person) CREATE (a)-[:Rel1]->(b) CREATE (a)-[:Rel1]->(c) CREATE (a)-[:Rel2]->(d) CREATE (a)-[:Rel1]->(b) CREATE (a)<-[:Rel2]-(b) CREATE (a)<-[:Rel2]-(c) CREATE (a)<-[:Rel2]-(d) CREATE (a)<-[:Rel1]-(d)").close(); + + TestUtil.testCall(db, "MATCH (a:Person{name:'test'}) RETURN apoc.node.degree.in(a, 'Rel1') as in1, apoc.node.degree.out(a, 'Rel1') as out1, apoc.node.degree.in(a, 'Rel2') as in2, apoc.node.degree.out(a, 'Rel2') as out2", (r) -> { + assertEquals(1l, r.get("in1")); + assertEquals(3l, r.get("out1")); + assertEquals(3l, r.get("in2")); + assertEquals(1l, r.get("out2")); + }); + + } + @Test public void testId() { assertTrue(db.execute("CREATE (f:Foo {foo:'bar'}) RETURN apoc.node.id(f) AS id").columnAs("id").next() >= 0); @@ -253,4 +277,4 @@ public void testRelType() { assertNull(db.execute("RETURN apoc.rel.type(null) AS type").columnAs("type").next()); } -} +} \ No newline at end of file