forked from neo4j-contrib/neo4j-apoc-procedures
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fixes neo4j-contrib#483 - Allow accessing other databases via bolt
- Loading branch information
1 parent
37b1ae1
commit 1abf917
Showing
15 changed files
with
757 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
|
||
Bolt procedures allows to accessing other databases via bolt protocol. | ||
|
||
[cols="3m,2"] | ||
|=== | ||
| CALL apoc.execute.bolt(url, statement, params, config) YIELD row | access to other databases via bolt for read and write | ||
| CALL apoc.load.bolt(url, statement, params, config) YIELD row | access to other databases via bolt for read | ||
|=== | ||
|
||
Config available are: | ||
|
||
* `statistics`: possible values are true/false, the default value is false. This config print the execution statistics; | ||
* `virtual`: possible values are true/false, the default value is false. This config return result in virtual format and not in map format. | ||
== Bolt Examples | ||
|
||
**Return node in map format** | ||
|
||
[source,cypher] | ||
---- | ||
call apoc.execute.bolt("bolt://user:password@localhost:7687", | ||
"match(p:Person {name:'Michael'}) return p", {}, {}) | ||
---- | ||
|
||
image::{img}/apoc.execute.bolt.nodemap.png[width=800] | ||
|
||
|
||
**Return node in virtual Node format** | ||
|
||
[source,cypher] | ||
---- | ||
call apoc.load.bolt("bolt://user:password@localhost:7687", | ||
"match(p:Person {name:'Michael'}) return p", {}, {virtual:true}) | ||
---- | ||
|
||
image::{img}/apoc.load.bolt.virtualnode.png[width=800] | ||
|
||
|
||
**Create node and return statistic** | ||
|
||
[source,cypher] | ||
---- | ||
call apoc.execute.bolt("bolt://user:password@localhost:7687", | ||
"create(n:Node {name:'Node1'})", {}, {statistics:true}) | ||
---- | ||
|
||
image::{img}/apoc.execute.bolt.createandstatistics.png[width=800] | ||
|
||
|
||
**Return more scalar values** | ||
|
||
[source,cypher] | ||
---- | ||
call apoc.execute.bolt("bolt://user:password@localhost:7687", | ||
"match (n:Person {name:'Michael'}) return n.age as age, n.name as name, n.surname as surname", {}, {}) | ||
---- | ||
|
||
image::{img}/apoc.execute.bolt.scalarmulti.png[width=800] | ||
|
||
|
||
**Return relationship in a map format** | ||
|
||
[source,cypher] | ||
---- | ||
call apoc.load.bolt("bolt://neo4j:test@localhost:7687", | ||
"MATCH (n:Person{name:'Anne'})-[r:KNOWS]->(p) return r as rel", {}, {}) | ||
---- | ||
|
||
image::{img}/apoc.load.bolt.relmap.png[width=800] | ||
|
||
|
||
**Return virtual path** | ||
|
||
[source,cypher] | ||
---- | ||
call apoc.load.bolt("bolt://user:password@localhost:7687", | ||
"START n=node(idNode) MATCH path= (n)-[r:REL_TYPE*..3]->(o) return path", {}, {virtual:true}) | ||
---- | ||
|
||
image::{img}/apoc.load.bolt.returnvirtualpath.png[width=800] | ||
|
||
|
||
**Create a Node with params in input** | ||
|
||
[source,cypher] | ||
---- | ||
call apoc.execute.bolt("bolt://user:password@localhost:7687", | ||
"CREATE (n:Car{brand:{brand},model:{model},year:{year}}) return n", {brand:'Ferrari',model:'California',year:2016}, {}) | ||
---- | ||
|
||
image::{img}/apoc.execute.bolt.createwithparams.png[width=800] |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
package apoc.execute; | ||
|
||
import apoc.Description; | ||
import apoc.result.RowResult; | ||
import apoc.util.UriCreator; | ||
import apoc.util.Util; | ||
import org.neo4j.driver.internal.InternalEntity; | ||
import org.neo4j.driver.internal.InternalPath; | ||
import org.neo4j.driver.internal.value.NodeValue; | ||
import org.neo4j.driver.internal.value.RelationshipValue; | ||
import org.neo4j.driver.v1.*; | ||
import org.neo4j.driver.v1.summary.SummaryCounters; | ||
import org.neo4j.driver.v1.types.Node; | ||
import org.neo4j.driver.v1.types.Path; | ||
import org.neo4j.driver.v1.types.Relationship; | ||
import org.neo4j.graphdb.GraphDatabaseService; | ||
import org.neo4j.procedure.Context; | ||
import org.neo4j.procedure.Name; | ||
import org.neo4j.procedure.Procedure; | ||
|
||
import java.net.MalformedURLException; | ||
import java.util.HashMap; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.stream.Stream; | ||
|
||
import static apoc.util.MapUtil.map; | ||
|
||
/** | ||
* @author AgileLARUS | ||
* @since 29.08.17 | ||
*/ | ||
public class Bolt { | ||
|
||
@Context | ||
public GraphDatabaseService db; | ||
|
||
@Procedure() | ||
@Description("apoc.execute.bolt(url, statement, params, config) - access to other databases via bolt for read and write") | ||
public Stream<RowResult> bolt(@Name("url") String url, @Name("statement") String statement, @Name(value = "params", defaultValue = "{}") Map<String, Object> params, @Name(value = "config", defaultValue = "{}") Map<String, Object> config) throws MalformedURLException { | ||
|
||
boolean addStatistics = (boolean) config.getOrDefault("statistics", false); | ||
UriCreator getUri = new UriCreator(url, config).invoke(); | ||
Driver driver = GraphDatabase.driver(getUri.getUrlDriver(), AuthTokens.basic(getUri.getUser(), getUri.getPassword())); | ||
Session session = driver.session(); | ||
|
||
if (addStatistics) { | ||
SummaryCounters resultSummary = session.writeTransaction((Transaction tx) -> tx.run(statement, params).summary()).counters(); | ||
return Stream.of(new RowResult(toMap(resultSummary))); | ||
} | ||
|
||
List<Record> records = session.writeTransaction((Transaction tx) -> tx.run(statement, params).list()); | ||
Stream<RowResult> results = records.stream() | ||
.map(record -> record.fields()) | ||
.map(keyValueList -> { | ||
Map<String, Object> map = new HashMap<>(); | ||
for (int i = 0; i < keyValueList.size(); i++) { | ||
String key = keyValueList.get(i).key(); | ||
Object value = keyValueList.get(i).value().asObject(); | ||
if (!(value instanceof InternalPath) && !(value instanceof InternalEntity)) | ||
map.putAll(Util.map(key, value)); | ||
getPath(map, value); | ||
getEntity(map, value, key); | ||
} | ||
return map; | ||
}) | ||
.map(resultMap -> new RowResult(resultMap)); | ||
|
||
session.close(); | ||
driver.close(); | ||
|
||
return results; | ||
} | ||
|
||
private Map<String, Object> toMap(SummaryCounters resultSummary) { | ||
return map( | ||
"nodesCreated", resultSummary.nodesCreated(), | ||
"nodesDeleted", resultSummary.nodesDeleted(), | ||
"labelsAdded", resultSummary.labelsAdded(), | ||
"labelsRemoved", resultSummary.labelsRemoved(), | ||
"relationshipsCreated", resultSummary.relationshipsCreated(), | ||
"relationshipsDeleted", resultSummary.relationshipsDeleted(), | ||
"propertiesSet", resultSummary.propertiesSet(), | ||
"constraintsAdded", resultSummary.constraintsAdded(), | ||
"constraintsRemoved", resultSummary.constraintsRemoved(), | ||
"indexesAdded", resultSummary.indexesAdded(), | ||
"indexesRemoved", resultSummary.indexesRemoved() | ||
); | ||
} | ||
|
||
private void getEntity(Map<String, Object> map, Object value, String key) { | ||
Map<String, Object> mapTemp = new HashMap<>(); | ||
if (value instanceof InternalEntity) { | ||
Value internalValue = ((InternalEntity) value).asValue(); | ||
if (internalValue instanceof NodeValue) { | ||
Node node = internalValue.asNode(); | ||
mapTemp = new HashMap<>(); | ||
mapTemp.putAll(Util.map("entityType", internalValue.type().name(), "labels", node.labels(), "id", node.id())); | ||
mapTemp.put("properties", node.asMap()); | ||
map.put(key, mapTemp); | ||
} else if (internalValue instanceof RelationshipValue) { | ||
Relationship relationship = internalValue.asRelationship(); | ||
mapTemp.putAll(Util.map("entityType", internalValue.type().name(), "type", relationship.type(), "id", relationship.id(), "start", relationship.startNodeId(), "end", relationship.endNodeId())); | ||
mapTemp.put("properties", relationship.asMap()); | ||
map.put(key, mapTemp); | ||
} | ||
} | ||
} | ||
|
||
private void getPath(Map<String, Object> map, Object value) { | ||
if (value instanceof InternalPath) { | ||
InternalPath value1 = (InternalPath) value; | ||
Path path = value1.asValue().asPath(); | ||
path.spliterator().forEachRemaining(segment -> { | ||
map.putAll(Util.map("entityType", "NODE", "startLabels", segment.start().labels(), "startId", segment.start().id(), "type", segment.relationship().type(), "endLabels", segment.end().labels(), "endId", segment.end().id())); | ||
map.put("startProperties", segment.start().asMap()); | ||
map.put("relProperties", segment.relationship().asMap()); | ||
map.put("endProperties", segment.end().asMap()); | ||
}); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
package apoc.load; | ||
|
||
import apoc.Description; | ||
import apoc.result.*; | ||
import apoc.util.UriCreator; | ||
import apoc.util.Util; | ||
import org.neo4j.driver.internal.InternalEntity; | ||
import org.neo4j.driver.internal.InternalPath; | ||
import org.neo4j.driver.internal.value.NodeValue; | ||
import org.neo4j.driver.internal.value.RelationshipValue; | ||
import org.neo4j.driver.v1.*; | ||
import org.neo4j.driver.v1.Driver; | ||
import org.neo4j.driver.v1.types.*; | ||
import org.neo4j.graphdb.GraphDatabaseService; | ||
import org.neo4j.graphdb.Label; | ||
import org.neo4j.graphdb.RelationshipType; | ||
import org.neo4j.procedure.Context; | ||
import org.neo4j.procedure.Name; | ||
import org.neo4j.procedure.Procedure; | ||
|
||
import java.net.MalformedURLException; | ||
import java.util.HashMap; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.stream.Stream; | ||
|
||
/** | ||
* @author AgileLARUS | ||
* @since 03.09.17 | ||
*/ | ||
public class Bolt { | ||
@Context | ||
public GraphDatabaseService db; | ||
|
||
@Procedure() | ||
@Description("apoc.load.bolt(url, statement, params, config) - access to other databases via bolt for read") | ||
public Stream<RowResult> bolt(@Name("url") String url, @Name("statement") String statement, @Name(value = "params", defaultValue = "{}") Map<String, Object> params, @Name(value = "config", defaultValue = "{}") Map<String, Object> config) throws MalformedURLException { | ||
|
||
boolean virtual = (boolean) config.getOrDefault("virtual", false); | ||
UriCreator uri = new UriCreator(url, config).invoke(); | ||
Driver driver = GraphDatabase.driver(uri.getUrlDriver(), AuthTokens.basic(uri.getUser(), uri.getPassword())); | ||
Session session = driver.session(); | ||
List<Record> records = session.readTransaction((Transaction tx) -> tx.run(statement, params).list()); | ||
Stream<RowResult> results = records.stream() | ||
.map(record -> record.fields()) | ||
.map(keyValueList -> { | ||
Map<String, Object> map = new HashMap<>(); | ||
for (int i = 0; i < keyValueList.size(); i++) { | ||
String key = keyValueList.get(i).key(); | ||
Object value = keyValueList.get(i).value().asObject(); | ||
if (!(value instanceof InternalPath) && !(value instanceof InternalEntity)) | ||
map.putAll(Util.map(key, value)); | ||
else { | ||
getPath(map, value, virtual); | ||
getEntity(value, map, key, virtual); | ||
} | ||
} | ||
return map; | ||
}) | ||
.map(resultMap -> new RowResult(resultMap)); | ||
session.close(); | ||
driver.close(); | ||
|
||
return results; | ||
} | ||
|
||
private void getEntity(Object value, Map<String, Object> map, String key, boolean virtual) { | ||
Map<String, Object> mapTemp = new HashMap<>(); | ||
if (value instanceof InternalEntity) { | ||
Value internalValue = ((InternalEntity) value).asValue(); | ||
if (internalValue instanceof NodeValue) { | ||
Node node = internalValue.asNode(); | ||
if (virtual) { | ||
VirtualNode virtualNode = new VirtualNode(node.id(), db); | ||
node.labels().forEach(l -> { | ||
virtualNode.addLabel(Label.label(l)); | ||
}); | ||
node.asMap().entrySet().iterator().forEachRemaining(i -> virtualNode.setProperty(i.getKey(), i.getValue())); | ||
map.put(key, virtualNode); | ||
} else { | ||
mapTemp = new HashMap<>(); | ||
mapTemp.putAll(Util.map("entityType", internalValue.type().name(), "labels", node.labels(), "id", node.id())); | ||
mapTemp.put("properties", node.asMap()); | ||
map.put(key, mapTemp); | ||
} | ||
} | ||
if (internalValue instanceof RelationshipValue) { | ||
Relationship relationship = internalValue.asRelationship(); | ||
if (virtual) { | ||
VirtualNode start = new VirtualNode(relationship.startNodeId(), db); | ||
VirtualNode end = new VirtualNode(relationship.endNodeId(), db); | ||
|
||
if(map.values().contains(start) && map.values().contains(end)) | ||
return; | ||
VirtualRelationship virtualRelationship = new VirtualRelationship(start, end, RelationshipType.withName(relationship.type())); | ||
relationship.asMap().entrySet().iterator().forEachRemaining(i -> virtualRelationship.setProperty(i.getKey(), i.getValue())); | ||
map.put(key, virtualRelationship); | ||
} else { | ||
mapTemp.putAll(Util.map("entityType", internalValue.type().name(), "type", relationship.type(), "id", relationship.id(), "start", relationship.startNodeId(), "end", relationship.endNodeId())); | ||
mapTemp.put("properties", relationship.asMap()); | ||
map.put(key, mapTemp); | ||
} | ||
} | ||
} | ||
} | ||
|
||
private void getPath(Map<String, Object> map, Object value, boolean virtual) { | ||
if (value instanceof InternalPath) { | ||
InternalPath value1 = (InternalPath) value; | ||
Path path = value1.asValue().asPath(); | ||
path.spliterator().forEachRemaining(segment -> { | ||
if (virtual) { | ||
VirtualNode startNode = new VirtualNode(segment.start().id(), db); | ||
segment.start().labels().forEach(l -> startNode.addLabel(Label.label(l))); | ||
segment.start().asMap().entrySet().iterator().forEachRemaining(p -> startNode.setProperty(p.getKey(), p.getValue())); | ||
VirtualNode endNode = new VirtualNode(segment.end().id(), db); | ||
segment.end().labels().forEach(l -> endNode.addLabel(Label.label(l))); | ||
segment.end().asMap().entrySet().iterator().forEachRemaining(p -> endNode.setProperty(p.getKey(), p.getValue())); | ||
map.putAll(Util.map("start", startNode, "end", endNode)); | ||
} else { | ||
map.putAll(Util.map("entityType", "NODE", "startLabels", segment.start().labels(), "startId", segment.start().id(), "type", segment.relationship().type(), "endLabels", segment.end().labels(), "endId", segment.end().id())); | ||
map.put("startProperties", segment.start().asMap()); | ||
map.put("relProperties", segment.relationship().asMap()); | ||
map.put("endProperties", segment.end().asMap()); | ||
} | ||
}); | ||
} | ||
} | ||
} |
Oops, something went wrong.