diff --git a/build.gradle b/build.gradle index b3b3995333..50ba89f566 100644 --- a/build.gradle +++ b/build.gradle @@ -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' diff --git a/docs/bolt.adoc b/docs/bolt.adoc new file mode 100644 index 0000000000..26292d09a8 --- /dev/null +++ b/docs/bolt.adoc @@ -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] diff --git a/docs/img/apoc.bolt.execute.createandstatistics.PNG b/docs/img/apoc.bolt.execute.createandstatistics.PNG new file mode 100644 index 0000000000..4051191bee Binary files /dev/null and b/docs/img/apoc.bolt.execute.createandstatistics.PNG differ diff --git a/docs/img/apoc.bolt.execute.createwithparams.PNG b/docs/img/apoc.bolt.execute.createwithparams.PNG new file mode 100644 index 0000000000..1e747e1c6e Binary files /dev/null and b/docs/img/apoc.bolt.execute.createwithparams.PNG differ diff --git a/docs/img/apoc.bolt.execute.nodemap.PNG b/docs/img/apoc.bolt.execute.nodemap.PNG new file mode 100644 index 0000000000..0656b427ce Binary files /dev/null and b/docs/img/apoc.bolt.execute.nodemap.PNG differ diff --git a/docs/img/apoc.bolt.load.relmap.PNG b/docs/img/apoc.bolt.load.relmap.PNG new file mode 100644 index 0000000000..9fa5181a02 Binary files /dev/null and b/docs/img/apoc.bolt.load.relmap.PNG differ diff --git a/docs/img/apoc.bolt.load.returnvirtualpath.PNG b/docs/img/apoc.bolt.load.returnvirtualpath.PNG new file mode 100644 index 0000000000..9771357d5c Binary files /dev/null and b/docs/img/apoc.bolt.load.returnvirtualpath.PNG differ diff --git a/docs/img/apoc.bolt.load.scalarmulti.PNG b/docs/img/apoc.bolt.load.scalarmulti.PNG new file mode 100644 index 0000000000..e3bd421761 Binary files /dev/null and b/docs/img/apoc.bolt.load.scalarmulti.PNG differ diff --git a/docs/img/apoc.bolt.load.virtualnode.PNG b/docs/img/apoc.bolt.load.virtualnode.PNG new file mode 100644 index 0000000000..449ff8a177 Binary files /dev/null and b/docs/img/apoc.bolt.load.virtualnode.PNG differ diff --git a/docs/index.adoc b/docs/index.adoc index ed51fd1fa1..db4fe4da44 100644 --- a/docs/index.adoc +++ b/docs/index.adoc @@ -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] diff --git a/src/main/java/apoc/bolt/Bolt.java b/src/main/java/apoc/bolt/Bolt.java new file mode 100644 index 0000000000..45c0133980 --- /dev/null +++ b/src/main/java/apoc/bolt/Bolt.java @@ -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 load(@Name("url") String url, @Name("statement") String statement, @Name(value = "params", defaultValue = "{}") Map params, @Name(value = "config", defaultValue = "{}") Map 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 execute(@Name("url") String url, @Name("statement") String statement, @Name(value = "params", defaultValue = "{}") Map params, @Name(value = "config", defaultValue = "{}") Map config) throws URISyntaxException { + Map configuration = new HashMap<>(config); + configuration.remove("virtual"); + return load(url, statement, params, configuration); + } + + private StatementResult runStatement(@Name("statement") String statement, Session session, Map finalParams) { + return session.writeTransaction((Transaction tx) -> tx.run(statement, finalParams)); + } + + private Stream getRowResultStream(boolean virtual, Session session, Map params, String statement) { + return runStatement(statement, session, params).list().stream() + .map(record -> record.fields()) + .map(fields -> { + Map 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 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 toRelationship(Object value, Map 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 toPath(Object value, boolean virtual, String key) { + Map 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 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() + ); + } + +} diff --git a/src/main/java/apoc/util/UriResolver.java b/src/main/java/apoc/util/UriResolver.java new file mode 100644 index 0000000000..37fa584bea --- /dev/null +++ b/src/main/java/apoc/util/UriResolver.java @@ -0,0 +1,102 @@ +package apoc.util; + + +import apoc.ApocConfiguration; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.HashMap; +import java.util.Map; + +/** + * @author AgileLARUS + * @since 03.09.17 + */ +public class UriResolver { + + private String user; + private String password; + private String urlDriver; + private int port; + private String context; + private String query; + private String url; + private String prefix; + private Map queryParams; + + public UriResolver(String url, String prefix) throws URISyntaxException { + this.url = url; + this.prefix = prefix; + } + + public String getUser() { + return user; + } + + public String getPassword() { + return password; + } + + public String getUrlDriver() { + return urlDriver; + } + + public int getPort() { + return port; + } + + public String getContext() { + return context; + } + + public String getQuery() { + return query; + } + + public Map getQueryParams() { + return queryParams; + } + + private String getUri(String key, String prefix) { + String keyUrl = prefix + "." + key + ".url"; + if (key == null || key.equals("")) + key = ApocConfiguration.get("bolt.url", key); + else if (ApocConfiguration.isEnabled(keyUrl)) + key = ApocConfiguration.get(keyUrl, key); + + return key; + } + + public void initialize() throws URISyntaxException { + this.url = getUri(this.url, this.prefix); + URI uri; + try { + uri = new URI(this.url); + } catch (URISyntaxException e) { + throw new URISyntaxException(e.getInput(), e.getMessage()); + } + if (uri.getUserInfo() != null) { + String[] userInfoArray = uri.getUserInfo().split(":"); + this.user = userInfoArray[0]; + this.password = userInfoArray[1]; + } else + throw new RuntimeException("user and password don't defined check your URL or if you use a key the property in your neo4j.conf file"); + this.context = uri.getPath(); + this.urlDriver = uri.getScheme() + "://" + uri.getHost(); + this.port = uri.getPort(); + this.query = uri.getQuery(); + if (this.query != null) + this.queryParams = getQueryParams(uri); + } + + private Map getQueryParams(URI uri) { + Map queryParamsMap = new HashMap<>(); + String[] pairs = uri.getQuery().split("&"); + for (String pair : pairs) { + int index = pair.indexOf("="); + if (index != -1) + queryParamsMap.put(pair.substring(0, index), pair.substring(index + 1)); + } + return queryParamsMap; + } +} diff --git a/src/test/java/apoc/bolt/BoltTest.java b/src/test/java/apoc/bolt/BoltTest.java new file mode 100644 index 0000000000..32c163e7f8 --- /dev/null +++ b/src/test/java/apoc/bolt/BoltTest.java @@ -0,0 +1,343 @@ +package apoc.bolt; + +import apoc.util.TestUtil; +import apoc.util.Util; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.neo4j.graphdb.GraphDatabaseService; +import org.neo4j.graphdb.Label; +import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.Relationship; +import org.neo4j.test.TestGraphDatabaseFactory; + +import java.net.ConnectException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static apoc.util.TestUtil.testCall; +import static apoc.util.TestUtil.testResult; +import static org.junit.Assert.*; + +/** + * @author AgileLARUS + * @since 29.08.17 + */ +public class BoltTest { + + private static final String BOLT_URL = "'bolt://neo4j:test@localhost:7687'"; + protected static GraphDatabaseService db; + + @BeforeClass + public static void setUp() throws Exception { + db = new TestGraphDatabaseFactory().newImpermanentDatabaseBuilder().newGraphDatabase(); + TestUtil.registerProcedure(db, Bolt.class); + } + + @AfterClass + public static void tearDown() { + db.shutdown(); + } + + //DATASET + /* + CREATE (p:Person {name:'Michael',surname:'Jordan',age:54, state:true}) + CREATE (q:Person {name:'Tom',surname:'Burton',age:23}) + CREATE (p:Person {name:'John',surname:'William',age:22}) + CREATE (q)-[:KNOWS{since:2016}]->(p) + */ + + @Test + public void testLoadNodeVirtual() throws Exception { + TestUtil.ignoreException(() -> { + testCall(db, "call apoc.bolt.load(" + BOLT_URL + ",'match(p:Person {name:{name}}) return p', {name:'Michael'}, {virtual:true})", r -> { + assertNotNull(r); + Map row = (Map) r.get("row"); + Node node = (Node) row.get("p"); + assertEquals(true, node.hasLabel(Label.label("Person"))); + assertEquals("Michael", node.getProperty("name")); + assertEquals("Jordan", node.getProperty("surname")); + assertEquals(true, node.getProperty("state")); + assertEquals(54L, node.getProperty("age")); + }); + }, ConnectException.class); + } + + @Test + public void testLoadNodesVirtual() throws Exception { + TestUtil.ignoreException(() -> { + testResult(db, "call apoc.bolt.load(" + BOLT_URL + ",'match(n) return n', {}, {virtual:true})", r -> { + assertNotNull(r); + Map row = r.next(); + Map result = (Map) row.get("row"); + Node node = (Node) result.get("n"); + assertEquals(true, node.hasLabel(Label.label("Person"))); + assertEquals("Tom", node.getProperty("name")); + assertEquals("Burton", node.getProperty("surname")); + assertEquals(23L, node.getProperty("age")); + row = r.next(); + result = (Map) row.get("row"); + node = (Node) result.get("n"); + assertEquals(true, node.hasLabel(Label.label("Person"))); + assertEquals("John", node.getProperty("name")); + assertEquals("William", node.getProperty("surname")); + assertEquals(22L, node.getProperty("age")); + row = r.next(); + result = (Map) row.get("row"); + node = (Node) result.get("n"); + assertEquals(true, node.hasLabel(Label.label("Person"))); + assertEquals("Michael", node.getProperty("name")); + assertEquals("Jordan", node.getProperty("surname")); + assertEquals(true, node.getProperty("state")); + assertEquals(54L, node.getProperty("age")); + + }); + }, ConnectException.class); + } + + @Test + public void testLoadPathVirtual() throws Exception { + TestUtil.ignoreException(() -> { + testCall(db, "call apoc.bolt.load(" + BOLT_URL + + ",'START neo=node({idNode}) MATCH path= (neo)-[r:KNOWS*..4]->(other) return path', {idNode:1}, {virtual:true})", r -> { + assertNotNull(r); + Map row = (Map) r.get("row"); + Map path = (Map) row.get("path"); + Node start = (Node) path.get("start"); + assertEquals(true, start.hasLabel(Label.label("Person"))); + assertEquals("Tom", start.getProperty("name")); + assertEquals("Burton", start.getProperty("surname")); + assertEquals(23L, start.getProperty("age")); + Node end = (Node) path.get("end"); + assertEquals(true, end.hasLabel(Label.label("Person"))); + assertEquals("John", end.getProperty("name")); + assertEquals("William", end.getProperty("surname")); + assertEquals(22L, end.getProperty("age")); + }); + }, ConnectException.class); + } + + @Test + public void testLoadRel() throws Exception { + TestUtil.ignoreException(() -> { + testCall(db, "call apoc.bolt.load(" + BOLT_URL + ",'match(p)-[r]->(c) return r', {}, {virtual:true})", r -> { + assertNotNull(r); + Map row = (Map) r.get("row"); + Relationship rel = (Relationship) row.get("r"); + assertEquals("KNOWS", rel.getType().name()); + assertEquals(2016L, rel.getProperty("since")); + }); + }, ConnectException.class); + } + + @Test + public void testLoadRelsAndNodes() throws Exception { + TestUtil.ignoreException(() -> { + testCall(db, "call apoc.bolt.load(" + BOLT_URL + ",'match(p)-[r]->(c) return *', {}, {virtual:true})", r -> { + Map result = (Map) r.get("row"); + Node node = (Node) result.get("p"); + assertEquals(true, node.hasLabel(Label.label("Person"))); + assertEquals("Tom", node.getProperty("name")); + assertEquals("Burton", node.getProperty("surname")); + assertEquals(23L, node.getProperty("age")); + node = (Node) result.get("c"); + assertEquals(true, node.hasLabel(Label.label("Person"))); + assertEquals("John", node.getProperty("name")); + assertEquals("William", node.getProperty("surname")); + assertEquals(22L, node.getProperty("age")); + }); + }, ConnectException.class); + } + + @Test + public void testLoadNullParams() throws Exception { + TestUtil.ignoreException(() -> { + testCall(db, "call apoc.bolt.load(\"bolt://neo4j:test@localhost:7687\",\"match(p:Person {name:'Michael'}) return p\")", r -> { + assertNotNull(r); + Map row = (Map) r.get("row"); + Map node = (Map) row.get("p"); + assertTrue(node.containsKey("entityType")); + assertEquals("NODE", node.get("entityType")); + assertTrue(node.containsKey("properties")); + Map properties = (Map) node.get("properties"); + assertEquals("Michael", properties.get("name")); + assertEquals("Jordan", properties.get("surname")); + assertEquals(54L, properties.get("age")); + assertEquals(true, properties.get("state")); + }); + }, ConnectException.class); + } + + @Test + public void testLoadNode() throws Exception { + TestUtil.ignoreException(() -> { + TestUtil.testCall(db, "call apoc.bolt.load(" + BOLT_URL + ",'match (p:Person {name:{name}}) return p', {name:'Michael'})", r -> { + assertNotNull(r); + Map row = (Map) r.get("row"); + Map node = (Map) row.get("p"); + assertTrue(node.containsKey("entityType")); + assertEquals("NODE", node.get("entityType")); + assertTrue(node.containsKey("properties")); + Map properties = (Map) node.get("properties"); + assertEquals("Michael", properties.get("name")); + assertEquals("Jordan", properties.get("surname")); + assertEquals(54L, properties.get("age")); + assertEquals(true, properties.get("state")); + }); + }, ConnectException.class); + } + + @Test + public void testLoadScalarSingleReusult() throws Exception { + TestUtil.ignoreException(() -> { + testCall(db, "call apoc.bolt.load(" + BOLT_URL + ",'match (n:Person {name:{name}}) return n.age as Age', {name:'Michael'})", (r) -> { + assertNotNull(r); + Map row = (Map) r.get("row"); + assertTrue(row.containsKey("Age")); + assertEquals(54L, row.get("Age")); + + }); + }, ConnectException.class); + } + + @Test + public void testLoadMixedContent() throws Exception { + TestUtil.ignoreException(() -> { + TestUtil.testCall(db, "call apoc.bolt.load(" + BOLT_URL + ",'match (n:Person {name:{name}}) return n.age, n.name, n.state', {name:'Michael'})", + r -> { + assertNotNull(r); + Map row = (Map) r.get("row"); + assertTrue(row.containsKey("n.age")); + assertEquals(54L, row.get("n.age")); + assertTrue(row.containsKey("n.name")); + assertEquals("Michael", row.get("n.name")); + assertTrue(row.containsKey("n.state")); + assertEquals(true, row.get("n.state")); + }); + }, ConnectException.class); + } + + @Test + public void testLoadList() throws Exception { + TestUtil.ignoreException(() -> { + TestUtil.testCall(db, "call apoc.bolt.load(" + BOLT_URL + + ",'match (p:Person {name:{name}}) with collect({personName:p.name}) as rows return rows', {name:'Michael'})", r -> { + assertNotNull(r); + Map row = (Map) r.get("row"); + List p = (List) row.get("rows"); + Map result = (Map) p.get(0); + assertTrue(result.containsKey("personName")); + assertEquals("Michael", result.get("personName")); + }); + }, ConnectException.class); + } + + @Test + public void testLoadMap() throws Exception { + TestUtil.ignoreException(() -> { + TestUtil.testCall(db, "call apoc.bolt.load(" + BOLT_URL + + ",'match (p:Person {name:{name}}) with p,collect({personName:p.name}) as rows return p{.*, rows:rows}', {name:'Michael'})", r -> { + assertNotNull(r); + Map row = (Map) r.get("row"); + Map p = (Map) row.get("p"); + assertTrue(p.containsKey("name")); + assertEquals("Michael", p.get("name")); + assertTrue(p.containsKey("age")); + assertEquals(54L, p.get("age")); + assertTrue(p.containsKey("surname")); + assertEquals("Jordan", p.get("surname")); + assertTrue(p.containsKey("state")); + assertEquals(true, p.get("state")); + }); + }, ConnectException.class); + } + + @Test + public void testLoadPath() throws Exception { + TestUtil.ignoreException(() -> { + TestUtil.testCall(db, "call apoc.bolt.load(" + BOLT_URL + ",'START neo=node({idNode}) MATCH path= (neo)-[r:KNOWS*..4]->(other) return path', {idNode:1})", r -> { + assertNotNull(r); + Map row = (Map) r.get("row"); + Map path = (Map) row.get("path"); + + assertEquals("KNOWS", path.get("type")); + assertEquals(Arrays.asList("Person"), path.get("startLabels")); + assertEquals(Arrays.asList("Person"), path.get("endLabels")); + + Map startProperties = (Map) path.get("startProperties"); + assertEquals("Tom", startProperties.get("name")); + assertEquals("Burton", startProperties.get("surname")); + assertEquals(23L, startProperties.get("age")); + + Map endProperties = (Map) path.get("endProperties"); + assertEquals("John", endProperties.get("name")); + assertEquals("William", endProperties.get("surname")); + assertEquals(22L, endProperties.get("age")); + + Map relProperties = (Map) path.get("relProperties"); + assertEquals(2016L, relProperties.get("since")); + }); + }, ConnectException.class); + } + + @Test + public void testLoadRels() throws Exception { + TestUtil.ignoreException(() -> { + testCall(db, "call apoc.bolt.load(" + BOLT_URL + ",'match (n)-[r]->(c) return r as rel', {})", (r) -> { + assertNotNull(r); + Map row = (Map) r.get("row"); + Map rel = (Map) row.get("rel"); + assertEquals(1L, rel.get("start")); + assertEquals(8L, rel.get("end")); + assertEquals("RELATIONSHIP", rel.get("entityType")); + assertEquals("KNOWS", rel.get("type")); + Map properties = (Map) rel.get("properties"); + assertEquals(2016L, properties.get("since")); + }); + }, ConnectException.class); + } + + @Test + public void testLoadCreateNodeStatistic() throws Exception { + TestUtil.ignoreException(() -> { + testResult(db, "call apoc.bolt.execute(" + BOLT_URL + ",'create(n:Node {name:{name}})', {name:'Node1'}, {statistics:true})", Collections.emptyMap(), + r -> { + assertNotNull(r); + Map row = r.next(); + Map result = (Map) row.get("row"); + assertEquals(1L, toLong(result.get("nodesCreated"))); + assertEquals(1L, toLong(result.get("labelsAdded"))); + assertEquals(1L, toLong(result.get("propertiesSet"))); + assertEquals(false, r.hasNext()); + }); + }, ConnectException.class); + } + + @Test + public void testLoadNoVirtual() throws Exception { + TestUtil.ignoreException(() -> { + testCall(db, + "call apoc.bolt.load(\"bolt://neo4j:test@localhost:7687\",\"match(p:Person {name:'Michael'}) return p\", {}, {virtual:false, test:false})", + r -> { + assertNotNull(r); + Map row = (Map) r.get("row"); + Map node = (Map) row.get("p"); + assertTrue(node.containsKey("entityType")); + assertEquals("NODE", node.get("entityType")); + assertTrue(node.containsKey("properties")); + Map properties = (Map) node.get("properties"); + assertEquals("Michael", properties.get("name")); + assertEquals("Jordan", properties.get("surname")); + assertEquals(54L, properties.get("age")); + assertEquals(true, properties.get("state")); + }); + }, ConnectException.class); + } + + private long toLong(Object value) { + return Util.toLong(value); + } +} + diff --git a/src/test/java/apoc/util/UtilTest.java b/src/test/java/apoc/util/UtilTest.java index e6c199d894..3e18616a06 100644 --- a/src/test/java/apoc/util/UtilTest.java +++ b/src/test/java/apoc/util/UtilTest.java @@ -69,7 +69,7 @@ public void testPartitionList() throws Exception { assertEquals(10,Util.partitionSubList(list,11).count()); assertEquals(10,Util.partitionSubList(list,20).count()); } - + @Test public void cleanPassword() throws Exception { String url = "http://%slocalhost:7474/path?query#ref"; @@ -80,4 +80,4 @@ public void cleanPassword() throws Exception { public void mergeNullMaps() { assertNotNull(Util.merge(null, null)); } -} +} \ No newline at end of file