Skip to content

Commit

Permalink
Creates a VirtualNode from an existing node, filtering its properties
Browse files Browse the repository at this point in the history
 fixes #148
  • Loading branch information
vboulaye authored and jexp committed Oct 4, 2019
1 parent d331c70 commit 90e74b9
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 63 deletions.
10 changes: 10 additions & 0 deletions docs/asciidoc/virtual/create-virtual-nodes-rels.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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]
Expand Down
58 changes: 32 additions & 26 deletions src/main/java/apoc/create/Create.java
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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<NodeResult> node(@Name("label") List<String> labelNames, @Name("props") Map<String, Object> 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)));
}


Expand All @@ -46,7 +43,7 @@ public Stream<NodeResult> 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<NodeResult> 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;
});
}
Expand All @@ -55,7 +52,7 @@ public Stream<NodeResult> 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<RelationshipResult> 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;
});
}
Expand All @@ -64,15 +61,16 @@ public Stream<RelationshipResult> 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<NodeResult> setProperties(@Name("nodes") Object nodes, @Name("keys") List<String> keys, @Name("values") List<Object> 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<NodeResult> removeProperties(@Name("nodes") Object nodes, @Name("keys") List<String> keys) {
return new Get(db).nodes(nodes).map((r) -> {
keys.forEach( r.node::removeProperty );
keys.forEach(r.node::removeProperty);
return r;
});
}
Expand All @@ -81,7 +79,7 @@ public Stream<NodeResult> 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<RelationshipResult> setRelProperties(@Name("rels") Object rels, @Name("keys") List<String> keys, @Name("values") List<Object> 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;
});
}
Expand All @@ -90,7 +88,7 @@ public Stream<RelationshipResult> 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<RelationshipResult> removeRelProperties(@Name("rels") Object rels, @Name("keys") List<String> keys) {
return new Get(db).rels(rels).map((r) -> {
keys.forEach( r.rel::removeProperty);
keys.forEach(r.rel::removeProperty);
return r;
});
}
Expand Down Expand Up @@ -125,6 +123,7 @@ public Stream<NodeResult> 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<NodeResult> nodes(@Name("label") List<String> labelNames, @Name("props") List<Map<String, Object>> props) {
Expand All @@ -137,21 +136,27 @@ public Stream<NodeResult> nodes(@Name("label") List<String> labelNames, @Name("p
public Stream<RelationshipResult> relationship(@Name("from") Node from,
@Name("relType") String relType, @Name("props") Map<String, Object> 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<NodeResult> vNode(@Name("label") List<String> labelNames, @Name("props") Map<String, Object> 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<String> labelNames, @Name(value = "props",defaultValue = "{}") Map<String, Object> props) {
public Node vNodeFunction(@Name("label") List<String> labelNames, @Name(value = "props", defaultValue = "{}") Map<String, Object> 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<String> propertyNames) {
return new VirtualNode(node, propertyNames);
}

@Procedure
@Description("apoc.create.vNodes(['Label'], [{key:value,...}]) returns virtual nodes")
public Stream<NodeResult> vNodes(@Name("label") List<String> labelNames, @Name("props") List<Map<String, Object>> props) {
Expand All @@ -162,21 +167,22 @@ public Stream<NodeResult> vNodes(@Name("label") List<String> labelNames, @Name("
@Procedure
@Description("apoc.create.vRelationship(nodeFrom,'KNOWS',{key:value,...}, nodeTo) returns a virtual relationship")
public Stream<RelationshipResult> vRelationship(@Name("from") Node from, @Name("relType") String relType, @Name("props") Map<String, Object> 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<String, Object> 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<VirtualPathResult> vPattern(@Name("from") Map<String,Object> n,
public Stream<VirtualPathResult> vPattern(@Name("from") Map<String, Object> n,
@Name("relType") String relType, @Name("props") Map<String, Object> props,
@Name("to") Map<String,Object> m) {
n = new LinkedHashMap<>(n); m=new LinkedHashMap<>(m);
@Name("to") Map<String, Object> 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);
Expand All @@ -186,14 +192,14 @@ public Stream<VirtualPathResult> vPattern(@Name("from") Map<String,Object> n,

@Procedure
@Description("apoc.create.vPatternFull(['LabelA'],{key:value},'KNOWS',{key:value,...},['LabelB'],{key:value}) returns a virtual pattern")
public Stream<VirtualPathResult> vPatternFull(@Name("labelsN") List<String> labelsN, @Name("n") Map<String,Object> n,
public Stream<VirtualPathResult> vPatternFull(@Name("labelsN") List<String> labelsN, @Name("n") Map<String, Object> n,
@Name("relType") String relType, @Name("props") Map<String, Object> props,
@Name("labelsM") List<String> labelsM, @Name("m") Map<String,Object> m) {
@Name("labelsM") List<String> labelsM, @Name("m") Map<String, Object> 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 extends PropertyContainer> T setProperties(T pc, Map<String, Object> p) {
Expand All @@ -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;
Expand All @@ -228,7 +234,7 @@ private Object toPropertyValue(Object value) {
@Procedure
@Description("apoc.create.uuids(count) yield uuid - creates 'count' UUIDs ")
public Stream<UUIDResult> 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 {
Expand All @@ -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());
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/main/java/apoc/path/RelationshipTypeAndDirections.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,13 @@ public static List<Pair<RelationshipType, Direction>> 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("[<>:]", "");
Expand Down
18 changes: 13 additions & 5 deletions src/main/java/apoc/result/VirtualNode.java
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -41,6 +42,14 @@ public VirtualNode(long nodeId, GraphDatabaseService db) {
this.db = db;
}

public VirtualNode(Node node, List<String> 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;
Expand Down Expand Up @@ -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
Expand All @@ -175,7 +184,7 @@ public void addLabel(Label label) {
}

public void addLabels(Iterable<Label> labels) {
for (Label label: labels) {
for (Label label : labels) {
addLabel(label);
}
}
Expand Down Expand Up @@ -218,7 +227,7 @@ public Object getProperty(String s, Object o) {

@Override
public void setProperty(String s, Object o) {
props.put(s,o);
props.put(s, o);
}

@Override
Expand Down Expand Up @@ -259,8 +268,7 @@ public int hashCode() {
}

@Override
public String toString()
{
public String toString() {
return "VirtualNode{" + "labels=" + labels + ", props=" + props + ", rels=" + rels + '}';
}
}
Loading

0 comments on commit 90e74b9

Please sign in to comment.