diff --git a/docs/asciidoc/virtual/create-virtual-nodes-rels.adoc b/docs/asciidoc/virtual/create-virtual-nodes-rels.adoc index d69973299f..eb8acd4f8b 100644 --- a/docs/asciidoc/virtual/create-virtual-nodes-rels.adoc +++ b/docs/asciidoc/virtual/create-virtual-nodes-rels.adoc @@ -18,6 +18,7 @@ Please note that they have negative id's. | CALL apoc.create.vNode(['Label'], {key:value,...}) YIELD node | returns a virtual node | apoc.create.vNode(['Label'], {key:value,...}) | function returns a virtual node | CALL apoc.create.vNodes(['Label'], [{key:value,...}]) | returns virtual nodes +| apoc.create.virtual.fromNode(node, [propertyNames]) | function returns a virtual node built from an existing node with only the requested properties | CALL apoc.create.vRelationship(nodeFrom,'KNOWS',{key:value,...}, nodeTo) YIELD rel | returns a virtual relationship | apoc.create.vRelationship(nodeFrom,'KNOWS',{key:value,...}, nodeTo) | function returns a virtual relationship | CALL apoc.create.vPattern({_labels:['LabelA'],key:value},'KNOWS',{key:value,...}, {_labels:['LabelB'],key:value}) | returns a virtual pattern @@ -77,6 +78,15 @@ Virtual nodes and virtual relationships have always a negative id image::vNodeId.png[scaledwidth="100%"] +Virtual nodes can also be built from existing nodes, filtering the properties in order to get a subset of them. +In this case, the Virtual node keeps the id of the original node. + +[source,cypher] +---- +MATCH (node:Person {name:'neo', age:'42'}) +return apoc.create.virtual.fromNode(node, ['name']) as person +---- + .Virtual pattern `vPattern` [source,cypher] diff --git a/src/main/java/apoc/create/Create.java b/src/main/java/apoc/create/Create.java index c64bb91e69..05ecd32de9 100644 --- a/src/main/java/apoc/create/Create.java +++ b/src/main/java/apoc/create/Create.java @@ -1,16 +1,13 @@ package apoc.create; -import org.neo4j.procedure.*; import apoc.get.Get; import apoc.result.*; import apoc.util.Util; import org.neo4j.graphdb.*; import org.neo4j.helpers.collection.Iterables; +import org.neo4j.procedure.*; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; +import java.util.*; import java.util.stream.LongStream; import java.util.stream.Stream; @@ -25,7 +22,7 @@ public class Create { @Procedure(mode = Mode.WRITE) @Description("apoc.create.node(['Label'], {key:value,...}) - create node with dynamic labels") public Stream node(@Name("label") List labelNames, @Name("props") Map props) { - return Stream.of(new NodeResult(setProperties(db.createNode(Util.labels(labelNames)),props))); + return Stream.of(new NodeResult(setProperties(db.createNode(Util.labels(labelNames)), props))); } @@ -46,7 +43,7 @@ public Stream addLabels(@Name("nodes") Object nodes, @Name("label") @Description("apoc.create.setProperty( [node,id,ids,nodes], key, value) - sets the given property on the node(s)") public Stream setProperty(@Name("nodes") Object nodes, @Name("key") String key, @Name("value") Object value) { return new Get(db).nodes(nodes).map((r) -> { - setProperty(r.node, key,toPropertyValue(value)); + setProperty(r.node, key, toPropertyValue(value)); return r; }); } @@ -55,7 +52,7 @@ public Stream setProperty(@Name("nodes") Object nodes, @Name("key") @Description("apoc.create.setRelProperty( [rel,id,ids,rels], key, value) - sets the given property on the relationship(s)") public Stream setRelProperty(@Name("relationships") Object rels, @Name("key") String key, @Name("value") Object value) { return new Get(db).rels(rels).map((r) -> { - setProperty(r.rel,key,toPropertyValue(value)); + setProperty(r.rel, key, toPropertyValue(value)); return r; }); } @@ -64,15 +61,16 @@ public Stream setRelProperty(@Name("relationships") Object r @Description("apoc.create.setProperties( [node,id,ids,nodes], [keys], [values]) - sets the given property on the nodes(s)") public Stream setProperties(@Name("nodes") Object nodes, @Name("keys") List keys, @Name("values") List values) { return new Get(db).nodes(nodes).map((r) -> { - setProperties(r.node, Util.mapFromLists(keys,values)); + setProperties(r.node, Util.mapFromLists(keys, values)); return r; }); } + @Procedure(mode = Mode.WRITE) @Description("apoc.create.removeProperties( [node,id,ids,nodes], [keys]) - removes the given property from the nodes(s)") public Stream removeProperties(@Name("nodes") Object nodes, @Name("keys") List keys) { return new Get(db).nodes(nodes).map((r) -> { - keys.forEach( r.node::removeProperty ); + keys.forEach(r.node::removeProperty); return r; }); } @@ -81,7 +79,7 @@ public Stream removeProperties(@Name("nodes") Object nodes, @Name("k @Description("apoc.create.setRelProperties( [rel,id,ids,rels], [keys], [values]) - sets the given property on the relationship(s)") public Stream setRelProperties(@Name("rels") Object rels, @Name("keys") List keys, @Name("values") List values) { return new Get(db).rels(rels).map((r) -> { - setProperties(r.rel, Util.mapFromLists(keys,values)); + setProperties(r.rel, Util.mapFromLists(keys, values)); return r; }); } @@ -90,7 +88,7 @@ public Stream setRelProperties(@Name("rels") Object rels, @N @Description("apoc.create.removeRelProperties( [rel,id,ids,rels], [keys], [values]) - removes the given property from the relationship(s)") public Stream removeRelProperties(@Name("rels") Object rels, @Name("keys") List keys) { return new Get(db).rels(rels).map((r) -> { - keys.forEach( r.rel::removeProperty); + keys.forEach(r.rel::removeProperty); return r; }); } @@ -125,6 +123,7 @@ public Stream removeLabels(@Name("nodes") Object nodes, @Name("label return r; }); } + @Procedure(mode = Mode.WRITE) @Description("apoc.create.nodes(['Label'], [{key:value,...}]) create multiple nodes with dynamic labels") public Stream nodes(@Name("label") List labelNames, @Name("props") List> props) { @@ -137,21 +136,27 @@ public Stream nodes(@Name("label") List labelNames, @Name("p public Stream relationship(@Name("from") Node from, @Name("relType") String relType, @Name("props") Map props, @Name("to") Node to) { - return Stream.of(new RelationshipResult(setProperties(from.createRelationshipTo(to, withName(relType)),props))); + return Stream.of(new RelationshipResult(setProperties(from.createRelationshipTo(to, withName(relType)), props))); } @Procedure @Description("apoc.create.vNode(['Label'], {key:value,...}) returns a virtual node") public Stream vNode(@Name("label") List labelNames, @Name("props") Map props) { - return Stream.of(new NodeResult(vNodeFunction(labelNames,props))); + return Stream.of(new NodeResult(vNodeFunction(labelNames, props))); } @UserFunction("apoc.create.vNode") @Description("apoc.create.vNode(['Label'], {key:value,...}) returns a virtual node") - public Node vNodeFunction(@Name("label") List labelNames, @Name(value = "props",defaultValue = "{}") Map props) { + public Node vNodeFunction(@Name("label") List labelNames, @Name(value = "props", defaultValue = "{}") Map props) { return new VirtualNode(Util.labels(labelNames), props, db); } + @UserFunction("apoc.create.virtual.fromNode") + @Description("apoc.create.virtual.fromNode(node, [propertyNames]) returns a virtual node built from an existing node with only the requested properties") + public Node virtualFromNodeFunction(@Name("node") Node node, @Name("propertyNames") List propertyNames) { + return new VirtualNode(node, propertyNames); + } + @Procedure @Description("apoc.create.vNodes(['Label'], [{key:value,...}]) returns virtual nodes") public Stream vNodes(@Name("label") List labelNames, @Name("props") List> props) { @@ -162,21 +167,22 @@ public Stream vNodes(@Name("label") List labelNames, @Name(" @Procedure @Description("apoc.create.vRelationship(nodeFrom,'KNOWS',{key:value,...}, nodeTo) returns a virtual relationship") public Stream vRelationship(@Name("from") Node from, @Name("relType") String relType, @Name("props") Map props, @Name("to") Node to) { - return Stream.of(new RelationshipResult(vRelationshipFunction(from,relType,props,to))); + return Stream.of(new RelationshipResult(vRelationshipFunction(from, relType, props, to))); } @UserFunction("apoc.create.vRelationship") @Description("apoc.create.vRelationship(nodeFrom,'KNOWS',{key:value,...}, nodeTo) returns a virtual relationship") public Relationship vRelationshipFunction(@Name("from") Node from, @Name("relType") String relType, @Name("props") Map props, @Name("to") Node to) { - return new VirtualRelationship(from,to, withName(relType)).withProperties(props); + return new VirtualRelationship(from, to, withName(relType)).withProperties(props); } @Procedure @Description("apoc.create.vPattern({_labels:['LabelA'],key:value},'KNOWS',{key:value,...}, {_labels:['LabelB'],key:value}) returns a virtual pattern") - public Stream vPattern(@Name("from") Map n, + public Stream vPattern(@Name("from") Map n, @Name("relType") String relType, @Name("props") Map props, - @Name("to") Map m) { - n = new LinkedHashMap<>(n); m=new LinkedHashMap<>(m); + @Name("to") Map m) { + n = new LinkedHashMap<>(n); + m = new LinkedHashMap<>(m); RelationshipType type = withName(relType); VirtualNode from = new VirtualNode(Util.labels(n.remove("_labels")), n, db); VirtualNode to = new VirtualNode(Util.labels(m.remove("_labels")), m, db); @@ -186,14 +192,14 @@ public Stream vPattern(@Name("from") Map n, @Procedure @Description("apoc.create.vPatternFull(['LabelA'],{key:value},'KNOWS',{key:value,...},['LabelB'],{key:value}) returns a virtual pattern") - public Stream vPatternFull(@Name("labelsN") List labelsN, @Name("n") Map n, + public Stream vPatternFull(@Name("labelsN") List labelsN, @Name("n") Map n, @Name("relType") String relType, @Name("props") Map props, - @Name("labelsM") List labelsM, @Name("m") Map m) { + @Name("labelsM") List labelsM, @Name("m") Map m) { RelationshipType type = withName(relType); VirtualNode from = new VirtualNode(Util.labels(labelsN), n, db); VirtualNode to = new VirtualNode(Util.labels(labelsM), m, db); Relationship rel = new VirtualRelationship(from, to, type).withProperties(props); - return Stream.of(new VirtualPathResult(from,rel,to)); + return Stream.of(new VirtualPathResult(from, rel, to)); } private T setProperties(T pc, Map p) { @@ -219,7 +225,7 @@ private Object toPropertyValue(Object value) { if (value instanceof Iterable) { Iterable it = (Iterable) value; Object first = Iterables.firstOrNull(it); - if (first==null) return EMPTY_ARRAY; + if (first == null) return EMPTY_ARRAY; return Iterables.asArray(first.getClass(), it); } return value; @@ -228,7 +234,7 @@ private Object toPropertyValue(Object value) { @Procedure @Description("apoc.create.uuids(count) yield uuid - creates 'count' UUIDs ") public Stream uuids(@Name("count") long count) { - return LongStream.range(0,count).mapToObj(UUIDResult::new); + return LongStream.range(0, count).mapToObj(UUIDResult::new); } public static class UUIDResult { @@ -238,7 +244,7 @@ public static class UUIDResult { public UUIDResult(long row) { this.row = row; this.uuid = UUID.randomUUID().toString(); - // TODO Long.toHexString(uuid.getMostSignificantBits())+Long.toHexString(uuid.getLeastSignificantBits()); + // TODO Long.toHexString(uuid.getMostSignificantBits())+Long.toHexString(uuid.getLeastSignificantBits()); } } diff --git a/src/main/java/apoc/path/RelationshipTypeAndDirections.java b/src/main/java/apoc/path/RelationshipTypeAndDirections.java index 2cf2bb8535..5132901d04 100644 --- a/src/main/java/apoc/path/RelationshipTypeAndDirections.java +++ b/src/main/java/apoc/path/RelationshipTypeAndDirections.java @@ -44,13 +44,13 @@ public static List> parse(String pathFilter) { return relsAndDirs; } - private static Direction directionFor(String type) { + public static Direction directionFor(String type) { if (type.contains("<")) return INCOMING; if (type.contains(">")) return OUTGOING; return BOTH; } - private static RelationshipType relationshipTypeFor(String name) { + public static RelationshipType relationshipTypeFor(String name) { if (name.indexOf(BACKTICK) > -1) name = name.substring(name.indexOf(BACKTICK)+1,name.lastIndexOf(BACKTICK)); else { name = name.replaceAll("[<>:]", ""); diff --git a/src/main/java/apoc/result/VirtualNode.java b/src/main/java/apoc/result/VirtualNode.java index cf4c1c978c..e02fe4b7dd 100644 --- a/src/main/java/apoc/result/VirtualNode.java +++ b/src/main/java/apoc/result/VirtualNode.java @@ -1,5 +1,6 @@ package apoc.result; +import apoc.util.Util; import org.neo4j.graphdb.*; import org.neo4j.helpers.collection.FilteringIterable; import org.neo4j.helpers.collection.Iterables; @@ -41,6 +42,14 @@ public VirtualNode(long nodeId, GraphDatabaseService db) { this.db = db; } + public VirtualNode(Node node, List propertyNames) { + this.id = node.getId(); + this.db = node.getGraphDatabase(); + this.labels.addAll(Util.labelStrings(node)); + String[] keys = propertyNames.toArray(new String[propertyNames.size()]); + this.props.putAll(node.getProperties(keys)); + } + @Override public long getId() { return id; @@ -166,7 +175,7 @@ public int getDegree(Direction direction) { @Override public int getDegree(RelationshipType relationshipType, Direction direction) { - return (int) Iterables.count(getRelationships(relationshipType,direction)); + return (int) Iterables.count(getRelationships(relationshipType, direction)); } @Override @@ -175,7 +184,7 @@ public void addLabel(Label label) { } public void addLabels(Iterable