This is an automated email from the ASF dual-hosted git repository. felixcheung pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/zeppelin.git
The following commit(s) were added to refs/heads/master by this push: new 9a185f1 [ZEPPELIN-3882] Neo4jInterpreter - Support Point and Date Types (#3284) 9a185f1 is described below commit 9a185f19124259f0d510a3669d1023065bdf6197 Author: Andrea Santurbano <sant...@gmail.com> AuthorDate: Mon Jan 21 20:51:01 2019 +0100 [ZEPPELIN-3882] Neo4jInterpreter - Support Point and Date Types (#3284) ### What is this PR for? Add the support for Point and Date data types introduced since Neo4j 3.4 ### What type of PR is it? [Improvement] In order to allow users to use the Neo4j Interpreter with the last introduced Data types ### Todos * [x] - Bumped Neo4j dependecies version * [x] - Added new data types to test ### What is the Jira issue? * Put link here, and add [ZEPPELIN-3882](https://issues.apache.org/jira/projects/ZEPPELIN/issues/ZEPPELIN-3882) ### How should this be tested? * Execute the unit tests ### Screenshots (if appropriate) ### Questions: * Does the licenses files need update? no * Is there breaking changes for older versions? no * Does this needs documentation? no * Added support for Neo4j temporal and point types * fix checkstyle --- docs/interpreter/neo4j.md | 3 + neo4j/pom.xml | 6 +- .../graph/neo4j/Neo4jCypherInterpreter.java | 14 +++ .../graph/neo4j/Neo4jCypherInterpreterTest.java | 105 +++++++++++++++++---- 4 files changed, 108 insertions(+), 20 deletions(-) diff --git a/docs/interpreter/neo4j.md b/docs/interpreter/neo4j.md index 1b14127..eec9e07 100644 --- a/docs/interpreter/neo4j.md +++ b/docs/interpreter/neo4j.md @@ -26,6 +26,9 @@ limitations under the License. ## Overview [Neo4j](https://neo4j.com/product/) is a native graph database, designed to store and process graphs from bottom to top. +### Supported Version + +The Neo4j Interpreter supports all Neo4j versions since v3 via the official [Neo4j Java Driver](https://github.com/neo4j/neo4j-java-driver)  diff --git a/neo4j/pom.xml b/neo4j/pom.xml index b8a89ad..cc39fdc 100644 --- a/neo4j/pom.xml +++ b/neo4j/pom.xml @@ -33,9 +33,9 @@ <name>Zeppelin: Neo4j interpreter</name> <properties> - <neo4j.driver.version>1.4.3</neo4j.driver.version> - <test.neo4j.kernel.version>3.2.3</test.neo4j.kernel.version> - <neo4j.version>3.2.3</neo4j.version> + <neo4j.driver.version>1.7.1</neo4j.driver.version> + <test.neo4j.kernel.version>3.4.10</test.neo4j.kernel.version> + <neo4j.version>3.4.10</neo4j.version> <jackson.version>2.8.9</jackson.version> <interpreter.name>neo4j</interpreter.name> </properties> diff --git a/neo4j/src/main/java/org/apache/zeppelin/graph/neo4j/Neo4jCypherInterpreter.java b/neo4j/src/main/java/org/apache/zeppelin/graph/neo4j/Neo4jCypherInterpreter.java index bcb9d7b..d7f8485 100644 --- a/neo4j/src/main/java/org/apache/zeppelin/graph/neo4j/Neo4jCypherInterpreter.java +++ b/neo4j/src/main/java/org/apache/zeppelin/graph/neo4j/Neo4jCypherInterpreter.java @@ -201,6 +201,20 @@ public class Neo4jCypherInterpreter extends Interpreter { value = val.asList(); } else if (val.hasType(InternalTypeSystem.TYPE_SYSTEM.MAP())) { value = val.asMap(); + } else if (val.hasType(InternalTypeSystem.TYPE_SYSTEM.POINT())) { + value = val.asPoint(); + } else if (val.hasType(InternalTypeSystem.TYPE_SYSTEM.DATE())) { + value = val.asLocalDate(); + } else if (val.hasType(InternalTypeSystem.TYPE_SYSTEM.TIME())) { + value = val.asOffsetTime(); + } else if (val.hasType(InternalTypeSystem.TYPE_SYSTEM.LOCAL_TIME())) { + value = val.asLocalTime(); + } else if (val.hasType(InternalTypeSystem.TYPE_SYSTEM.LOCAL_DATE_TIME())) { + value = val.asLocalDateTime(); + } else if (val.hasType(InternalTypeSystem.TYPE_SYSTEM.DATE_TIME())) { + value = val.asZonedDateTime(); + } else if (val.hasType(InternalTypeSystem.TYPE_SYSTEM.DURATION())) { + value = val.asIsoDuration(); } } if (value instanceof Collection) { diff --git a/neo4j/src/test/java/org/apache/zeppelin/graph/neo4j/Neo4jCypherInterpreterTest.java b/neo4j/src/test/java/org/apache/zeppelin/graph/neo4j/Neo4jCypherInterpreterTest.java index 24bd513..7940d5f 100644 --- a/neo4j/src/test/java/org/apache/zeppelin/graph/neo4j/Neo4jCypherInterpreterTest.java +++ b/neo4j/src/test/java/org/apache/zeppelin/graph/neo4j/Neo4jCypherInterpreterTest.java @@ -55,10 +55,15 @@ public class Neo4jCypherInterpreterTest { private static final String REL_KNOWS = "KNOWS"; private static final String CYPHER_FOREACH = - "FOREACH (x in range(1,1000) | CREATE (:%s{name: \"name\" + x, age: %s}))"; - private static final String CHPHER_UNWIND = "UNWIND range(1,1000) as x " - + "MATCH (n), (m) WHERE id(n) = x AND id(m) = toInt(rand() * 1000) " + "FOREACH (x in range(1,100) | CREATE (:%s{name: \"name\" + x, age: %s, " + + "address: point({ longitude: 56.7, latitude: 12.78, height: 8 }), " + + "birth: date('1984-04-04')}))"; + private static final String CHPHER_UNWIND = "UNWIND range(1,100) as x " + + "MATCH (n), (m) WHERE id(n) = x AND id(m) = toInt(rand() * 100) " + "CREATE (n)-[:%s]->(m)"; + + private static final String TABLE_RESULT_PREFIX = "%table "; + private static final String NETWORK_RESULT_PREFIX = "%network "; @BeforeClass public static void setUpNeo4jServer() throws Exception { @@ -73,7 +78,7 @@ public class Neo4jCypherInterpreterTest { public static void tearDownNeo4jServer() throws Exception { server.close(); } - + @Before public void setUpZeppelin() { Properties p = new Properties(); @@ -83,7 +88,7 @@ public class Neo4jCypherInterpreterTest { interpreter = new Neo4jCypherInterpreter(p); context = InterpreterContext.builder() .setInterpreterOut(new InterpreterOutput(null)) - .build();; + .build(); } @After @@ -98,14 +103,15 @@ public class Neo4jCypherInterpreterTest { "return 'a' as colA, 'b' as colB, [1, 2, 3] as colC", context); assertEquals(Code.SUCCESS, result.code()); final String tableResult = "colA\tcolB\tcolC\n\"a\"\t\"b\"\t[1,2,3]\n"; - assertEquals(tableResult, result.toString().replace("%table ", StringUtils.EMPTY)); - + assertEquals(tableResult, result.toString().replace(TABLE_RESULT_PREFIX, StringUtils.EMPTY)); + result = interpreter.interpret( "return 'a' as colA, 'b' as colB, [{key: \"value\"}, {key: 1}] as colC", context); assertEquals(Code.SUCCESS, result.code()); final String tableResultWithMap = "colA\tcolB\tcolC\n\"a\"\t\"b\"\t[{\"key\":\"value\"},{\"key\":1}]\n"; - assertEquals(tableResultWithMap, result.toString().replace("%table ", StringUtils.EMPTY)); + assertEquals(tableResultWithMap, result.toString().replace(TABLE_RESULT_PREFIX, + StringUtils.EMPTY)); } @Test @@ -121,10 +127,14 @@ public class Neo4jCypherInterpreterTest { interpreter.open(); InterpreterResult result = interpreter.interpret("MATCH (n:Person) " + "WHERE n.name IN ['name1', 'name2', 'name3'] " - + "RETURN n.name AS name, n.age AS age", context); + + "RETURN n.name AS name, n.age AS age, " + + "n.address AS address, n.birth AS birth", context); assertEquals(Code.SUCCESS, result.code()); - final String tableResult = "name\tage\n\"name1\"\t1\n\"name2\"\t2\n\"name3\"\t3\n"; - assertEquals(tableResult, result.toString().replace("%table ", StringUtils.EMPTY)); + final String tableResult = "name\tage\taddress\tbirth\n" + + "\"name1\"\t1\tPoint{srid=4979, x=56.7, y=12.78, z=8.0}\t1984-04-04\n" + + "\"name2\"\t2\tPoint{srid=4979, x=56.7, y=12.78, z=8.0}\t1984-04-04\n" + + "\"name3\"\t3\tPoint{srid=4979, x=56.7, y=12.78, z=8.0}\t1984-04-04\n"; + assertEquals(tableResult, result.toString().replace(TABLE_RESULT_PREFIX, StringUtils.EMPTY)); } @Test @@ -136,7 +146,7 @@ public class Neo4jCypherInterpreterTest { final String objectListKey = "object.listKey"; InterpreterResult result = interpreter.interpret(jsonQuery, context); assertEquals(Code.SUCCESS, result.code()); - String[] rows = result.toString().replace("%table ", StringUtils.EMPTY) + String[] rows = result.toString().replace(TABLE_RESULT_PREFIX, StringUtils.EMPTY) .split(Neo4jCypherInterpreter.NEW_LINE); assertEquals(rows.length, 2); List<String> header = Arrays.asList(rows[0].split(Neo4jCypherInterpreter.TAB)); @@ -153,7 +163,7 @@ public class Neo4jCypherInterpreterTest { + "AS array UNWIND array AS object RETURN object"; result = interpreter.interpret(query, context); assertEquals(Code.SUCCESS, result.code()); - rows = result.toString().replace("%table ", StringUtils.EMPTY) + rows = result.toString().replace(TABLE_RESULT_PREFIX, StringUtils.EMPTY) .split(Neo4jCypherInterpreter.NEW_LINE); assertEquals(rows.length, 3); header = Arrays.asList(rows[0].split(Neo4jCypherInterpreter.TAB)); @@ -175,7 +185,7 @@ public class Neo4jCypherInterpreterTest { + "AS array UNWIND array AS object RETURN object"; result = interpreter.interpret(jsonListWithNullQuery, context); assertEquals(Code.SUCCESS, result.code()); - rows = result.toString().replace("%table ", StringUtils.EMPTY) + rows = result.toString().replace(TABLE_RESULT_PREFIX, StringUtils.EMPTY) .split(Neo4jCypherInterpreter.NEW_LINE); assertEquals(rows.length, 3); header = Arrays.asList(rows[0].split(Neo4jCypherInterpreter.TAB, -1)); @@ -191,13 +201,13 @@ public class Neo4jCypherInterpreterTest { assertEquals(row.get(header.indexOf(objectKey)), "value2"); assertEquals(row.get(header.indexOf(objectListKey)), "[{\"inner\":\"Map1\"},{\"inner\":\"Map2\"}]"); - + final String jsonListWithoutListKeyQuery = "WITH [{key: \"value\"}," + "{key: \"value2\", listKey: [{inner: \"Map1\"}, {inner: \"Map2\"}]}] " + "AS array UNWIND array AS object RETURN object"; result = interpreter.interpret(jsonListWithoutListKeyQuery, context); assertEquals(Code.SUCCESS, result.code()); - rows = result.toString().replace("%table ", StringUtils.EMPTY) + rows = result.toString().replace(TABLE_RESULT_PREFIX, StringUtils.EMPTY) .split(Neo4jCypherInterpreter.NEW_LINE); assertEquals(rows.length, 3); header = Arrays.asList(rows[0].split(Neo4jCypherInterpreter.TAB, -1)); @@ -219,7 +229,7 @@ public class Neo4jCypherInterpreterTest { interpreter.open(); InterpreterResult result = interpreter.interpret( "MATCH (n)-[r:KNOWS]-(m) RETURN n, r, m LIMIT 1", context); - GraphResult.Graph graph = gson.fromJson(result.toString().replace("%network ", + GraphResult.Graph graph = gson.fromJson(result.toString().replace(NETWORK_RESULT_PREFIX, StringUtils.EMPTY), GraphResult.Graph.class); assertEquals(2, graph.getNodes().size()); assertEquals(true, graph.getNodes().iterator().next().getLabel().equals(LABEL_PERSON)); @@ -248,4 +258,65 @@ public class Neo4jCypherInterpreterTest { context); assertEquals(Code.ERROR, result.code()); } + + @Test + public void testDates() { + InterpreterResult result = interpreter.interpret( + "RETURN datetime('2015-06-24T12:50:35.556+0100') AS theDateTime", context); + assertEquals(Code.SUCCESS, result.code()); + assertEquals("theDateTime\n2015-06-24T12:50:35.556+01:00\n", result.toString() + .replace(TABLE_RESULT_PREFIX, StringUtils.EMPTY)); + + result = interpreter.interpret("RETURN localdatetime('2015185T19:32:24') AS theLocalDateTime", + context); + assertEquals(Code.SUCCESS, result.code()); + assertEquals("theLocalDateTime\n2015-07-04T19:32:24\n", result.toString() + .replace(TABLE_RESULT_PREFIX, StringUtils.EMPTY)); + + result = interpreter.interpret("RETURN date('+2015-W13-4') AS theDate", context); + assertEquals(Code.SUCCESS, result.code()); + assertEquals("theDate\n2015-03-26\n", result.toString() + .replace(TABLE_RESULT_PREFIX, StringUtils.EMPTY)); + + result = interpreter.interpret("RETURN time('125035.556+0100') AS theTime", context); + assertEquals(Code.SUCCESS, result.code()); + assertEquals("theTime\n12:50:35.556+01:00\n", result.toString().replace(TABLE_RESULT_PREFIX, + StringUtils.EMPTY)); + + result = interpreter.interpret("RETURN localtime('12:50:35.556') AS theLocalTime", context); + assertEquals(Code.SUCCESS, result.code()); + assertEquals("theLocalTime\n12:50:35.556\n", result.toString() + .replace(TABLE_RESULT_PREFIX, StringUtils.EMPTY)); + } + + @Test + public void testDuration() { + InterpreterResult result = interpreter.interpret( + "RETURN duration('P14DT16H12M') AS theDuration", context); + assertEquals(Code.SUCCESS, result.code()); + assertEquals("theDuration\nP0M14DT58320S\n", result.toString() + .replace(TABLE_RESULT_PREFIX, StringUtils.EMPTY)); + } + + @Test + public void testPoint() { + InterpreterResult result = interpreter.interpret("RETURN point({ x:3, y:0 }) AS cartesian_2d," + + "point({ x:0, y:4, z:1 }) AS cartesian_3d," + + "point({ latitude: 12, longitude: 56 }) AS geo_2d," + + "point({ latitude: 12, longitude: 56, height: 1000 }) AS geo_3d", context); + assertEquals(Code.SUCCESS, result.code()); + assertEquals("cartesian_2d\tcartesian_3d\tgeo_2d\tgeo_3d\n" + + "Point{srid=7203, x=3.0, y=0.0}\tPoint{srid=9157, x=0.0, y=4.0, z=1.0}\t" + + "Point{srid=4326, x=56.0, y=12.0}\tPoint{srid=4979, x=56.0, y=12.0, z=1000.0}\n", + result.toString().replace(TABLE_RESULT_PREFIX, StringUtils.EMPTY)); + + result = interpreter.interpret( + "WITH point({ latitude: 12, longitude: 56, height: 1000 }) AS geo_3d " + + "RETURN geo_3d.latitude AS latitude, geo_3d.longitude AS longitude, " + + "geo_3d.height AS height", context); + assertEquals(Code.SUCCESS, result.code()); + assertEquals("latitude\tlongitude\theight\n" + + "12.0\t56.0\t1000.0\n", + result.toString().replace(TABLE_RESULT_PREFIX, StringUtils.EMPTY)); + } }