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 87d47b7
Showing
14 changed files
with
750 additions
and
4 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,138 @@ | ||
Bolt procedures allows to accessing other databases via bolt protocol. | ||
|
||
[cols="3m,2"] | ||
|=== | ||
| CALL apoc.bolt.execute(urlOrKey, statement, params, config) YIELD row | access to other databases via bolt for read and write | ||
| CALL apoc.bolt.load(urlOrKey, statement, params, config) YIELD row | access to other databases via bolt for read | ||
|=== | ||
|
||
**urlOrKey** param allows users to decide if send url by apoc or if put it into neo4j.conf file. | ||
|
||
* **apoc** : write the complete url in his right position on the apoc. | ||
[source,cypher] | ||
---- | ||
call apoc.bolt.execute("bolt://user:password@localhost:7687","match(p:Person {name:{name}}) return p", {name:'Michael'}) | ||
---- | ||
|
||
* **neo4j.conf** : here the are two choices: | ||
1) **complete url**: write the complete url with the param apoc.bolt.url; | ||
|
||
.apoc | ||
|
||
[source,cypher] | ||
---- | ||
call apoc.bolt.execute("","match(p:Person {name:{name}}) return p", {name:'Michael'}) | ||
---- | ||
|
||
.neo4jConf | ||
|
||
[source,txt] | ||
---- | ||
//simple url | ||
apoc.bolt.url=bolt://neo4j:test@localhost:7687 | ||
---- | ||
|
||
|
||
2) **by key**: set the url with a personal key apoc.bolt.yourKey.url; in this case in the apoc on the url param user has to insert the key. | ||
|
||
.apoc | ||
|
||
[source,cypher] | ||
---- | ||
call apoc.bolt.execute("test","match(p:Person {name:{name}}) return p", {name:'Michael'}) | ||
---- | ||
|
||
.neo4jConf | ||
|
||
[source,txt] | ||
---- | ||
//with key | ||
apoc.bolt.test.url=bolt://user:password@localhost:7687 | ||
apoc.bolt.production.url=bolt://password:test@localhost:7688 | ||
---- | ||
|
||
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, in apoc.bolt.load. | ||
== Bolt Examples | ||
|
||
**Return node in map format** | ||
|
||
[source,cypher] | ||
---- | ||
call apoc.bolt.execute("bolt://user:password@localhost:7687", | ||
"match(p:Person {name:{name}}) return p", {name:'Michael'}) | ||
---- | ||
|
||
image::{img}/apoc.bolt.execute.nodemap.png[width=800] | ||
|
||
|
||
**Return node in virtual Node format** | ||
|
||
[source,cypher] | ||
---- | ||
call apoc.bolt.load("bolt://user:password@localhost:7687", | ||
"match(p:Person {name:{name}}) return p", {name:'Michael'}, {virtual:true}) | ||
---- | ||
|
||
image::{img}/apoc.bolt.load.virtualnode.png[width=800] | ||
|
||
|
||
**Create node and return statistic** | ||
|
||
[source,cypher] | ||
---- | ||
call apoc.bolt.execute("bolt://user:password@localhost:7687", | ||
"create(n:Node {name:{name}})", {name:'Node1'}, {statistics:true}) | ||
---- | ||
|
||
image::{img}/apoc.bolt.execute.createandstatistics.png[width=800] | ||
|
||
|
||
**Return more scalar values** | ||
|
||
[source,cypher] | ||
---- | ||
call apoc.bolt.execute("bolt://user:password@localhost:7687", | ||
"match (n:Person {name:{name}}) return n.age as age, n.name as name, n.surname as surname", {name:'Michael'}) | ||
---- | ||
|
||
image::{img}/apoc.bolt.execute.scalarmulti.png[width=800] | ||
|
||
|
||
**Return relationship in a map format** | ||
|
||
[source,cypher] | ||
---- | ||
call apoc.bolt.load("bolt://neo4j:test@localhost:7687", | ||
"MATCH (n:Person{name:{name}})-[r:KNOWS]->(p) return r as rel", {name:'Anne'}) | ||
---- | ||
|
||
image::{img}/apoc.bolt.load.relmap.png[width=800] | ||
|
||
|
||
**Return virtual path** | ||
|
||
[source,cypher] | ||
---- | ||
call apoc.bolt.load("bolt://user:password@localhost:7687", | ||
"START n=node({idNode}) MATCH path= (n)-[r:REL_TYPE*..3]->(o) return path", {idNode:200}, {virtual:true}) | ||
---- | ||
|
||
image::{img}/apoc.bolt.load.returnvirtualpath.png[width=800] | ||
|
||
|
||
**Create a Node with params in input** | ||
|
||
[source,cypher] | ||
---- | ||
call apoc.bolt.execute("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.bolt.execute.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,160 @@ | ||
package apoc.bolt; | ||
|
||
import apoc.Description; | ||
import apoc.result.RowResult; | ||
import apoc.result.VirtualNode; | ||
import apoc.result.VirtualRelationship; | ||
import apoc.util.UriResolver; | ||
import apoc.util.Util; | ||
import org.neo4j.driver.internal.InternalEntity; | ||
import org.neo4j.driver.internal.InternalPath; | ||
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.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.URISyntaxException; | ||
import java.util.Collections; | ||
import java.util.HashMap; | ||
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.bolt.load(url-or-key, statement, params, config) - access to other databases via bolt for read") | ||
public Stream<RowResult> load(@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 URISyntaxException { | ||
|
||
if (params == null) params = Collections.emptyMap(); | ||
if (config == null) config = Collections.emptyMap(); | ||
boolean virtual = (boolean) config.getOrDefault("virtual", false); | ||
boolean addStatistics = (boolean) config.getOrDefault("statistics", false); | ||
UriResolver uri = new UriResolver(url, "bolt"); | ||
uri.initialize(); | ||
Driver driver = null; | ||
Session session = null; | ||
try { | ||
driver = GraphDatabase.driver(uri.getUrlDriver(), AuthTokens.basic(uri.getUser(), uri.getPassword())); | ||
session = driver.session(); | ||
if (addStatistics) | ||
return Stream.of(new RowResult(toMap(runStatement(statement, session, params).summary().counters()))); | ||
|
||
return getRowResultStream(virtual, session, params, statement); | ||
} catch (Exception e) { | ||
throw new RuntimeException(e); | ||
} finally { | ||
session.close(); | ||
driver.close(); | ||
} | ||
} | ||
|
||
@Procedure() | ||
@Description("apoc.bolt.execute(url-or-key, statement, params, config) - access to other databases via bolt for read") | ||
public Stream<RowResult> execute(@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 URISyntaxException { | ||
Map<String, Object> configuration = new HashMap<>(config); | ||
configuration.remove("virtual"); | ||
return load(url, statement, params, configuration); | ||
} | ||
|
||
private StatementResult runStatement(@Name("statement") String statement, Session session, Map<String, Object> finalParams) { | ||
return session.writeTransaction((Transaction tx) -> tx.run(statement, finalParams)); | ||
} | ||
|
||
private Stream<RowResult> getRowResultStream(boolean virtual, Session session, Map<String, Object> params, String statement) { | ||
return runStatement(statement, session, params).list().stream() | ||
.map(record -> record.fields()) | ||
.map(fields -> { | ||
Map<String, Object> map = new HashMap<>(); | ||
for (int i = 0; i < fields.size(); i++) { | ||
String key = fields.get(i).key(); | ||
Object value = fields.get(i).value().asObject(); | ||
if (value instanceof Path) map.putAll(toPath(value, virtual, key)); | ||
else if (value instanceof Node) map.putAll(toNode(value, key, virtual)); | ||
else if (value instanceof Relationship) map.putAll(toRelationship(value, map, key, virtual)); | ||
else map.put(key, value); | ||
} | ||
return map; | ||
}) | ||
.map(resultMap -> new RowResult(resultMap)); | ||
} | ||
|
||
private Map<String, Object> toNode(Object value, String key, boolean virtual) { | ||
Value internalValue = ((InternalEntity) value).asValue(); | ||
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())); | ||
return Util.map(key, virtualNode); | ||
} else | ||
return Util.map(key, Util.map("entityType", internalValue.type().name(), "labels", node.labels(), "id", node.id(), "properties", node.asMap())); | ||
} | ||
|
||
private Map<String, Object> toRelationship(Object value, Map<String, Object> map, String key, boolean virtual) { | ||
Value internalValue = ((InternalEntity) value).asValue(); | ||
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 map; | ||
VirtualRelationship virtualRelationship = new VirtualRelationship(start, end, RelationshipType.withName(relationship.type())); | ||
relationship.asMap().entrySet().iterator().forEachRemaining(i -> virtualRelationship.setProperty(i.getKey(), i.getValue())); | ||
return Util.map(key, virtualRelationship); | ||
} else | ||
return Util.map(key, Util.map("entityType", internalValue.type().name(), "type", relationship.type(), "id", relationship.id(), "start", relationship.startNodeId(), "end", relationship.endNodeId(), "properties", relationship.asMap())); | ||
} | ||
|
||
private Map<String, Object> toPath(Object value, boolean virtual, String key) { | ||
Map<String, Object> map = new HashMap<>(); | ||
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.put(key, Util.map("start", startNode, "end", endNode)); | ||
} else | ||
map.put(key, Util.map("entityType", "NODE", "startLabels", segment.start().labels(), "startId", segment.start().id(), "type", segment.relationship().type(), "endLabels", segment.end().labels(), | ||
"endId", segment.end().id(), "startProperties", segment.start().asMap(), "relProperties", segment.relationship().asMap(), "endProperties", segment.end().asMap())); | ||
}); | ||
return map; | ||
} | ||
|
||
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() | ||
); | ||
} | ||
|
||
} |
Oops, something went wrong.