Skip to content

Commit

Permalink
Fixes neo4j-contrib#483 - Allow accessing other databases via bolt
Browse files Browse the repository at this point in the history
  • Loading branch information
AngeloBusato committed Sep 6, 2017
1 parent 37b1ae1 commit 1abf917
Show file tree
Hide file tree
Showing 15 changed files with 757 additions and 0 deletions.
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ dependencies {
testCompile group: 'org.neo4j', name: 'neo4j-causal-clustering', version:neo4jVersion, classifier: "tests"
testCompile group: 'org.neo4j', name: 'neo4j-kernel', version:neo4jVersion, classifier: "tests"
testCompile group: 'org.neo4j', name: 'neo4j-io', version:neo4jVersion, classifier: "tests"
testCompile group: 'org.neo4j', name: 'neo4j-bolt', version: '3.0.1'

compileOnly 'org.mongodb:mongodb-driver:3.2.2'
testCompile 'org.mongodb:mongodb-driver:3.2.2'
Expand Down
92 changes: 92 additions & 0 deletions docs/bolt.adoc
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.
Binary file added docs/img/apoc.execute.bolt.createwithparams.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/img/apoc.execute.bolt.nodemap.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/img/apoc.execute.bolt.scalarmulti.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/img/apoc.load.bolt.relmap.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/img/apoc.load.bolt.returnvirtualpath.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/img/apoc.load.bolt.virtualnode.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions docs/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,10 @@ include::schema.adoc[leveloffset=2]

include::atomic.adoc[leveloffset=1]

== Bolt

include::bolt.adoc[leveloffset=1]

== Appendix: Complete Overview

include::overview.adoc[tags=overview,leveloffset=1]
Expand Down
122 changes: 122 additions & 0 deletions src/main/java/apoc/execute/Bolt.java
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());
});
}
}
}
129 changes: 129 additions & 0 deletions src/main/java/apoc/load/Bolt.java
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());
}
});
}
}
}
Loading

0 comments on commit 1abf917

Please sign in to comment.