Skip to content

Commit

Permalink
Add new procs for virtual paths and graphs (#2153)
Browse files Browse the repository at this point in the history
Fixes #914

Co-authored-by: Giuseppe Villani <[email protected]>
  • Loading branch information
github-actions[bot] and vga91 authored Aug 11, 2021
1 parent 0a31af2 commit 5b0c459
Show file tree
Hide file tree
Showing 19 changed files with 345 additions and 4 deletions.
24 changes: 24 additions & 0 deletions core/src/main/java/apoc/create/Create.java
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,30 @@ public Stream<VirtualPathResult> virtualPath(@Name("labelsN") List<String> label
return Stream.of(new VirtualPathResult(from, rel, to));
}

@Procedure
@Description("apoc.create.clonePathToVirtual")
public Stream<PathResult> clonePathToVirtual(@Name("path") Path path) {
return Stream.of(createVirtualPath(path));
}

@Procedure
@Description("apoc.create.clonePathsToVirtual")
public Stream<PathResult> clonePathsToVirtual(@Name("paths") List<Path> paths) {
return paths.stream().map(this::createVirtualPath);
}

private PathResult createVirtualPath(Path path) {
final Iterable<Relationship> relationships = path.relationships();
final Node first = path.startNode();
VirtualPath virtualPath = new VirtualPath(new VirtualNode(first, Iterables.asList(first.getPropertyKeys())));
for (Relationship rel : relationships) {
VirtualNode start = VirtualNode.from(rel.getStartNode());
VirtualNode end = VirtualNode.from(rel.getEndNode());
virtualPath.addRel(VirtualRelationship.from(start, end, rel));
}
return new PathResult(virtualPath);
}

private <T extends Entity> T setProperties(T pc, Map<String, Object> p) {
if (p == null) return pc;
for (Map.Entry<String, Object> entry : p.entrySet()) {
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/java/apoc/graph/Graphs.java
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ else if (data instanceof Iterator) {
return found;
}

@Description("apoc.graph.fromPaths(path,'name',{properties}) - creates a virtual graph object for later processing")
@Description("apoc.graph.fromPath(path,'name',{properties}) - creates a virtual graph object for later processing")
@Procedure
public Stream<VirtualGraph> fromPath(@Name("path") Path paths, @Name("name") String name, @Name("properties") Map<String,Object> properties) {
return Stream.of(new VirtualGraph(name, paths.nodes(), paths.relationships(),properties));
Expand Down
4 changes: 4 additions & 0 deletions core/src/main/java/apoc/result/VirtualNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ public VirtualNode(Node node, List<String> propertyNames) {
this.props.putAll(node.getProperties(keys));
}

public static VirtualNode from(Node node) {
return new VirtualNode(node, Iterables.asList(node.getPropertyKeys()));
}

@Override
public long getId() {
return id;
Expand Down
121 changes: 121 additions & 0 deletions core/src/main/java/apoc/result/VirtualPath.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package apoc.result;

import org.neo4j.graphdb.Entity;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Path;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.traversal.Paths;

import javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

import static org.apache.commons.collections4.IterableUtils.reversedIterable;


public class VirtualPath implements Path {

private final Node start;
private final List<Relationship> relationships = new ArrayList<>();

public VirtualPath(Node start) {
Objects.requireNonNull(start);
this.start = start;
}

public void addRel(Relationship relationship) {
Objects.requireNonNull(relationship);
this.relationships.add(relationship);
}

@Override
public Node startNode() {
return start;
}

@Override
public Node endNode() {
return reverseNodes().iterator().next();
}

@Override
public Relationship lastRelationship() {
return relationships.isEmpty() ? null : relationships.get(relationships.size() - 1);
}

@Override
public Iterable<Relationship> relationships() {
return relationships;
}

@Override
public Iterable<Relationship> reverseRelationships() {
return reversedIterable(relationships());
}

@Override
public Iterable<Node> nodes() {
List<Node> nodes = new ArrayList<>();
nodes.add(start);

AtomicReference<Node> currNode = new AtomicReference<>(start);
final List<Node> otherNodes = relationships.stream().map(rel -> {
final Node otherNode = rel.getOtherNode(currNode.get());
currNode.set(otherNode);
return otherNode;
}).collect(Collectors.toList());

nodes.addAll(otherNodes);
return nodes;
}

@Override
public Iterable<Node> reverseNodes() {
return reversedIterable(nodes());
}

@Override
public int length() {
return relationships.size();
}

@Override
@Nonnull
public Iterator<Entity> iterator() {
return new Iterator<>() {
Iterator<? extends Entity> current = nodes().iterator();
Iterator<? extends Entity> next = relationships().iterator();

@Override
public boolean hasNext() {
return current.hasNext();
}

@Override
public Entity next() {
try {
return current.next();
}
finally {
Iterator<? extends Entity> temp = current;
current = next;
next = temp;
}
}

@Override
public void remove() {
next.remove();
}
};
}

@Override
public String toString() {
return Paths.defaultPathToString(this);
}
}
4 changes: 4 additions & 0 deletions core/src/main/java/apoc/result/VirtualRelationship.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ public VirtualRelationship(long id, Node startNode, Node endNode, RelationshipTy
this.type = type;
this.props.putAll(props);
}

public static Relationship from(VirtualNode start, VirtualNode end, Relationship rel) {
return new VirtualRelationship(start, end, rel.getType()).withProperties(rel.getAllProperties());
}

@Override
public long getId() {
Expand Down
68 changes: 68 additions & 0 deletions core/src/test/java/apoc/create/CreateTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,23 @@
import org.junit.Test;
import org.neo4j.graphdb.Label;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Path;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.RelationshipType;
import org.neo4j.graphdb.ResourceIterator;
import org.neo4j.internal.helpers.collection.Iterables;
import org.neo4j.test.rule.DbmsRule;
import org.neo4j.test.rule.ImpermanentDbmsRule;

import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static apoc.util.TestUtil.testCall;
import static apoc.util.TestUtil.testResult;
import static org.junit.Assert.*;
import static org.neo4j.graphdb.Label.label;

public class CreateTest {

Expand Down Expand Up @@ -228,5 +235,66 @@ public void testVirtualFromNodeFunction() throws Exception {
assertNull(node.getProperty("born"));
});
}

@Test
public void testVirtualPath() {
db.executeTransactionally("CREATE p=(a:Test {foo: 7})-[:TEST]->(b:Baa:Baz {a:'b'})<-[:TEST_2 {aa:'bb'}]-(:Bar {one:'www'}), \n" +
"q=(:Omega {alpha: 'beta'})<-[:TEST_3 {aa:'ccc'}]-(:Bar {one:'jjj'})");
testCall(db, "MATCH p=(a:Test {foo: 7})-[:TEST]->(b:Baa:Baz {a:'b'})<-[:TEST_2 {aa:'bb'}]-(:Bar {one:'www'}) WITH p \n" +
"CALL apoc.create.clonePathToVirtual(p) YIELD path RETURN path",
(row) -> assertionsFirstVirtualPath((Path) row.get("path")));

testResult(db, "MATCH p=(a:Test {foo: 7})-[:TEST]->(b:Baa:Baz {a:'b'})<-[:TEST_2 {aa:'bb'}]-(:Bar {one:'www'}), \n" +
"q=(:Omega {alpha: 'beta'})<-[:TEST_3 {aa:'ccc'}]-(:Bar {one:'jjj'}) WITH [p, q] as paths \n" +
"CALL apoc.create.clonePathsToVirtual(paths) YIELD path RETURN path",
(res) -> {
ResourceIterator<Path> paths = res.columnAs("path");
Path firstPath = paths.next();
assertionsFirstVirtualPath(firstPath);
Path secondPath = paths.next();
Iterator<Node> nodes = secondPath.nodes().iterator();
Node firstNode = nodes.next();
assertEquals(List.of(label("Omega")), firstNode.getLabels());
assertEquals(Map.of("alpha", "beta"), firstNode.getAllProperties());

Node secondNode = nodes.next();
assertEquals(List.of(label("Bar")), secondNode.getLabels());
assertEquals(Map.of("one", "jjj"), secondNode.getAllProperties());
assertFalse(nodes.hasNext());

Iterator<Relationship> rels = secondPath.relationships().iterator();
Relationship relationship = rels.next();
assertEquals("TEST_3", relationship.getType().name());
assertEquals(Map.of("aa", "ccc"), relationship.getAllProperties());
assertFalse(rels.hasNext());
assertFalse(paths.hasNext());
});
}

private void assertionsFirstVirtualPath(Path path) {
Iterator<Node> nodes = path.nodes().iterator();
Node firstNode = nodes.next();
assertEquals(List.of(label("Test")), firstNode.getLabels());
assertEquals(Map.of("foo", 7L), firstNode.getAllProperties());

Node secondNode = nodes.next();
assertEquals(Set.of(label("Baa"), label("Baz")), Iterables.asSet(secondNode.getLabels()));
assertEquals(Map.of("a", "b"), secondNode.getAllProperties());

Node thirdNode = nodes.next();
assertEquals(List.of(label("Bar")), thirdNode.getLabels());
assertEquals(Map.of("one", "www"), thirdNode.getAllProperties());
assertFalse(nodes.hasNext());

Iterator<Relationship> rels = path.relationships().iterator();
Relationship firstRel = rels.next();
assertEquals("TEST", firstRel.getType().name());
assertTrue(firstRel.getAllProperties().isEmpty());

Relationship secondRel = rels.next();
assertEquals("TEST_2", secondRel.getType().name());
assertEquals(Map.of("aa", "bb"), secondRel.getAllProperties());
assertFalse(rels.hasNext());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
¦apoc.create.uuids(count :: INTEGER?) :: (row :: INTEGER?, uuid :: STRING?)
¦apoc.create.vNode(label :: LIST? OF STRING?, props :: MAP?) :: (node :: NODE?)
¦apoc.create.vNodes(label :: LIST? OF STRING?, props :: LIST? OF MAP?) :: (node :: NODE?)
¦apoc.create.clonePathToVirtual(path :: PATH?) :: (path :: PATH?)
¦apoc.create.clonePathsToVirtual(paths :: LIST? OF PATH?) :: (path :: PATH?)
¦apoc.create.vPattern(from :: MAP?, relType :: STRING?, props :: MAP?, to :: MAP?) :: (from :: NODE?, rel :: RELATIONSHIP?, to :: NODE?)
¦apoc.create.vPatternFull(labelsN :: LIST? OF STRING?, n :: MAP?, relType :: STRING?, props :: MAP?, labelsM :: LIST? OF STRING?, m :: MAP?) :: (from :: NODE?, rel :: RELATIONSHIP?, to :: NODE?)
¦apoc.create.vRelationship(from :: NODE?, relType :: STRING?, props :: MAP?, to :: NODE?) :: (rel :: RELATIONSHIP?)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
¦signature
¦apoc.create.clonePathToVirtual(path :: PATH?) :: (path :: PATH?)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
¦xref::overview/apoc.create/apoc.create.clonePathToVirtual.adoc[apoc.create.clonePathToVirtual icon:book[]] +


¦label:procedure[]
¦label:apoc-core[]
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
¦signature
¦apoc.create.clonePathsToVirtual(paths :: LIST? OF PATH?) :: (path :: PATH?)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
¦xref::overview/apoc.create/apoc.create.clonePathsToVirtual.adoc[apoc.create.clonePathsToVirtual icon:book[]] +


¦label:procedure[]
¦label:apoc-core[]
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,16 @@ apoc.create.vNode(['Label'], {key:value,...}) returns a virtual node
apoc.create.vNodes(['Label'], [{key:value,...}]) returns virtual nodes
|label:procedure[]
|label:apoc-core[]
|xref::overview/apoc.create/apoc.create.adoc[apoc.create.clonePathToVirtual icon:book[]]

apoc.create.clonePathToVirtual
|label:procedure[]
|label:apoc-core[]
|xref::overview/apoc.create/apoc.create.adoc[apoc.create.clonePathsToVirtual icon:book[]]

apoc.create.clonePathsToVirtual
|label:procedure[]
|label:apoc-core[]
|xref::overview/apoc.create/apoc.create.adoc[apoc.create.vPattern icon:book[]]

apoc.create.vPattern({_labels:['LabelA'],key:value},'KNOWS',{key:value,...}, {_labels:['LabelB'],key:value}) returns a virtual pattern
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@
¦procedure¦apoc.create.uuids¦apoc.create.uuids(count :: INTEGER?) :: (row :: INTEGER?, uuid :: STRING?)¦apoc.create.uuids(count) yield uuid - creates 'count' UUIDs ¦true¦
¦procedure¦apoc.create.vNode¦apoc.create.vNode(label :: LIST? OF STRING?, props :: MAP?) :: (node :: NODE?)¦apoc.create.vNode(['Label'], {key:value,...}) returns a virtual node¦true¦xref::virtual/virtual-nodes-rels.adoc
¦procedure¦apoc.create.vNodes¦apoc.create.vNodes(label :: LIST? OF STRING?, props :: LIST? OF MAP?) :: (node :: NODE?)¦apoc.create.vNodes(['Label'], [{key:value,...}]) returns virtual nodes¦true¦xref::virtual/virtual-nodes-rels.adoc
¦procedure¦apoc.create.clonePathToVirtual¦apoc.create.clonePathToVirtual(path :: PATH?) :: (path :: PATH?)¦apoc.create.clonePathToVirtual¦true¦xref::virtual/virtual-nodes-rels.adoc
¦procedure¦apoc.create.clonePathsToVirtual¦apoc.create.clonePathsToVirtual(paths :: LIST? OF PATH?) :: (path :: PATH?)¦apoc.create.clonePathsToVirtual¦true¦xref::virtual/virtual-nodes-rels.adoc
¦procedure¦apoc.create.vPattern¦apoc.create.vPattern(from :: MAP?, relType :: STRING?, props :: MAP?, to :: MAP?) :: (from :: NODE?, rel :: RELATIONSHIP?, to :: NODE?)¦apoc.create.vPattern({_labels:['LabelA'],key:value},'KNOWS',{key:value,...}, {_labels:['LabelB'],key:value}) returns a virtual pattern¦true¦xref::virtual/virtual-nodes-rels.adoc
¦procedure¦apoc.create.vPatternFull¦apoc.create.vPatternFull(labelsN :: LIST? OF STRING?, n :: MAP?, relType :: STRING?, props :: MAP?, labelsM :: LIST? OF STRING?, m :: MAP?) :: (from :: NODE?, rel :: RELATIONSHIP?, to :: NODE?)¦apoc.create.vPatternFull(['LabelA'],{key:value},'KNOWS',{key:value,...},['LabelB'],{key:value}) returns a virtual pattern¦true¦xref::virtual/virtual-nodes-rels.adoc
¦procedure¦apoc.create.vRelationship¦apoc.create.vRelationship(from :: NODE?, relType :: STRING?, props :: MAP?, to :: NODE?) :: (rel :: RELATIONSHIP?)¦apoc.create.vRelationship(nodeFrom,'KNOWS',{key:value,...}, nodeTo) returns a virtual relationship¦true¦xref::virtual/virtual-nodes-rels.adoc
Expand Down Expand Up @@ -123,7 +125,7 @@
¦procedure¦apoc.graph.fromDB¦apoc.graph.fromDB(name :: STRING?, properties :: MAP?) :: (graph :: MAP?)¦apoc.graph.fromDB('name',\{properties}) - creates a virtual graph object for later processing¦true¦xref::virtual/virtual-graph.adoc
¦procedure¦apoc.graph.fromData¦apoc.graph.fromData(nodes :: LIST? OF NODE?, relationships :: LIST? OF RELATIONSHIP?, name :: STRING?, properties :: MAP?) :: (graph :: MAP?)¦apoc.graph.fromData([nodes],[relationships],'name',\{properties}) | creates a virtual graph object for later processing¦true¦xref::virtual/virtual-graph.adoc
¦procedure¦apoc.graph.fromDocument¦apoc.graph.fromDocument(json :: ANY?, config = {} :: MAP?) :: (graph :: MAP?)¦apoc.graph.fromDocument(\{json}, \{config}) yield graph - transform JSON documents into graph structures¦true¦xref::virtual/virtual-graph.adoc
¦procedure¦apoc.graph.fromPath¦apoc.graph.fromPath(path :: PATH?, name :: STRING?, properties :: MAP?) :: (graph :: MAP?)¦apoc.graph.fromPaths(path,'name',\{properties}) - creates a virtual graph object for later processing¦true¦xref::virtual/virtual-graph.adoc
¦procedure¦apoc.graph.fromPath¦apoc.graph.fromPath(path :: PATH?, name :: STRING?, properties :: MAP?) :: (graph :: MAP?)¦apoc.graph.fromPath(path,'name',\{properties}) - creates a virtual graph object for later processing¦true¦xref::virtual/virtual-graph.adoc
¦procedure¦apoc.graph.fromPaths¦apoc.graph.fromPaths(paths :: LIST? OF PATH?, name :: STRING?, properties :: MAP?) :: (graph :: MAP?)¦apoc.graph.fromPaths([paths],'name',\{properties}) - creates a virtual graph object for later processing¦true¦xref::virtual/virtual-graph.adoc
¦procedure¦apoc.graph.validateDocument¦apoc.graph.validateDocument(json :: ANY?, config = {} :: MAP?) :: (row :: MAP?)¦apoc.graph.validateDocument(\{json}, \{config}) yield row - validates the json, return the result of the validation¦true¦xref::virtual/virtual-graph.adoc
¦procedure¦apoc.help¦apoc.help(proc :: STRING?) :: (type :: STRING?, name :: STRING?, text :: STRING?, signature :: STRING?, roles :: LIST? OF STRING?, writes :: BOOLEAN?, core :: BOOLEAN?)¦Provides descriptions of available procedures. To narrow the results, supply a search string. To also search in the description text, append + to the end of the search string.¦true¦
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
////
This file is generated by DocsTest, so don't change it!
////

= apoc.create.clonePathToVirtual
:description: This section contains reference documentation for the apoc.create.clonePathToVirtual procedure.

label:procedure[] label:apoc-core[]

[.emphasis]
apoc.create.clonePathToVirtual

== Signature

[source]
----
apoc.create.clonePathToVirtual(path :: PATH?) :: (path :: PATH?)
----

== Input parameters
[.procedures, opts=header]
|===
| Name | Type | Default
|path|PATH?|null
|===

== Output parameters
[.procedures, opts=header]
|===
| Name | Type
|path|PATH?
|===

xref::virtual/virtual-nodes-rels.adoc[More documentation of apoc.create.clonePathToVirtual,role=more information]

Loading

0 comments on commit 5b0c459

Please sign in to comment.