You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@zeppelin.apache.org by fe...@apache.org on 2017/10/11 06:44:10 UTC

zeppelin git commit: [ZEPPELIN-2761] - Neo4j Interpreter

Repository: zeppelin
Updated Branches:
  refs/heads/master 0c64d9ca6 -> e0fafac75


[ZEPPELIN-2761] - Neo4j Interpreter

### What is this PR for?
This contribution would to introduce Neo4j Cypher intepreter and at the same time provides base APIs that enable other graph databases (or graph framworks such as GraphX or Giraph).

### What type of PR is it?
[Feature]

### Todos
* [x] - Provide base APIs to manage graph results (under: org.apache.zeppelin.interpreter.graph.*)
* [x] - Create the Neo4j intepreter

### What is the Jira issue?
[[ZEPPELIN-2761]](https://issues.apache.org/jira/browse/ZEPPELIN-2761)

### How should this be tested?
Donwload and execute [Neo4j](https://neo4j.com/download/?ref=home) v3.x, you can also pull a [Docker image](https://neo4j.com/developer/docker/).

In order to execute test cases, if you are running Java 7, you need to also provide an environment variable telling the tests where to find Java 8, because Neo4j-the-database needs it to run.
```bash
export NEO4J_JAVA=<path/to/java/home>
```

Use this statement to create a dummy dataset
```bash
%neo4j
UNWIND range(1,100) as id
CREATE (p:Person {id:id, name: "Name " + id, age: id % 3}) WITH collect(p) as people
UNWIND people as p1
UNWIND range(1,10) as friend
WITH p1, people[(p1.id + friend) % size(people)] as p2
CREATE (p1)-[:KNOWS {years: abs(p2.id - p2.id)}]->(p2)
```

Then you can write some simple queries like:
```bash
%neo4j
MATCH (p:Person)-[r:KNOWS]-(p1:Person)
RETURN p, r, p1
LIMIT 10;
```

```bash
%neo4j
MATCH (p:Person)-[r:KNOWS]-(p1:Person)
RETURN p.id AS ID_A, p.name AS NAME_A, r.years AS YEARS, p1.id AS ID_B, p1.name AS NAME_B
LIMIT 20;
```

### Video
![zeppelin query examples](https://user-images.githubusercontent.com/1833335/28041473-daaa1f02-65c9-11e7-9445-c45478669d66.gif)

### Questions:
* Does the licenses files need update? **Yes**
* Is there breaking changes for older versions? **No**
* Does this needs documentation? **Yes**

Author: conker84 <sa...@gmail.com>

Closes #2478 from conker84/master and squashes the following commits:

5fb97ce [conker84] review by felixcheung 18/09
1e3d611 [conker84] Review by felixcheung (09/08)
aaf618e [conker84] Review by 1ambda 26/07
b5c2c66 [conker84] Added docs
9eb568d [conker84] Final behavior after discussion
2f88e98 [conker84] Moved business logic to the connection manager
8e4690e [conker84] Create connection manager class
35b4e29 [conker84] First commit


Project: http://git-wip-us.apache.org/repos/asf/zeppelin/repo
Commit: http://git-wip-us.apache.org/repos/asf/zeppelin/commit/e0fafac7
Tree: http://git-wip-us.apache.org/repos/asf/zeppelin/tree/e0fafac7
Diff: http://git-wip-us.apache.org/repos/asf/zeppelin/diff/e0fafac7

Branch: refs/heads/master
Commit: e0fafac75c3e64bf48960501b2776933e9119c65
Parents: 0c64d9c
Author: conker84 <sa...@gmail.com>
Authored: Tue Sep 26 19:54:10 2017 +0200
Committer: Felix Cheung <fe...@apache.org>
Committed: Tue Oct 10 23:44:02 2017 -0700

----------------------------------------------------------------------
 docs/_includes/themes/zeppelin/_navigation.html |   1 +
 .../zeppelin/img/docs-img/neo4j-config.png      | Bin 0 -> 21282 bytes
 .../img/docs-img/neo4j-dynamic-forms.png        | Bin 0 -> 26003 bytes
 .../zeppelin/img/docs-img/neo4j-graph.png       | Bin 0 -> 30119 bytes
 .../img/docs-img/neo4j-interpreter-video.gif    | Bin 0 -> 1577800 bytes
 docs/index.md                                   |   1 +
 docs/interpreter/neo4j.md                       | 117 ++++++++
 neo4j/pom.xml                                   | 144 ++++++++++
 .../graph/neo4j/Neo4jConnectionManager.java     | 151 ++++++++++
 .../graph/neo4j/Neo4jCypherInterpreter.java     | 274 +++++++++++++++++++
 .../graph/neo4j/utils/Neo4jConversionUtils.java |  66 +++++
 .../src/main/resources/interpreter-setting.json |  42 +++
 .../graph/neo4j/Neo4jCypherInterpreterTest.java | 249 +++++++++++++++++
 pom.xml                                         |   1 +
 zeppelin-distribution/src/bin_license/LICENSE   |   1 +
 .../zeppelin/interpreter/graph/GraphResult.java | 122 +++++++++
 .../apache/zeppelin/tabledata/GraphEntity.java  |  74 +++++
 .../org/apache/zeppelin/tabledata/Node.java     |  49 ++++
 .../apache/zeppelin/tabledata/Relationship.java |  63 +++++
 .../zeppelin/conf/ZeppelinConfiguration.java    |   5 +-
 20 files changed, 1358 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/zeppelin/blob/e0fafac7/docs/_includes/themes/zeppelin/_navigation.html
----------------------------------------------------------------------
diff --git a/docs/_includes/themes/zeppelin/_navigation.html b/docs/_includes/themes/zeppelin/_navigation.html
index ecdccbd..215c944 100644
--- a/docs/_includes/themes/zeppelin/_navigation.html
+++ b/docs/_includes/themes/zeppelin/_navigation.html
@@ -137,6 +137,7 @@
                 <li><a href="{{BASE_PATH}}/interpreter/lens.html">Lens</a></li>
                 <li><a href="{{BASE_PATH}}/interpreter/livy.html">Livy</a></li>
                 <li><a href="{{BASE_PATH}}/interpreter/markdown.html">Markdown</a></li>
+                <li><a href="{{BASE_PATH}}/interpreter/neo4j.html">Neo4j</a></li>
                 <li><a href="{{BASE_PATH}}/interpreter/pig.html">Pig</a></li>
                 <li><a href="{{BASE_PATH}}/interpreter/postgresql.html">Postgresql, HAWQ</a></li>
                 <li><a href="{{BASE_PATH}}/interpreter/r.html">R</a></li>

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/e0fafac7/docs/assets/themes/zeppelin/img/docs-img/neo4j-config.png
----------------------------------------------------------------------
diff --git a/docs/assets/themes/zeppelin/img/docs-img/neo4j-config.png b/docs/assets/themes/zeppelin/img/docs-img/neo4j-config.png
new file mode 100644
index 0000000..2de3699
Binary files /dev/null and b/docs/assets/themes/zeppelin/img/docs-img/neo4j-config.png differ

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/e0fafac7/docs/assets/themes/zeppelin/img/docs-img/neo4j-dynamic-forms.png
----------------------------------------------------------------------
diff --git a/docs/assets/themes/zeppelin/img/docs-img/neo4j-dynamic-forms.png b/docs/assets/themes/zeppelin/img/docs-img/neo4j-dynamic-forms.png
new file mode 100644
index 0000000..177e0a5
Binary files /dev/null and b/docs/assets/themes/zeppelin/img/docs-img/neo4j-dynamic-forms.png differ

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/e0fafac7/docs/assets/themes/zeppelin/img/docs-img/neo4j-graph.png
----------------------------------------------------------------------
diff --git a/docs/assets/themes/zeppelin/img/docs-img/neo4j-graph.png b/docs/assets/themes/zeppelin/img/docs-img/neo4j-graph.png
new file mode 100644
index 0000000..396b960
Binary files /dev/null and b/docs/assets/themes/zeppelin/img/docs-img/neo4j-graph.png differ

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/e0fafac7/docs/assets/themes/zeppelin/img/docs-img/neo4j-interpreter-video.gif
----------------------------------------------------------------------
diff --git a/docs/assets/themes/zeppelin/img/docs-img/neo4j-interpreter-video.gif b/docs/assets/themes/zeppelin/img/docs-img/neo4j-interpreter-video.gif
new file mode 100644
index 0000000..28c1915
Binary files /dev/null and b/docs/assets/themes/zeppelin/img/docs-img/neo4j-interpreter-video.gif differ

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/e0fafac7/docs/index.md
----------------------------------------------------------------------
diff --git a/docs/index.md b/docs/index.md
index 5e991f1..dbec040 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -142,6 +142,7 @@ limitations under the License.
   * [Lens](./interpreter/lens.html)
   * [Livy](./interpreter/livy.html)
   * [markdown](./interpreter/markdown.html)
+  * [Neo4j](./interpreter/neo4j.html)
   * [Pig](./interpreter/pig.html)
   * [Postgresql, HAWQ](./interpreter/postgresql.html)
   * [Python](./interpreter/python.html)

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/e0fafac7/docs/interpreter/neo4j.md
----------------------------------------------------------------------
diff --git a/docs/interpreter/neo4j.md b/docs/interpreter/neo4j.md
new file mode 100644
index 0000000..37f1f8c
--- /dev/null
+++ b/docs/interpreter/neo4j.md
@@ -0,0 +1,117 @@
+---
+layout: page
+title: "Neo4j Interpreter for Apache Zeppelin"
+description: "Neo4j is a native graph database, designed to store and process graphs from bottom to top."
+group: interpreter
+---
+<!--
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+{% include JB/setup %}
+
+# Neo4j Interpreter for Apache Zeppelin
+
+<div id="toc"></div>
+
+## Overview
+[Neo4j](https://neo4j.com/product/) is a native graph database, designed to store and process graphs from bottom to top.
+
+
+![Neo4j - Interpreter - Video]({{BASE_PATH}}/assets/themes/zeppelin/img/docs-img/neo4j-interpreter-video.gif)
+
+## Configuration
+<table class="table-configuration">
+  <tr>
+    <th>Property</th>
+    <th>Default</th>
+    <th>Description</th>
+  </tr>
+  <tr>
+    <td>neo4j.url</td>
+    <td>bolt://localhost:7687</td>
+    <td>The Neo4j's BOLT url.</td>
+  </tr>
+  <tr>
+    <td>neo4j.auth.type</td>
+    <td>BASIC</td>
+    <td>The Neo4j's authentication type (NONE, BASIC).</td>
+  </tr>
+  <tr>
+    <td>neo4j.auth.user</td>
+    <td>neo4j</td>
+    <td>The Neo4j user name.</td>
+  </tr>
+  <tr>
+    <td>neo4j.auth.password</td>
+    <td>neo4j</td>
+    <td>The Neo4j user password.</td>
+  </tr>
+  <tr>
+    <td>neo4j.max.concurrency</td>
+    <td>50</td>
+    <td>Max concurrency call from Zeppelin to Neo4j server.</td>
+  </tr>
+</table>
+
+<center>
+  ![Interpreter configuration]({{BASE_PATH}}/assets/themes/zeppelin/img/docs-img/neo4j-config.png)
+</center>
+
+
+## Enabling the Neo4j Interpreter
+In a notebook, to enable the **Neo4j** interpreter, click the **Gear** icon and select **Neo4j**.
+
+## Using the Neo4j Interpreter
+In a paragraph, use `%neo4j` to select the Neo4j interpreter and then input the Cypher commands.
+For list of Cypher commands please refer to the official [Cyper Refcard](http://neo4j.com/docs/cypher-refcard/current/)
+
+```bash
+%neo4j
+//Sample the TrumpWorld dataset
+WITH
+'https://docs.google.com/spreadsheets/u/1/d/1Z5Vo5pbvxKJ5XpfALZXvCzW26Cl4we3OaN73K9Ae5Ss/export?format=csv&gid=1996904412' AS url
+LOAD CSV WITH HEADERS FROM url AS row
+RETURN row.`Entity A`, row.`Entity A Type`, row.`Entity B`, row.`Entity B Type`, row.Connection, row.`Source(s)`
+LIMIT 10
+```
+
+The Neo4j interpreter leverages the [Network display system](../usage/display_system/basic.html#network) allowing to visualize the them directly from the paragraph.
+
+
+### Write your Cypher queries and navigate your graph
+
+This query:
+
+```bash
+%neo4j
+MATCH (vp:Person {name:"VLADIMIR PUTIN"}), (dt:Person {name:"DONALD J. TRUMP"})
+MATCH path = allShortestPaths( (vp)-[*]-(dt) )
+RETURN path
+```
+produces the following result_
+![Neo4j - Graph - Result]({{BASE_PATH}}/assets/themes/zeppelin/img/docs-img/neo4j-graph.png)
+
+### Apply Zeppelin Dynamic Forms
+You can leverage [Zeppelin Dynamic Form](../usage/dynamic_form/intro.html) inside your queries. This query:
+
+```bash
+%neo4j
+MATCH (o:Organization)-[r]-()
+RETURN o.name, count(*), collect(distinct type(r)) AS types
+ORDER BY count(*) DESC
+LIMIT ${Show top=10}
+```
+
+produces the following result:
+![Neo4j - Zeppelin - Dynamic Forms]({{BASE_PATH}}/assets/themes/zeppelin/img/docs-img/neo4j-dynamic-forms.png)
+

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/e0fafac7/neo4j/pom.xml
----------------------------------------------------------------------
diff --git a/neo4j/pom.xml b/neo4j/pom.xml
new file mode 100644
index 0000000..298726f
--- /dev/null
+++ b/neo4j/pom.xml
@@ -0,0 +1,144 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one or more
+  ~ contributor license agreements.  See the NOTICE file distributed with
+  ~ this work for additional information regarding copyright ownership.
+  ~ The ASF licenses this file to You under the Apache License, Version 2.0
+  ~ (the "License"); you may not use this file except in compliance with
+  ~ the License.  You may obtain a copy of the License at
+  ~
+  ~    http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+  
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <artifactId>zeppelin</artifactId>
+    <groupId>org.apache.zeppelin</groupId>
+    <version>0.8.0-SNAPSHOT</version>
+    <relativePath>..</relativePath>
+  </parent>
+
+  <groupId>org.apache.zeppelin</groupId>
+  <artifactId>zeppelin-neo4j</artifactId>
+  <packaging>jar</packaging>
+  <version>0.8.0-SNAPSHOT</version>
+  <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>
+  	<jackson.version>2.8.9</jackson.version>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>${project.groupId}</groupId>
+      <artifactId>zeppelin-interpreter</artifactId>
+      <version>${project.version}</version>
+      <scope>provided</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>com.fasterxml.jackson.core</groupId>
+      <artifactId>jackson-databind</artifactId>
+      <version>${jackson.version}</version>
+    </dependency>
+
+    <dependency>
+      <groupId>org.neo4j.driver</groupId>
+      <artifactId>neo4j-java-driver</artifactId>
+      <version>${neo4j.driver.version}</version>
+    </dependency>
+
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-api</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-log4j12</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <scope>test</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>org.neo4j.test</groupId>
+      <artifactId>neo4j-harness</artifactId>
+      <version>${neo4j.version}</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <artifactId>maven-enforcer-plugin</artifactId>
+        <version>1.3.1</version>            
+        <executions> 
+          <execution> 
+            <id>enforce</id> 
+            <phase>none</phase> 
+          </execution>
+        </executions>
+      </plugin>
+
+      <plugin>
+        <artifactId>maven-dependency-plugin</artifactId>
+        <version>2.8</version>
+        <executions>
+          <execution>
+            <id>copy-dependencies</id>
+            <phase>package</phase>
+            <goals>
+              <goal>copy-dependencies</goal>
+            </goals>
+            <configuration>
+              <outputDirectory>${project.build.directory}/../../interpreter/neo4j</outputDirectory>
+              <overWriteReleases>false</overWriteReleases>
+              <overWriteSnapshots>false</overWriteSnapshots>
+              <overWriteIfNewer>true</overWriteIfNewer>
+              <includeScope>runtime</includeScope>
+            </configuration>
+          </execution>
+          <execution>
+            <id>copy-artifact</id>
+            <phase>package</phase>
+            <goals>
+              <goal>copy</goal>
+            </goals>
+            <configuration>
+              <outputDirectory>${project.build.directory}/../../interpreter/neo4j</outputDirectory>
+              <overWriteReleases>false</overWriteReleases>
+              <overWriteSnapshots>false</overWriteSnapshots>
+              <overWriteIfNewer>true</overWriteIfNewer>
+              <includeScope>runtime</includeScope>
+              <artifactItems>
+                <artifactItem>
+                  <groupId>${project.groupId}</groupId>
+                  <artifactId>${project.artifactId}</artifactId>
+                  <version>${project.version}</version>
+                  <type>${project.packaging}</type>
+                </artifactItem>
+              </artifactItems>              
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+
+</project>

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/e0fafac7/neo4j/src/main/java/org/apache/zeppelin/graph/neo4j/Neo4jConnectionManager.java
----------------------------------------------------------------------
diff --git a/neo4j/src/main/java/org/apache/zeppelin/graph/neo4j/Neo4jConnectionManager.java b/neo4j/src/main/java/org/apache/zeppelin/graph/neo4j/Neo4jConnectionManager.java
new file mode 100644
index 0000000..7cd504e
--- /dev/null
+++ b/neo4j/src/main/java/org/apache/zeppelin/graph/neo4j/Neo4jConnectionManager.java
@@ -0,0 +1,151 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.zeppelin.graph.neo4j;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.zeppelin.interpreter.InterpreterContext;
+import org.apache.zeppelin.resource.Resource;
+import org.apache.zeppelin.resource.ResourcePool;
+import org.neo4j.driver.v1.AuthToken;
+import org.neo4j.driver.v1.AuthTokens;
+import org.neo4j.driver.v1.Config;
+import org.neo4j.driver.v1.Driver;
+import org.neo4j.driver.v1.GraphDatabase;
+import org.neo4j.driver.v1.Session;
+import org.neo4j.driver.v1.StatementResult;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Neo4j connection manager for Zeppelin.
+ */
+public class Neo4jConnectionManager {
+  static final Logger LOGGER = LoggerFactory.getLogger(Neo4jConnectionManager.class);
+  
+  public static final String NEO4J_SERVER_URL = "neo4j.url";
+  public static final String NEO4J_AUTH_TYPE = "neo4j.auth.type";
+  public static final String NEO4J_AUTH_USER = "neo4j.auth.user";
+  public static final String NEO4J_AUTH_PASSWORD = "neo4j.auth.password";
+  public static final String NEO4J_MAX_CONCURRENCY = "neo4j.max.concurrency";
+
+  private static final Pattern PROPERTY_PATTERN = Pattern.compile("\\{\\w+\\}");
+  private static final String REPLACE_CURLY_BRACKETS = "\\{|\\}";
+
+  private static final Pattern $_PATTERN = Pattern.compile("\\$\\w+\\}");
+  private static final String REPLACE_$ = "\\$";
+
+  private Driver driver = null;
+
+  private final String neo4jUrl;
+
+  private final Config config;
+
+  private final AuthToken authToken;
+
+  /**
+   * 
+   * Enum type for the AuthToken 
+   *
+   */
+  public enum Neo4jAuthType {NONE, BASIC}
+
+  public Neo4jConnectionManager(Properties properties) {
+    this.neo4jUrl = properties.getProperty(NEO4J_SERVER_URL);
+    this.config = Config.build()
+          .withMaxIdleSessions(Integer.parseInt(properties.getProperty(NEO4J_MAX_CONCURRENCY)))
+          .toConfig();
+    String authType = properties.getProperty(NEO4J_AUTH_TYPE);
+    switch (Neo4jAuthType.valueOf(authType.toUpperCase())) {
+      case BASIC:
+        String username = properties.getProperty(NEO4J_AUTH_USER);
+        String password = properties.getProperty(NEO4J_AUTH_PASSWORD);
+        this.authToken = AuthTokens.basic(username, password);
+        break;
+      case NONE:
+        LOGGER.debug("Creating NONE authentication");
+        this.authToken = AuthTokens.none();
+        break;
+      default:
+        throw new RuntimeException("Neo4j authentication type not supported");
+    }
+  }
+
+  private Driver getDriver() {
+    if (driver == null) {
+      driver = GraphDatabase.driver(this.neo4jUrl, this.authToken, this.config);
+    }
+    return driver;
+  }
+
+  public void open() {
+    getDriver();
+  }
+
+  public void close() {
+    getDriver().close();
+  }
+
+  private Session getSession() {
+    return getDriver().session();
+  }
+
+  public StatementResult execute(String cypherQuery,
+      InterpreterContext interpreterContext) {
+    Map<String, Object> params = new HashMap<>();
+    if (interpreterContext != null) {
+      ResourcePool resourcePool = interpreterContext.getResourcePool();
+      Set<String> keys = extractParams(cypherQuery, PROPERTY_PATTERN, REPLACE_CURLY_BRACKETS);
+      keys.addAll(extractParams(cypherQuery, $_PATTERN, REPLACE_$));
+      for (String key : keys) {
+        Resource resource = resourcePool.get(key);
+        if (resource != null) {
+          params.put(key, resource.get());
+        }
+      }
+    }
+    LOGGER.debug("Executing cypher query {} with params {}", cypherQuery, params);
+    StatementResult result;
+    try (Session session = getSession()) {
+      result = params.isEmpty()
+            ? getSession().run(cypherQuery) : getSession().run(cypherQuery, params);
+    }
+    return result;
+  }
+
+  public StatementResult execute(String cypherQuery) {
+    return execute(cypherQuery, null);
+  }
+
+  private Set<String> extractParams(String cypherQuery, Pattern pattern, String replaceChar) {
+    Matcher matcher = pattern.matcher(cypherQuery);
+    Set<String> keys = new HashSet<>();
+    while (matcher.find()) {
+      keys.add(matcher.group().replaceAll(replaceChar, StringUtils.EMPTY));
+    }
+    return keys;
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/e0fafac7/neo4j/src/main/java/org/apache/zeppelin/graph/neo4j/Neo4jCypherInterpreter.java
----------------------------------------------------------------------
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
new file mode 100644
index 0000000..a625522
--- /dev/null
+++ b/neo4j/src/main/java/org/apache/zeppelin/graph/neo4j/Neo4jCypherInterpreter.java
@@ -0,0 +1,274 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.zeppelin.graph.neo4j;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Properties;
+import java.util.Set;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.zeppelin.graph.neo4j.utils.Neo4jConversionUtils;
+import org.apache.zeppelin.interpreter.Interpreter;
+import org.apache.zeppelin.interpreter.InterpreterContext;
+import org.apache.zeppelin.interpreter.InterpreterResult;
+import org.apache.zeppelin.interpreter.InterpreterResult.Code;
+import org.apache.zeppelin.interpreter.graph.GraphResult;
+import org.apache.zeppelin.scheduler.Scheduler;
+import org.apache.zeppelin.scheduler.SchedulerFactory;
+import org.neo4j.driver.internal.types.InternalTypeSystem;
+import org.neo4j.driver.internal.util.Iterables;
+import org.neo4j.driver.v1.Record;
+import org.neo4j.driver.v1.StatementResult;
+import org.neo4j.driver.v1.Value;
+import org.neo4j.driver.v1.types.Node;
+import org.neo4j.driver.v1.types.Relationship;
+import org.neo4j.driver.v1.types.TypeSystem;
+import org.neo4j.driver.v1.util.Pair;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/**
+ * Neo4j interpreter for Zeppelin.
+ */
+public class Neo4jCypherInterpreter extends Interpreter {
+  private static final String TABLE = "%table";
+  public static final String NEW_LINE = "\n";
+  public static final String TAB = "\t";
+
+  private static final String MAP_KEY_TEMPLATE = "%s.%s";
+
+  private Map<String, String> labels;
+
+  private Set<String> types;
+  
+  private final Neo4jConnectionManager neo4jConnectionManager;
+  
+  private final ObjectMapper jsonMapper = new ObjectMapper();
+
+  public Neo4jCypherInterpreter(Properties properties) {
+    super(properties);
+    this.neo4jConnectionManager = new Neo4jConnectionManager(properties);
+  }
+
+  @Override
+  public void open() {
+    this.neo4jConnectionManager.open();
+  }
+
+  @Override
+  public void close() {
+    this.neo4jConnectionManager.close();
+  }
+
+  public Map<String, String> getLabels(boolean refresh) {
+    if (labels == null || refresh) {
+      Map<String, String> old = labels == null ?
+          new LinkedHashMap<String, String>() : new LinkedHashMap<>(labels);
+      labels = new LinkedHashMap<>();
+      StatementResult result = this.neo4jConnectionManager.execute("CALL db.labels()");
+      Set<String> colors = new HashSet<>();
+      while (result.hasNext()) {
+        Record record = result.next();
+        String label = record.get("label").asString();
+        String color = old.get(label);
+        while (color == null || colors.contains(color)) {
+          color = Neo4jConversionUtils.getRandomLabelColor();
+        }
+        colors.add(color);
+        labels.put(label, color);
+      }
+    }
+    return labels;
+  }
+
+  private Set<String> getTypes(boolean refresh) {
+    if (types == null || refresh) {
+      types = new HashSet<>();
+      StatementResult result = this.neo4jConnectionManager.execute("CALL db.relationshipTypes()");
+      while (result.hasNext()) {
+        Record record = result.next();
+        types.add(record.get("relationshipType").asString());
+      }
+    }
+    return types;
+  }
+
+  @Override
+  public InterpreterResult interpret(String cypherQuery, InterpreterContext interpreterContext) {
+    logger.info("Opening session");
+    if (StringUtils.isBlank(cypherQuery)) {
+      return new InterpreterResult(Code.SUCCESS);
+    }
+    try {
+      StatementResult result = this.neo4jConnectionManager.execute(cypherQuery,
+              interpreterContext);
+      Set<Node> nodes = new HashSet<>();
+      Set<Relationship> relationships = new HashSet<>();
+      List<String> columns = new ArrayList<>();
+      List<List<String>> lines = new ArrayList<List<String>>();
+      while (result.hasNext()) {
+        Record record = result.next();
+        List<Pair<String, Value>> fields = record.fields();
+        List<String> line = new ArrayList<>();
+        for (Pair<String, Value> field : fields) {
+          if (field.value().hasType(InternalTypeSystem.TYPE_SYSTEM.NODE())) {
+            nodes.add(field.value().asNode());
+          } else if (field.value().hasType(InternalTypeSystem.TYPE_SYSTEM.RELATIONSHIP())) {
+            relationships.add(field.value().asRelationship());
+          } else if (field.value().hasType(InternalTypeSystem.TYPE_SYSTEM.PATH())) {
+            nodes.addAll(Iterables.asList(field.value().asPath().nodes()));
+            relationships.addAll(Iterables.asList(field.value().asPath().relationships()));
+          } else {
+            setTabularResult(field.key(), field.value(), columns, line,
+                    InternalTypeSystem.TYPE_SYSTEM);
+          }
+        }
+        if (!line.isEmpty()) {
+          lines.add(line);
+        }
+      }
+      if (!nodes.isEmpty()) {
+        return renderGraph(nodes, relationships);
+      } else {
+        return renderTable(columns, lines);
+      }
+    } catch (Exception e) {
+      logger.error("Exception while interpreting cypher query", e);
+      return new InterpreterResult(Code.ERROR, e.getMessage());
+    }
+  }
+
+  private void setTabularResult(String key, Object obj, List<String> columns, List<String> line,
+      TypeSystem typeSystem) {
+    if (obj instanceof Value) {
+      Value value = (Value) obj;
+      if (value.hasType(typeSystem.MAP())) {
+        Map<String, Object> map = value.asMap();
+        for (Entry<String, Object> entry : map.entrySet()) {
+          setTabularResult(String.format(MAP_KEY_TEMPLATE, key, entry.getKey()), entry.getValue(),
+                columns, line, typeSystem);
+        }
+      } else {
+        addValueToLine(key, columns, line, value);
+      }
+    } else if (obj instanceof Map) {
+      Map<String, Object> map = (Map<String, Object>) obj;
+      for (Entry<String, Object> entry : map.entrySet()) {
+        setTabularResult(String.format(MAP_KEY_TEMPLATE, key, entry.getKey()), entry.getValue(),
+                columns, line, typeSystem);
+      }
+    } else {
+      addValueToLine(key, columns, line, obj);
+    }
+  }
+
+  private void addValueToLine(String key, List<String> columns, List<String> line, Object value) {
+    if (!columns.contains(key)) {
+      columns.add(key);
+    }
+    int position = columns.indexOf(key);
+    if (line.size() < columns.size()) {
+      for (int i = line.size(); i < columns.size(); i++) {
+        line.add(null);
+      }
+    }
+    if (value != null) {
+      if (value instanceof Value) {
+        Value val = (Value) value;
+        if (val.hasType(InternalTypeSystem.TYPE_SYSTEM.LIST())) {
+          value = val.asList();
+        } else if (val.hasType(InternalTypeSystem.TYPE_SYSTEM.MAP())) {
+          value = val.asMap();
+        }
+      }
+      if (value instanceof Collection) {
+        try {
+          value = jsonMapper.writer().writeValueAsString(value);
+        } catch (Exception ignored) {}
+      }
+    }
+    line.set(position, value == null ? null : value.toString());
+  }
+
+  private InterpreterResult renderTable(List<String> cols, List<List<String>> lines) {
+    logger.info("Executing renderTable method");
+    StringBuilder msg = null;
+    if (cols.isEmpty()) {
+      msg = new StringBuilder();
+    } else {
+      msg = new StringBuilder(TABLE);
+      msg.append(NEW_LINE);
+      msg.append(StringUtils.join(cols, TAB));
+      msg.append(NEW_LINE);
+      for (List<String> line : lines) {
+        if (line.size() < cols.size()) {
+          for (int i = line.size(); i < cols.size(); i++) {
+            line.add(null);
+          }
+        }
+        msg.append(StringUtils.join(line, TAB));
+        msg.append(NEW_LINE);
+      }
+    }
+    return new InterpreterResult(Code.SUCCESS, msg.toString());
+  }
+
+  private InterpreterResult renderGraph(Set<Node> nodes,
+      Set<Relationship> relationships) {
+    logger.info("Executing renderGraph method");
+    List<org.apache.zeppelin.tabledata.Node> nodesList = new ArrayList<>();
+    List<org.apache.zeppelin.tabledata.Relationship> relsList = new ArrayList<>();
+    for (Relationship rel : relationships) {
+      relsList.add(Neo4jConversionUtils.toZeppelinRelationship(rel));
+    }
+    Map<String, String> labels = getLabels(true);
+    for (Node node : nodes) {
+      nodesList.add(Neo4jConversionUtils.toZeppelinNode(node, labels));
+    }
+    return new GraphResult(Code.SUCCESS,
+        new GraphResult.Graph(nodesList, relsList, labels, getTypes(true), true));
+  }
+
+  @Override
+  public Scheduler getScheduler() {
+    return SchedulerFactory.singleton()
+        .createOrGetParallelScheduler(Neo4jCypherInterpreter.class.getName() + this.hashCode(),
+            Integer.parseInt(getProperty(Neo4jConnectionManager.NEO4J_MAX_CONCURRENCY)));
+  }
+
+  @Override
+  public int getProgress(InterpreterContext context) {
+    return 0;
+  }
+
+  @Override
+  public FormType getFormType() {
+    return FormType.SIMPLE;
+  }
+
+  @Override
+  public void cancel(InterpreterContext context) {
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/e0fafac7/neo4j/src/main/java/org/apache/zeppelin/graph/neo4j/utils/Neo4jConversionUtils.java
----------------------------------------------------------------------
diff --git a/neo4j/src/main/java/org/apache/zeppelin/graph/neo4j/utils/Neo4jConversionUtils.java b/neo4j/src/main/java/org/apache/zeppelin/graph/neo4j/utils/Neo4jConversionUtils.java
new file mode 100644
index 0000000..4849401
--- /dev/null
+++ b/neo4j/src/main/java/org/apache/zeppelin/graph/neo4j/utils/Neo4jConversionUtils.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.zeppelin.graph.neo4j.utils;
+
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.neo4j.driver.v1.types.Node;
+import org.neo4j.driver.v1.types.Relationship;
+
+/**
+ * Neo4jConversionUtils
+ */
+public class Neo4jConversionUtils {
+  private Neo4jConversionUtils() {}
+  
+  private static final String[] LETTERS = "0123456789ABCDEF".split("");
+
+  public static final String COLOR_GREY = "#D3D3D3";
+  
+  public static org.apache.zeppelin.tabledata.Node toZeppelinNode(Node n,
+      Map<String, String> graphLabels) {
+    Set<String> labels = new LinkedHashSet<>();
+    String firstLabel = null;
+    for (String label : n.labels()) {
+      if (firstLabel == null) {
+        firstLabel = label;
+      }
+      labels.add(label);
+    }
+    return new org.apache.zeppelin.tabledata.Node(n.id(), n.asMap(),
+        labels);
+  }
+  
+  public static org.apache.zeppelin.tabledata.Relationship
+  toZeppelinRelationship(Relationship r) {
+    return new org.apache.zeppelin.tabledata.Relationship(r.id(), r.asMap(),
+        r.startNodeId(), r.endNodeId(), r.type());
+  }
+
+  public static String getRandomLabelColor() {
+    char[] color = new char[7];
+    color[0] = '#';
+    for (int i = 1; i < color.length; i++) {
+      color[i] = LETTERS[(int) Math.floor(Math.random() * 16)].charAt(0);
+    }
+    return new String(color);
+  }
+  
+}

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/e0fafac7/neo4j/src/main/resources/interpreter-setting.json
----------------------------------------------------------------------
diff --git a/neo4j/src/main/resources/interpreter-setting.json b/neo4j/src/main/resources/interpreter-setting.json
new file mode 100644
index 0000000..8db4367
--- /dev/null
+++ b/neo4j/src/main/resources/interpreter-setting.json
@@ -0,0 +1,42 @@
+[
+  {
+    "group": "neo4j",
+    "name": "neo4j",
+    "className": "org.apache.zeppelin.graph.neo4j.Neo4jCypherInterpreter",
+    "properties": {
+      "neo4j.url": {
+        "envName": null,
+        "propertyName": "neo4j.url",
+        "defaultValue": "bolt://localhost:7687",
+        "description": "The Neo4j's BOLT url."
+      },
+      "neo4j.auth.type": {
+        "envName": null,
+        "propertyName": "neo4j.auth.type",
+        "defaultValue": "BASIC",
+        "description": "The Neo4j's authentication type (NONE, BASIC)."
+      },
+      "neo4j.auth.user": {
+        "envName": null,
+        "propertyName": "neo4j.auth.user",
+        "defaultValue": "",
+        "description": "The Neo4j user name."
+      },
+      "neo4j.auth.password": {
+        "envName": null,
+        "propertyName": "neo4j.auth.password",
+        "defaultValue": "",
+        "description": "The Neo4j user password."
+      },
+      "neo4j.max.concurrency": {
+        "envName": null,
+        "propertyName": "neo4j.max.concurrency",
+        "defaultValue": "50",
+        "description": "Max concurrency call from Zeppelin to Neo4j server."
+      }
+    },
+    "editor": {
+      "editOnDblClick": false
+    }
+  }
+]

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/e0fafac7/neo4j/src/test/java/org/apache/zeppelin/graph/neo4j/Neo4jCypherInterpreterTest.java
----------------------------------------------------------------------
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
new file mode 100644
index 0000000..1bb14b7
--- /dev/null
+++ b/neo4j/src/test/java/org/apache/zeppelin/graph/neo4j/Neo4jCypherInterpreterTest.java
@@ -0,0 +1,249 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.zeppelin.graph.neo4j;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Properties;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.zeppelin.display.AngularObjectRegistry;
+import org.apache.zeppelin.display.GUI;
+import org.apache.zeppelin.graph.neo4j.Neo4jConnectionManager.Neo4jAuthType;
+import org.apache.zeppelin.interpreter.InterpreterContext;
+import org.apache.zeppelin.interpreter.InterpreterContextRunner;
+import org.apache.zeppelin.interpreter.InterpreterGroup;
+import org.apache.zeppelin.interpreter.InterpreterOutput;
+import org.apache.zeppelin.interpreter.InterpreterResult;
+import org.apache.zeppelin.interpreter.InterpreterResult.Code;
+import org.apache.zeppelin.interpreter.graph.GraphResult;
+import org.apache.zeppelin.resource.LocalResourcePool;
+import org.apache.zeppelin.user.AuthenticationInfo;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+import org.neo4j.harness.ServerControls;
+import org.neo4j.harness.TestServerBuilders;
+
+import com.google.gson.Gson;
+
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class Neo4jCypherInterpreterTest {
+
+  private Neo4jCypherInterpreter interpreter;
+
+  private InterpreterContext context;
+
+  private static ServerControls server;
+
+  private static final Gson gson = new Gson();
+
+  private static final String LABEL_PERSON = "Person";
+  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) "
+        + "CREATE (n)-[:%s]->(m)";
+
+  @BeforeClass
+  public static void setUpNeo4jServer() throws Exception {
+    server = TestServerBuilders.newInProcessBuilder()
+                .withConfig("dbms.security.auth_enabled","false")
+                .withFixture(String.format(CYPHER_FOREACH, LABEL_PERSON, "x % 10"))
+                .withFixture(String.format(CHPHER_UNWIND, REL_KNOWS))
+                .newServer();
+  }
+
+  @AfterClass
+  public static void tearDownNeo4jServer() throws Exception {
+    server.close();
+  }
+  
+  @Before
+  public void setUpZeppelin() {
+    Properties p = new Properties();
+    p.setProperty(Neo4jConnectionManager.NEO4J_SERVER_URL, server.boltURI().toString());
+    p.setProperty(Neo4jConnectionManager.NEO4J_AUTH_TYPE, Neo4jAuthType.NONE.toString());
+    p.setProperty(Neo4jConnectionManager.NEO4J_MAX_CONCURRENCY, "50");
+    interpreter = new Neo4jCypherInterpreter(p);
+    context = new InterpreterContext("note", "id", null, "title", "text",
+            new AuthenticationInfo(),
+            new HashMap<String, Object>(),
+            new GUI(),
+            new AngularObjectRegistry(new InterpreterGroup().getId(), null),
+            new LocalResourcePool("id"),
+            new LinkedList<InterpreterContextRunner>(),
+            new InterpreterOutput(null));
+  }
+
+  @After
+  public void tearDownZeppelin() throws Exception {
+    interpreter.close();
+  }
+
+  @Test
+  public void testTableWithArray() {
+    interpreter.open();
+    InterpreterResult result = interpreter.interpret("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));
+    
+    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));
+  }
+
+  @Test
+  public void testCreateIndex() {
+    interpreter.open();
+    InterpreterResult result = interpreter.interpret("CREATE INDEX ON :Person(name)", context);
+    assertEquals(Code.SUCCESS, result.code());
+    assertEquals(StringUtils.EMPTY, result.toString());
+  }
+
+  @Test
+  public void testRenderTable() {
+    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);
+    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));
+  }
+
+  @Test
+  public void testRenderMap() {
+    interpreter.open();
+    final String jsonQuery = "RETURN {key: \"value\", listKey: [{inner: \"Map1\"}, {inner: \"Map2\"}]} as object";
+    final String objectKey = "object.key";
+    final String objectListKey = "object.listKey";
+    InterpreterResult result = interpreter.interpret(jsonQuery, context);
+    assertEquals(Code.SUCCESS, result.code());
+    String[] rows = result.toString().replace("%table ", StringUtils.EMPTY).split(Neo4jCypherInterpreter.NEW_LINE);
+    assertEquals(rows.length, 2);
+    List<String> header = Arrays.asList(rows[0].split(Neo4jCypherInterpreter.TAB));
+    assertEquals(header.contains(objectKey), true);
+    assertEquals(header.contains(objectListKey), true);
+    List<String> row = Arrays.asList(rows[1].split(Neo4jCypherInterpreter.TAB));
+    assertEquals(row.size(), header.size());
+    assertEquals(row.get(header.indexOf(objectKey)), "value");
+    assertEquals(row.get(header.indexOf(objectListKey)), "[{\"inner\":\"Map1\"},{\"inner\":\"Map2\"}]");
+
+    final String query = "WITH [{key: \"value\", listKey: [{inner: \"Map1\"}, {inner: \"Map2\"}]},"
+    		+ "{key: \"value2\", listKey: [{inner: \"Map12\"}, {inner: \"Map22\"}]}] "
+    		+ "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).split(Neo4jCypherInterpreter.NEW_LINE);
+    assertEquals(rows.length, 3);
+    header = Arrays.asList(rows[0].split(Neo4jCypherInterpreter.TAB));
+    assertEquals(header.contains(objectKey), true);
+    assertEquals(header.contains(objectListKey), true);
+    row = Arrays.asList(rows[1].split(Neo4jCypherInterpreter.TAB));
+    assertEquals(row.size(), header.size());
+    assertEquals(row.get(header.indexOf(objectKey)), "value");
+    assertEquals(row.get(header.indexOf(objectListKey)), "[{\"inner\":\"Map1\"},{\"inner\":\"Map2\"}]");
+    row = Arrays.asList(rows[2].split(Neo4jCypherInterpreter.TAB));
+    assertEquals(row.size(), header.size());
+    assertEquals(row.get(header.indexOf(objectKey)), "value2");
+    assertEquals(row.get(header.indexOf(objectListKey)), "[{\"inner\":\"Map12\"},{\"inner\":\"Map22\"}]");
+
+    final String jsonListWithNullQuery = "WITH [{key: \"value\", listKey: null},"
+    		+ "{key: \"value2\", listKey: [{inner: \"Map1\"}, {inner: \"Map2\"}]}] "
+    		+ "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).split(Neo4jCypherInterpreter.NEW_LINE);
+    assertEquals(rows.length, 3);
+    header = Arrays.asList(rows[0].split(Neo4jCypherInterpreter.TAB, -1));
+    assertEquals(header.contains(objectKey), true);
+    assertEquals(header.contains(objectListKey), true);
+    row = Arrays.asList(rows[1].split(Neo4jCypherInterpreter.TAB, -1));
+    assertEquals(row.size(), header.size());
+    assertEquals(row.get(header.indexOf(objectKey)), "value");
+    assertEquals(row.get(header.indexOf(objectListKey)), StringUtils.EMPTY);
+    assertEquals(row.get(header.indexOf(objectListKey)), "");
+    row = Arrays.asList(rows[2].split(Neo4jCypherInterpreter.TAB, -1));
+    assertEquals(row.size(), header.size());
+    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).split(Neo4jCypherInterpreter.NEW_LINE);
+    assertEquals(rows.length, 3);
+    header = Arrays.asList(rows[0].split(Neo4jCypherInterpreter.TAB, -1));
+    assertEquals(header.contains(objectKey), true);
+    assertEquals(header.contains(objectListKey), true);
+    row = Arrays.asList(rows[1].split(Neo4jCypherInterpreter.TAB, -1));
+    assertEquals(row.size(), header.size());
+    assertEquals(row.get(header.indexOf(objectKey)), "value");
+    assertEquals(row.get(header.indexOf(objectListKey)), StringUtils.EMPTY);
+    row = Arrays.asList(rows[2].split(Neo4jCypherInterpreter.TAB, -1));
+    assertEquals(row.size(), header.size());
+    assertEquals(row.get(header.indexOf(objectKey)), "value2");
+    assertEquals(row.get(header.indexOf(objectListKey)), "[{\"inner\":\"Map1\"},{\"inner\":\"Map2\"}]");
+  }
+
+  @Test
+  public void testRenderNetwork() {
+    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 ", StringUtils.EMPTY), GraphResult.Graph.class);
+    assertEquals(2, graph.getNodes().size());
+    assertEquals(true, graph.getNodes().iterator().next().getLabel().equals(LABEL_PERSON));
+    assertEquals(1, graph.getEdges().size());
+    assertEquals(true, graph.getEdges().iterator().next().getLabel().equals(REL_KNOWS));
+    assertEquals(1, graph.getLabels().size());
+    assertEquals(1, graph.getTypes().size());
+    assertEquals(true, graph.getLabels().containsKey(LABEL_PERSON));
+    assertEquals(REL_KNOWS, graph.getTypes().iterator().next());
+    assertEquals(Code.SUCCESS, result.code());
+  }
+
+  @Test
+  public void testFallingQuery() {
+    interpreter.open();
+    final String ERROR_MSG_EMPTY = "";
+    InterpreterResult result = interpreter.interpret(StringUtils.EMPTY, context);
+    assertEquals(Code.SUCCESS, result.code());
+    assertEquals(ERROR_MSG_EMPTY, result.toString());
+
+    result = interpreter.interpret(null, context);
+    assertEquals(Code.SUCCESS, result.code());
+    assertEquals(ERROR_MSG_EMPTY, result.toString());
+
+    result = interpreter.interpret("MATCH (n:Person{name: }) RETURN n.name AS name, n.age AS age", context);
+    assertEquals(Code.ERROR, result.code());
+  }
+  
+}

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/e0fafac7/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index acfcd05..7ff9acd 100644
--- a/pom.xml
+++ b/pom.xml
@@ -76,6 +76,7 @@
     <module>bigquery</module>
     <module>alluxio</module>
     <module>scio</module>
+    <module>neo4j</module>
     <module>zeppelin-web</module>
     <module>zeppelin-server</module>
     <module>zeppelin-jupyter</module>

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/e0fafac7/zeppelin-distribution/src/bin_license/LICENSE
----------------------------------------------------------------------
diff --git a/zeppelin-distribution/src/bin_license/LICENSE b/zeppelin-distribution/src/bin_license/LICENSE
index ec0da9e..ab120f2 100644
--- a/zeppelin-distribution/src/bin_license/LICENSE
+++ b/zeppelin-distribution/src/bin_license/LICENSE
@@ -217,6 +217,7 @@ The following components are provided under Apache License.
     (Apache 2.0) frontend-maven-plugin 1.3 (com.github.eirslett:frontend-maven-plugin:1.3 - https://github.com/eirslett/frontend-maven-plugin/blob/frontend-plugins-1.3/LICENSE
     (Apache 2.0) frontend-plugin-core 1.3 (com.github.eirslett:frontend-plugin-core) - https://github.com/eirslett/frontend-maven-plugin/blob/frontend-plugins-1.3/LICENSE
     (Apache 2.0) mongo-java-driver 3.4.1 (org.mongodb:mongo-java-driver:3.4.1) - https://github.com/mongodb/mongo-java-driver/blob/master/LICENSE.txt
+    (Apache 2.0) Neo4j Java Driver (https://github.com/neo4j/neo4j-java-driver) - https://github.com/neo4j/neo4j-java-driver/blob/1.4.3/LICENSE.txt
 
 ========================================================================
 MIT licenses

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/e0fafac7/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/graph/GraphResult.java
----------------------------------------------------------------------
diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/graph/GraphResult.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/graph/GraphResult.java
new file mode 100644
index 0000000..df1b9a3
--- /dev/null
+++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/graph/GraphResult.java
@@ -0,0 +1,122 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.zeppelin.interpreter.graph;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.zeppelin.interpreter.InterpreterResult;
+import org.apache.zeppelin.tabledata.Node;
+import org.apache.zeppelin.tabledata.Relationship;
+
+import com.google.gson.Gson;
+
+/**
+ * The intepreter result template for Networks
+ *
+ */
+public class GraphResult extends InterpreterResult {
+
+  /**
+   * The Graph structure parsed from the front-end
+   *
+   */
+  public static class Graph {
+    private Collection<Node> nodes;
+    
+    private Collection<Relationship> edges;
+    
+    /**
+     * The node types in the whole graph, and the related colors
+     * 
+     */
+    private Map<String, String> labels;
+    
+    /**
+     * The relationship types in the whole graph
+     * 
+     */
+    private Set<String> types;
+
+    /**
+     * Is a directed graph
+     */
+    private boolean directed;
+    
+    public Graph() {}
+
+    public Graph(Collection<Node> nodes, Collection<Relationship> edges,
+        Map<String, String> labels, Set<String> types, boolean directed) {
+      super();
+      this.setNodes(nodes);
+      this.setEdges(edges);
+      this.setLabels(labels);
+      this.setTypes(types);
+      this.setDirected(directed);
+    }
+
+    public Collection<Node> getNodes() {
+      return nodes;
+    }
+
+    public void setNodes(Collection<Node> nodes) {
+      this.nodes = nodes;
+    }
+
+    public Collection<Relationship> getEdges() {
+      return edges;
+    }
+
+    public void setEdges(Collection<Relationship> edges) {
+      this.edges = edges;
+    }
+
+    public Map<String, String> getLabels() {
+      return labels;
+    }
+
+    public void setLabels(Map<String, String> labels) {
+      this.labels = labels;
+    }
+
+    public Set<String> getTypes() {
+      return types;
+    }
+    
+    public void setTypes(Set<String> types) {
+      this.types = types;
+    }
+
+    public boolean isDirected() {
+      return directed;
+    }
+
+    public void setDirected(boolean directed) {
+      this.directed = directed;
+    }
+
+  }
+  
+  private static final Gson gson = new Gson();
+
+  public GraphResult(Code code, Graph graphObject) {
+    super(code, Type.NETWORK, gson.toJson(graphObject));
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/e0fafac7/zeppelin-interpreter/src/main/java/org/apache/zeppelin/tabledata/GraphEntity.java
----------------------------------------------------------------------
diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/tabledata/GraphEntity.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/tabledata/GraphEntity.java
new file mode 100644
index 0000000..320b144
--- /dev/null
+++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/tabledata/GraphEntity.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.zeppelin.tabledata;
+
+import java.util.Map;
+
+/**
+ * The base network entity
+ *
+ */
+public abstract class GraphEntity {
+
+  private long id;
+
+  /**
+   * The data of the entity
+   * 
+   */
+  private Map<String, Object> data;
+
+  /**
+   * The primary type of the entity
+   */
+  private String label;
+  
+  public GraphEntity() {}
+
+  public GraphEntity(long id, Map<String, Object> data, String label) {
+    super();
+    this.setId(id);
+    this.setData(data);
+    this.setLabel(label);
+  }
+
+  public long getId() {
+    return id;
+  }
+
+  public void setId(long id) {
+    this.id = id;
+  }
+
+  public Map<String, Object> getData() {
+    return data;
+  }
+
+  public void setData(Map<String, Object> data) {
+    this.data = data;
+  }
+
+  public String getLabel() {
+    return label;
+  }
+
+  public void setLabel(String label) {
+    this.label = label;
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/e0fafac7/zeppelin-interpreter/src/main/java/org/apache/zeppelin/tabledata/Node.java
----------------------------------------------------------------------
diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/tabledata/Node.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/tabledata/Node.java
new file mode 100644
index 0000000..2efabc4
--- /dev/null
+++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/tabledata/Node.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.zeppelin.tabledata;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * The Zeppelin Node Entity
+ *
+ */
+public class Node extends GraphEntity {
+
+  /**
+   * The labels (types) attached to a node
+   */
+  private Set<String> labels;
+
+  public Node() {}
+
+  
+  public Node(long id, Map<String, Object> data, Set<String> labels) {
+    super(id, data, labels.iterator().next());
+  }
+
+  public Set<String> getLabels() {
+    return labels;
+  }
+
+  public void setLabels(Set<String> labels) {
+    this.labels = labels;
+  }
+ 
+}

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/e0fafac7/zeppelin-interpreter/src/main/java/org/apache/zeppelin/tabledata/Relationship.java
----------------------------------------------------------------------
diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/tabledata/Relationship.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/tabledata/Relationship.java
new file mode 100644
index 0000000..aa8ddb7
--- /dev/null
+++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/tabledata/Relationship.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.zeppelin.tabledata;
+
+import java.util.Map;
+
+/**
+ * The Zeppelin Relationship entity
+ *
+ */
+public class Relationship extends GraphEntity {
+
+  /**
+   * Source node ID
+   */
+  private long source;
+
+  /**
+   * End node ID
+   */
+  private long target;
+
+  public Relationship() {}
+
+  public Relationship(long id, Map<String, Object> data, long source,
+      long target, String label) {
+    super(id, data, label);
+    this.setSource(source);
+    this.setTarget(target);
+  }
+
+  public long getSource() {
+    return source;
+  }
+
+  public void setSource(long startNodeId) {
+    this.source = startNodeId;
+  }
+
+  public long getTarget() {
+    return target;
+  }
+
+  public void setTarget(long endNodeId) {
+    this.target = endNodeId;
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/e0fafac7/zeppelin-zengine/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java
----------------------------------------------------------------------
diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java
index a3deaaa..f329d47 100644
--- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java
+++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java
@@ -627,7 +627,8 @@ public class ZeppelinConfiguration extends XMLConfiguration {
         + "org.apache.zeppelin.bigquery.BigQueryInterpreter,"
         + "org.apache.zeppelin.beam.BeamInterpreter,"
         + "org.apache.zeppelin.scio.ScioInterpreter,"
-        + "org.apache.zeppelin.groovy.GroovyInterpreter"
+        + "org.apache.zeppelin.groovy.GroovyInterpreter,"
+        + "org.apache.zeppelin.neo4j.Neo4jCypherInterpreter"
         ),
     ZEPPELIN_INTERPRETER_JSON("zeppelin.interpreter.setting", "interpreter-setting.json"),
     ZEPPELIN_INTERPRETER_DIR("zeppelin.interpreter.dir", "interpreter"),
@@ -638,7 +639,7 @@ public class ZeppelinConfiguration extends XMLConfiguration {
     ZEPPELIN_INTERPRETER_MAX_POOL_SIZE("zeppelin.interpreter.max.poolsize", 10),
     ZEPPELIN_INTERPRETER_GROUP_ORDER("zeppelin.interpreter.group.order", "spark,md,angular,sh,"
         + "livy,alluxio,file,psql,flink,python,ignite,lens,cassandra,geode,kylin,elasticsearch,"
-        + "scalding,jdbc,hbase,bigquery,beam,pig,scio,groovy"),
+        + "scalding,jdbc,hbase,bigquery,beam,pig,scio,groovy,neo4j"),
     ZEPPELIN_INTERPRETER_OUTPUT_LIMIT("zeppelin.interpreter.output.limit", 1024 * 100),
     ZEPPELIN_ENCODING("zeppelin.encoding", "UTF-8"),
     ZEPPELIN_NOTEBOOK_DIR("zeppelin.notebook.dir", "notebook"),