You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@metamodel.apache.org by to...@apache.org on 2015/11/28 11:32:44 UTC

metamodel git commit: Merging Neo4j module (query-only)

Repository: metamodel
Updated Branches:
  refs/heads/master 137caf0d2 -> 0ddd0cdfe


Merging Neo4j module (query-only)


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

Branch: refs/heads/master
Commit: 0ddd0cdfe7b351b8a6589f265773268bb437c861
Parents: 137caf0
Author: Tomasz Guzialek <to...@guzialek.info>
Authored: Sat Nov 28 11:30:30 2015 +0100
Committer: Tomasz Guzialek <to...@guzialek.info>
Committed: Sat Nov 28 11:30:30 2015 +0100

----------------------------------------------------------------------
 .travis.yml                                     |   7 +
 ...del-integrationtest-configuration.properties |  12 +-
 full/pom.xml                                    |   5 +
 hadoop/src/test/resources/log4j.xml             |  40 +-
 neo4j/.gitignore                                |   4 +
 neo4j/pom.xml                                   |  74 +++
 .../neo4j/Neo4jCypherQueryBuilder.java          | 162 ++++++
 .../metamodel/neo4j/Neo4jDataContext.java       | 356 ++++++++++++
 .../apache/metamodel/neo4j/Neo4jDataSet.java    |  77 +++
 .../metamodel/neo4j/Neo4jRequestWrapper.java    | 136 +++++
 .../apache/metamodel/neo4j/package-info.java    |  23 +
 .../metamodel/neo4j/Neo4jDataContextTest.java   | 545 +++++++++++++++++++
 .../neo4j/Neo4jRequestWrapperTest.java          | 172 ++++++
 .../apache/metamodel/neo4j/Neo4jTestCase.java   | 117 ++++
 pom.xml                                         |  34 +-
 ...del-integrationtest-configuration.properties |   7 +
 16 files changed, 1743 insertions(+), 28 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/metamodel/blob/0ddd0cdf/.travis.yml
----------------------------------------------------------------------
diff --git a/.travis.yml b/.travis.yml
index 1c9a38d..14f8734 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,6 +3,13 @@ language: java
 jdk:
   - openjdk7
   - oraclejdk8
+  
+before_install:
+  # install Neo4j locally:
+  - wget dist.neo4j.org/neo4j-community-2.2.3-unix.tar.gz
+  - tar -xzf neo4j-community-2.2.3-unix.tar.gz
+  - sed -i.bak s/dbms.security.auth_enabled=true/dbms.security.auth_enabled=false/g neo4j-community-2.2.3/conf/neo4j-server.properties
+  - neo4j-community-2.2.3/bin/neo4j start
 
 services:
   - couchdb

http://git-wip-us.apache.org/repos/asf/metamodel/blob/0ddd0cdf/example-metamodel-integrationtest-configuration.properties
----------------------------------------------------------------------
diff --git a/example-metamodel-integrationtest-configuration.properties b/example-metamodel-integrationtest-configuration.properties
index 8a2281d..83c5f4e 100644
--- a/example-metamodel-integrationtest-configuration.properties
+++ b/example-metamodel-integrationtest-configuration.properties
@@ -106,4 +106,14 @@
 
 #cassandra.hostname=localhost
 #cassandra.port=9042
-#cassandra.keyspace=my_keyspace
\ No newline at end of file
+#cassandra.keyspace=my_keyspace
+
+# ---------------------------
+# Neo4j module properties:
+# ---------------------------
+
+#neo4j.hostname=localhost
+#neo4j.port=7474
+#neo4j.username=neo4j
+#neo4j.password=neo4j
+#neo4j.serviceroot=/data/db

http://git-wip-us.apache.org/repos/asf/metamodel/blob/0ddd0cdf/full/pom.xml
----------------------------------------------------------------------
diff --git a/full/pom.xml b/full/pom.xml
index 5835eef..867b61a 100644
--- a/full/pom.xml
+++ b/full/pom.xml
@@ -152,6 +152,11 @@ under the License.
 		</dependency>
 		<dependency>
 			<groupId>org.apache.metamodel</groupId>
+			<artifactId>MetaModel-neo4j</artifactId>
+			<version>${project.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.metamodel</groupId>
 			<artifactId>MetaModel-fixedwidth</artifactId>
 			<version>${project.version}</version>
 		</dependency>

http://git-wip-us.apache.org/repos/asf/metamodel/blob/0ddd0cdf/hadoop/src/test/resources/log4j.xml
----------------------------------------------------------------------
diff --git a/hadoop/src/test/resources/log4j.xml b/hadoop/src/test/resources/log4j.xml
index 29f497f..b1971c7 100644
--- a/hadoop/src/test/resources/log4j.xml
+++ b/hadoop/src/test/resources/log4j.xml
@@ -1,21 +1,21 @@
-<?xml version="1.0" encoding="UTF-8" ?>
-<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
-<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
-
-	<appender name="consoleAppender" class="org.apache.log4j.ConsoleAppender">
-		<param name="Target" value="System.out" />
-		<layout class="org.apache.log4j.PatternLayout">
-			<param name="ConversionPattern" value="%-5p %d{HH:mm:ss} %c{1} - %m%n" />
-		</layout>
-	</appender>
-
-	<logger name="org.apache.metamodel">
-		<level value="info" />
-	</logger>
-
-	<root>
-		<priority value="fatal" />
-		<appender-ref ref="consoleAppender" />
-	</root>
-
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
+<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
+
+	<appender name="consoleAppender" class="org.apache.log4j.ConsoleAppender">
+		<param name="Target" value="System.out" />
+		<layout class="org.apache.log4j.PatternLayout">
+			<param name="ConversionPattern" value="%-5p %d{HH:mm:ss} %c{1} - %m%n" />
+		</layout>
+	</appender>
+
+	<logger name="org.apache.metamodel">
+		<level value="info" />
+	</logger>
+
+	<root>
+		<priority value="fatal" />
+		<appender-ref ref="consoleAppender" />
+	</root>
+
 </log4j:configuration>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/metamodel/blob/0ddd0cdf/neo4j/.gitignore
----------------------------------------------------------------------
diff --git a/neo4j/.gitignore b/neo4j/.gitignore
new file mode 100644
index 0000000..4e247ee
--- /dev/null
+++ b/neo4j/.gitignore
@@ -0,0 +1,4 @@
+/.settings
+/target
+/.classpath
+/.project

http://git-wip-us.apache.org/repos/asf/metamodel/blob/0ddd0cdf/neo4j/pom.xml
----------------------------------------------------------------------
diff --git a/neo4j/pom.xml b/neo4j/pom.xml
new file mode 100644
index 0000000..90ca1a9
--- /dev/null
+++ b/neo4j/pom.xml
@@ -0,0 +1,74 @@
+<?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">
+	<parent>
+		<artifactId>MetaModel</artifactId>
+		<groupId>org.apache.metamodel</groupId>
+		<version>4.4.2-SNAPSHOT</version>
+	</parent>
+	<modelVersion>4.0.0</modelVersion>
+	<artifactId>MetaModel-neo4j</artifactId>
+	<name>MetaModel module for Neo4j databases</name>
+	<dependencies>
+		<dependency>
+			<groupId>org.apache.metamodel</groupId>
+			<artifactId>MetaModel-core</artifactId>
+			<version>${project.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.httpcomponents</groupId>
+			<artifactId>httpclient</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.slf4j</groupId>
+			<artifactId>jcl-over-slf4j</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>com.fasterxml.jackson.datatype</groupId>
+			<artifactId>jackson-datatype-json-org</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>commons-io</groupId>
+			<artifactId>commons-io</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>commons-pool</groupId>
+			<artifactId>commons-pool</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>com.google.guava</groupId>
+			<artifactId>guava</artifactId>
+		</dependency>
+
+		<!-- Test dependencies -->
+		<dependency>
+			<groupId>org.slf4j</groupId>
+			<artifactId>slf4j-nop</artifactId>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>junit</groupId>
+			<artifactId>junit</artifactId>
+			<scope>test</scope>
+		</dependency>
+	</dependencies>
+
+</project>

http://git-wip-us.apache.org/repos/asf/metamodel/blob/0ddd0cdf/neo4j/src/main/java/org/apache/metamodel/neo4j/Neo4jCypherQueryBuilder.java
----------------------------------------------------------------------
diff --git a/neo4j/src/main/java/org/apache/metamodel/neo4j/Neo4jCypherQueryBuilder.java b/neo4j/src/main/java/org/apache/metamodel/neo4j/Neo4jCypherQueryBuilder.java
new file mode 100644
index 0000000..5d3ac6c
--- /dev/null
+++ b/neo4j/src/main/java/org/apache/metamodel/neo4j/Neo4jCypherQueryBuilder.java
@@ -0,0 +1,162 @@
+/**
+ * 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.metamodel.neo4j;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.metamodel.query.FilterItem;
+import org.apache.metamodel.schema.Column;
+import org.apache.metamodel.schema.Table;
+
+public class Neo4jCypherQueryBuilder {
+
+    public static String buildSelectQuery(Table table, Column[] columns, int firstRow, int maxRows) {
+        String[] columnNames = new String[columns.length];
+        for (int i = 0; i < columns.length; i++) {
+            columnNames[i] = columns[i].getName();
+        }
+        return buildSelectQuery(table.getName(), columnNames, firstRow, maxRows);
+    }
+
+    public static String buildSelectQuery(String tableName, String[] columnNames, int firstRow, int maxRows) {
+        Map<String, String> returnClauseMap = new LinkedHashMap<>();
+        Map<String, Integer> relationshipIndexMap = new LinkedHashMap<>();
+        for (String columnName : columnNames) {
+            if (columnName.startsWith(Neo4jDataContext.RELATIONSHIP_PREFIX)) {
+                columnName = columnName.replace(Neo4jDataContext.RELATIONSHIP_PREFIX, "");
+
+                String relationshipName;
+                String relationshipPropertyName;
+
+                if (columnName.contains(Neo4jDataContext.RELATIONSHIP_COLUMN_SEPARATOR)) {
+                    String[] parsedColumnNameArray = columnName.split(Neo4jDataContext.RELATIONSHIP_COLUMN_SEPARATOR);
+
+                    relationshipName = parsedColumnNameArray[0];
+                    relationshipPropertyName = parsedColumnNameArray[1];
+                } else {
+                    relationshipName = columnName;
+                    relationshipPropertyName = "metamodel_neo4j_relationship_marker";
+                }
+
+                String relationshipAlias;
+                if (relationshipIndexMap.containsKey(relationshipName)) {
+                    relationshipAlias = "r" + relationshipIndexMap.get(relationshipName);
+                } else {
+                    int nextIndex;
+                    if (relationshipIndexMap.values().isEmpty()) {
+                        nextIndex = 0;
+                    } else {
+                        nextIndex = Collections.max(relationshipIndexMap.values()) + 1;
+                    }
+                    relationshipIndexMap.put(relationshipName, nextIndex);
+                    relationshipAlias = "r" + relationshipIndexMap.get(relationshipName);
+                }
+
+                if (relationshipPropertyName.equals("metamodel_neo4j_relationship_marker")) {
+                    returnClauseMap.put(columnName, "id(" + relationshipAlias + "_relationshipEndNode)");
+                } else {
+                    returnClauseMap.put(columnName, relationshipAlias + "." + relationshipPropertyName);
+                }
+            } else {
+                if (columnName.equals("_id")) {
+                    returnClauseMap.put(columnName, "id(n)");
+                } else {
+                    returnClauseMap.put(columnName, "n." + columnName);
+                }
+            }
+        }
+
+        StringBuilder cypherBuilder = new StringBuilder();
+        cypherBuilder.append("MATCH (n:");
+        cypherBuilder.append(tableName);
+        for (Map.Entry<String, Integer> relationshipAliasEntry : relationshipIndexMap.entrySet()) {
+            cypherBuilder.append(") OPTIONAL MATCH (n)-[r" + relationshipAliasEntry.getValue() + ":"
+                    + relationshipAliasEntry.getKey() + "]->(r" + relationshipAliasEntry.getValue()
+                    + "_relationshipEndNode");
+        }
+        cypherBuilder.append(") RETURN ");
+        boolean addComma = false;
+        for (Map.Entry<String, String> returnClauseEntry : returnClauseMap.entrySet()) {
+            if (addComma) {
+                cypherBuilder.append(",");
+            }
+            cypherBuilder.append(returnClauseEntry.getValue());
+            addComma = true;
+        }
+
+        if (firstRow > 1) {
+            cypherBuilder.append(" SKIP " + (firstRow - 1));
+        }
+        if (maxRows > -1) {
+            cypherBuilder.append(" LIMIT " + maxRows);
+        }
+        return cypherBuilder.toString();
+    }
+
+    public static String buildCountQuery(String tableName, List<FilterItem> whereItems) {
+        StringBuilder cypherBuilder = new StringBuilder();
+        cypherBuilder.append("MATCH (n:");
+        cypherBuilder.append(tableName);
+        cypherBuilder.append(") ");
+        cypherBuilder.append(buildWhereClause(whereItems, "n"));
+        cypherBuilder.append(" RETURN COUNT(*);");
+        return cypherBuilder.toString();
+    }
+
+    private static String buildWhereClause(List<FilterItem> whereItems, String queryObjectHandle) {
+        if ((whereItems != null) && (!whereItems.isEmpty())) {
+            StringBuilder whereClauseBuilder = new StringBuilder();
+            whereClauseBuilder.append("WHERE ");
+
+            FilterItem firstWhereItem = whereItems.get(0);
+            whereClauseBuilder.append(buildWhereClauseItem(firstWhereItem, queryObjectHandle));
+
+            for (int i = 1; i < whereItems.size(); i++) {
+                whereClauseBuilder.append(" AND ");
+                FilterItem whereItem = whereItems.get(i);
+                whereClauseBuilder.append(buildWhereClauseItem(whereItem, queryObjectHandle));
+            }
+
+            return whereClauseBuilder.toString();
+        } else {
+            return "";
+        }
+    }
+
+    private static String buildWhereClauseItem(FilterItem whereItem, String queryObjectHandle) {
+        StringBuilder whereClauseItemBuilder = new StringBuilder();
+        whereClauseItemBuilder.append(queryObjectHandle);
+        whereClauseItemBuilder.append(".");
+        whereClauseItemBuilder.append(whereItem.getSelectItem().getColumn().getName());
+        whereClauseItemBuilder.append(whereItem.getOperator().toSql());
+        final Object operand = whereItem.getOperand();
+        if (operand instanceof String) {
+            whereClauseItemBuilder.append("\"");
+        }
+        whereClauseItemBuilder.append(operand);
+        if (operand instanceof String) {
+            whereClauseItemBuilder.append("\"");
+        }
+        return whereClauseItemBuilder.toString();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/metamodel/blob/0ddd0cdf/neo4j/src/main/java/org/apache/metamodel/neo4j/Neo4jDataContext.java
----------------------------------------------------------------------
diff --git a/neo4j/src/main/java/org/apache/metamodel/neo4j/Neo4jDataContext.java b/neo4j/src/main/java/org/apache/metamodel/neo4j/Neo4jDataContext.java
new file mode 100644
index 0000000..568ad71
--- /dev/null
+++ b/neo4j/src/main/java/org/apache/metamodel/neo4j/Neo4jDataContext.java
@@ -0,0 +1,356 @@
+/**
+ * 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.metamodel.neo4j;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.http.HttpHost;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.metamodel.DataContext;
+import org.apache.metamodel.MetaModelException;
+import org.apache.metamodel.MetaModelHelper;
+import org.apache.metamodel.QueryPostprocessDataContext;
+import org.apache.metamodel.data.DataSet;
+import org.apache.metamodel.data.DocumentSource;
+import org.apache.metamodel.query.FilterItem;
+import org.apache.metamodel.query.SelectItem;
+import org.apache.metamodel.schema.Column;
+import org.apache.metamodel.schema.MutableSchema;
+import org.apache.metamodel.schema.MutableTable;
+import org.apache.metamodel.schema.Schema;
+import org.apache.metamodel.schema.Table;
+import org.apache.metamodel.schema.builder.DocumentSourceProvider;
+import org.apache.metamodel.util.SimpleTableDef;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * DataContext implementation for Neo4j
+ */
+public class Neo4jDataContext extends QueryPostprocessDataContext implements DataContext, DocumentSourceProvider {
+
+    public static final Logger logger = LoggerFactory.getLogger(Neo4jDataContext.class);
+
+    public static final String SCHEMA_NAME = "neo4j";
+
+    public static final int DEFAULT_PORT = 7474;
+
+    public static final String RELATIONSHIP_PREFIX = "rel_";
+
+    public static final String RELATIONSHIP_COLUMN_SEPARATOR = "#";
+
+    private final SimpleTableDef[] _tableDefs;
+
+    private final Neo4jRequestWrapper _requestWrapper;
+
+    private final HttpHost _httpHost;
+
+    private String _serviceRoot = "/db/data";
+
+    public Neo4jDataContext(String hostname, int port, String username, String password, SimpleTableDef... tableDefs) {
+        _httpHost = new HttpHost(hostname, port);
+        final CloseableHttpClient httpClient = HttpClientBuilder.create().build();
+        _requestWrapper = new Neo4jRequestWrapper(httpClient, _httpHost, username, password, _serviceRoot);
+        _tableDefs = tableDefs;
+    }
+
+    public Neo4jDataContext(String hostname, int port, String username, String password, String serviceRoot,
+            SimpleTableDef... tableDefs) {
+        _httpHost = new HttpHost(hostname, port);
+        final CloseableHttpClient httpClient = HttpClientBuilder.create().build();
+        _requestWrapper = new Neo4jRequestWrapper(httpClient, _httpHost, username, password, _serviceRoot);
+        _tableDefs = tableDefs;
+        _serviceRoot = serviceRoot;
+    }
+
+    public Neo4jDataContext(String hostname, int port, String username, String password) {
+        _httpHost = new HttpHost(hostname, port);
+        final CloseableHttpClient httpClient = HttpClientBuilder.create().build();
+        _requestWrapper = new Neo4jRequestWrapper(httpClient, _httpHost, username, password, _serviceRoot);
+        _tableDefs = detectTableDefs();
+    }
+
+    public Neo4jDataContext(String hostname, int port, String username, String password, String serviceRoot) {
+        _httpHost = new HttpHost(hostname, port);
+        final CloseableHttpClient httpClient = HttpClientBuilder.create().build();
+        _requestWrapper = new Neo4jRequestWrapper(httpClient, _httpHost, username, password, _serviceRoot);
+        _tableDefs = detectTableDefs();
+        _serviceRoot = serviceRoot;
+    }
+
+    public Neo4jDataContext(String hostname, int port, CloseableHttpClient httpClient) {
+        _httpHost = new HttpHost(hostname, port);
+        _requestWrapper = new Neo4jRequestWrapper(httpClient, _httpHost, _serviceRoot);
+        _tableDefs = detectTableDefs();
+    }
+
+    public Neo4jDataContext(String hostname, int port, CloseableHttpClient httpClient, String serviceRoot) {
+        _httpHost = new HttpHost(hostname, port);
+        _requestWrapper = new Neo4jRequestWrapper(httpClient, _httpHost, _serviceRoot);
+        _tableDefs = detectTableDefs();
+        _serviceRoot = serviceRoot;
+    }
+
+    public Neo4jDataContext(String hostname, int port, CloseableHttpClient httpClient, SimpleTableDef... tableDefs) {
+        _httpHost = new HttpHost(hostname, port);
+        _requestWrapper = new Neo4jRequestWrapper(httpClient, _httpHost, _serviceRoot);
+        _tableDefs = tableDefs;
+    }
+
+    public Neo4jDataContext(String hostname, int port, CloseableHttpClient httpClient, String serviceRoot,
+            SimpleTableDef... tableDefs) {
+        _httpHost = new HttpHost(hostname, port);
+        _requestWrapper = new Neo4jRequestWrapper(httpClient, _httpHost, _serviceRoot);
+        _tableDefs = tableDefs;
+        _serviceRoot = serviceRoot;
+    }
+
+    @Override
+    protected String getDefaultSchemaName() throws MetaModelException {
+        return SCHEMA_NAME;
+    }
+
+    @Override
+    protected Schema getMainSchema() throws MetaModelException {
+        MutableSchema schema = new MutableSchema(getMainSchemaName());
+        for (SimpleTableDef tableDef : _tableDefs) {
+            MutableTable table = tableDef.toTable().setSchema(schema);
+            schema.addTable(table);
+        }
+        return schema;
+    }
+
+    @Override
+    protected String getMainSchemaName() throws MetaModelException {
+        return SCHEMA_NAME;
+    }
+
+    public SimpleTableDef[] detectTableDefs() {
+        List<SimpleTableDef> tableDefs = new ArrayList<SimpleTableDef>();
+
+        String labelsJsonString = _requestWrapper.executeRestRequest(new HttpGet(_serviceRoot + "/labels"));
+
+        JSONArray labelsJsonArray;
+        try {
+            labelsJsonArray = new JSONArray(labelsJsonString);
+            for (int i = 0; i < labelsJsonArray.length(); i++) {
+                String label = labelsJsonArray.getString(i);
+
+                List<JSONObject> nodesPerLabel = getAllNodesPerLabel(label);
+
+                List<String> propertiesPerLabel = new ArrayList<String>();
+                for (JSONObject node : nodesPerLabel) {
+                    List<String> propertiesPerNode = getAllPropertiesPerNode(node);
+                    for (String property : propertiesPerNode) {
+                        if (!propertiesPerLabel.contains(property)) {
+                            propertiesPerLabel.add(property);
+                        }
+                    }
+                }
+
+                List<String> relationshipPropertiesPerLabel = new ArrayList<String>();
+                for (JSONObject node : nodesPerLabel) {
+                    Integer nodeId = (Integer) node.getJSONObject("metadata").get("id");
+                    List<JSONObject> relationshipsPerNode = getOutgoingRelationshipsPerNode(nodeId);
+                    for (JSONObject relationship : relationshipsPerNode) {
+                        // Add the relationship as a column in the table
+                        String relationshipName = relationship.getString("type");
+                        String relationshipNameProperty = RELATIONSHIP_PREFIX + relationshipName;
+                        if (!relationshipPropertiesPerLabel.contains(relationshipNameProperty)) {
+                            relationshipPropertiesPerLabel.add(relationshipNameProperty);
+                        }
+
+                        // Add all the relationship properties as table columns
+                        List<String> propertiesPerRelationship = getAllPropertiesPerRelationship(relationship);
+                        relationshipPropertiesPerLabel.addAll(propertiesPerRelationship);
+                    }
+                }
+                propertiesPerLabel.addAll(relationshipPropertiesPerLabel);
+
+                // Do not add a table if label has no nodes (empty tables are
+                // considered non-existent)
+                if (!nodesPerLabel.isEmpty()) {
+                    SimpleTableDef tableDef = new SimpleTableDef(label,
+                            propertiesPerLabel.toArray(new String[propertiesPerLabel.size()]));
+                    tableDefs.add(tableDef);
+                }
+            }
+            return tableDefs.toArray(new SimpleTableDef[tableDefs.size()]);
+        } catch (JSONException e) {
+            logger.error("Error occured in parsing JSON while detecting the schema: ", e);
+            throw new IllegalStateException(e);
+        }
+    }
+
+    private List<String> getAllPropertiesPerRelationship(JSONObject relationship) {
+        List<String> propertyNames = new ArrayList<String>();
+        try {
+            String relationshipName = RELATIONSHIP_PREFIX + relationship.getJSONObject("metadata").getString("type");
+            JSONObject relationshipPropertiesJSONObject = relationship.getJSONObject("data");
+            if (relationshipPropertiesJSONObject.length() > 0) {
+                JSONArray relationshipPropertiesNamesJSONArray = relationshipPropertiesJSONObject.names();
+                for (int i = 0; i < relationshipPropertiesNamesJSONArray.length(); i++) {
+                    String propertyName = relationshipName + RELATIONSHIP_COLUMN_SEPARATOR
+                            + relationshipPropertiesNamesJSONArray.getString(i);
+                    if (!propertyNames.contains(propertyName)) {
+                        propertyNames.add(propertyName);
+                    }
+                }
+            }
+            return propertyNames;
+        } catch (JSONException e) {
+            logger.error("Error occured in parsing JSON while getting relationship properties: ", e);
+            throw new IllegalStateException(e);
+        }
+    }
+
+    private List<JSONObject> getOutgoingRelationshipsPerNode(Integer nodeId) {
+        List<JSONObject> outgoingRelationshipsPerNode = new ArrayList<JSONObject>();
+
+        String outgoingRelationshipsPerNodeJsonString = _requestWrapper.executeRestRequest(new HttpGet(_serviceRoot
+                + "/node/" + nodeId + "/relationships/out"));
+
+        JSONArray outgoingRelationshipsPerNodeJsonArray;
+        try {
+            outgoingRelationshipsPerNodeJsonArray = new JSONArray(outgoingRelationshipsPerNodeJsonString);
+            for (int i = 0; i < outgoingRelationshipsPerNodeJsonArray.length(); i++) {
+                JSONObject relationship = outgoingRelationshipsPerNodeJsonArray.getJSONObject(i);
+                if (!outgoingRelationshipsPerNode.contains(relationship)) {
+                    outgoingRelationshipsPerNode.add(relationship);
+                }
+            }
+            return outgoingRelationshipsPerNode;
+        } catch (JSONException e) {
+            logger.error("Error occured in parsing JSON while detecting outgoing relationships for node: " + nodeId, e);
+            throw new IllegalStateException(e);
+        }
+    }
+
+    private List<JSONObject> getAllNodesPerLabel(String label) {
+        List<JSONObject> allNodesPerLabel = new ArrayList<JSONObject>();
+
+        String allNodesForLabelJsonString = _requestWrapper.executeRestRequest(new HttpGet(_serviceRoot + "/label/"
+                + label + "/nodes"));
+
+        JSONArray allNodesForLabelJsonArray;
+        try {
+            allNodesForLabelJsonArray = new JSONArray(allNodesForLabelJsonString);
+            for (int i = 0; i < allNodesForLabelJsonArray.length(); i++) {
+                JSONObject node = allNodesForLabelJsonArray.getJSONObject(i);
+                allNodesPerLabel.add(node);
+            }
+            return allNodesPerLabel;
+        } catch (JSONException e) {
+            logger.error("Error occured in parsing JSON while detecting the nodes for a label: " + label, e);
+            throw new IllegalStateException(e);
+        }
+    }
+
+    private List<String> getAllPropertiesPerNode(JSONObject node) {
+        List<String> properties = new ArrayList<String>();
+        properties.add("_id");
+
+        String propertiesEndpoint;
+        try {
+            propertiesEndpoint = node.getString("properties");
+
+            String allPropertiesPerNodeJsonString = _requestWrapper.executeRestRequest(new HttpGet(propertiesEndpoint));
+
+            JSONObject allPropertiesPerNodeJsonObject = new JSONObject(allPropertiesPerNodeJsonString);
+            for (int j = 0; j < allPropertiesPerNodeJsonObject.length(); j++) {
+                JSONArray propertiesJsonArray = allPropertiesPerNodeJsonObject.names();
+                for (int k = 0; k < propertiesJsonArray.length(); k++) {
+                    String property = propertiesJsonArray.getString(k);
+                    properties.add(property);
+                }
+            }
+            return properties;
+        } catch (JSONException e) {
+            logger.error("Error occured in parsing JSON while detecting the properties of a node: " + node, e);
+            throw new IllegalStateException(e);
+        }
+    }
+
+    @Override
+    protected DataSet materializeMainSchemaTable(Table table, Column[] columns, int firstRow, int maxRows) {
+        if ((columns != null) && (columns.length > 0)) {
+            Neo4jDataSet dataSet = null;
+            try {
+                String selectQuery = Neo4jCypherQueryBuilder.buildSelectQuery(table, columns, firstRow, maxRows);
+                String responseJSONString = _requestWrapper.executeCypherQuery(selectQuery);
+                JSONObject resultJSONObject = new JSONObject(responseJSONString);
+                final SelectItem[] selectItems = MetaModelHelper.createSelectItems(columns);
+                dataSet = new Neo4jDataSet(selectItems, resultJSONObject);
+            } catch (JSONException e) {
+                logger.error("Error occured in parsing JSON while materializing the schema: ", e);
+                throw new IllegalStateException(e);
+            }
+
+            return dataSet;
+        } else {
+            logger.error("Encountered null or empty columns array for materializing main schema table.");
+            throw new IllegalArgumentException("Columns cannot be null or empty array");
+        }
+    }
+
+    @Override
+    protected DataSet materializeMainSchemaTable(Table table, Column[] columns, int maxRows) {
+        return materializeMainSchemaTable(table, columns, 1, maxRows);
+    }
+
+    @Override
+    protected Number executeCountQuery(Table table, List<FilterItem> whereItems, boolean functionApproximationAllowed) {
+        String countQuery = Neo4jCypherQueryBuilder.buildCountQuery(table.getName(), whereItems);
+        String jsonResponse = _requestWrapper.executeCypherQuery(countQuery);
+
+        JSONObject jsonResponseObject;
+        try {
+            jsonResponseObject = new JSONObject(jsonResponse);
+            JSONArray resultsJSONArray = jsonResponseObject.getJSONArray("results");
+            JSONObject resultJSONObject = (JSONObject) resultsJSONArray.get(0);
+            JSONArray dataJSONArray = resultJSONObject.getJSONArray("data");
+            JSONObject rowJSONObject = (JSONObject) dataJSONArray.get(0);
+            JSONArray valueJSONArray = rowJSONObject.getJSONArray("row");
+            Number value = (Number) valueJSONArray.get(0);
+            return value;
+        } catch (JSONException e) {
+            logger.error("Error occured in parsing JSON response: ", e);
+            // Do not throw an exception here. Returning null here will make
+            // MetaModel attempt to count records manually and therefore recover
+            // from the error.
+            return null;
+        }
+    }
+
+    @Override
+    public DocumentSource getMixedDocumentSourceForSampling() {
+        return null;
+    }
+
+    @Override
+    public DocumentSource getDocumentSourceForTable(String sourceCollectionName) {
+        return null;
+    }
+}

http://git-wip-us.apache.org/repos/asf/metamodel/blob/0ddd0cdf/neo4j/src/main/java/org/apache/metamodel/neo4j/Neo4jDataSet.java
----------------------------------------------------------------------
diff --git a/neo4j/src/main/java/org/apache/metamodel/neo4j/Neo4jDataSet.java b/neo4j/src/main/java/org/apache/metamodel/neo4j/Neo4jDataSet.java
new file mode 100644
index 0000000..bf132e8
--- /dev/null
+++ b/neo4j/src/main/java/org/apache/metamodel/neo4j/Neo4jDataSet.java
@@ -0,0 +1,77 @@
+/**
+ * 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.metamodel.neo4j;
+
+import org.apache.metamodel.data.AbstractDataSet;
+import org.apache.metamodel.data.DefaultRow;
+import org.apache.metamodel.data.Row;
+import org.apache.metamodel.data.SimpleDataSetHeader;
+import org.apache.metamodel.query.SelectItem;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+final class Neo4jDataSet extends AbstractDataSet {
+
+    private JSONObject _resultJSONObject;
+    private int _currentRowIndex;
+    private Row _row;
+
+    public Neo4jDataSet(SelectItem[] selectItems, JSONObject resultJSONObject) {
+        super(selectItems);
+        _resultJSONObject = resultJSONObject;
+        _currentRowIndex = 0;
+    }
+
+    @Override
+    public boolean next() {
+        try {
+            JSONArray resultsArray = _resultJSONObject.getJSONArray("results");
+            if (resultsArray.length() > 0) {
+                JSONObject results = resultsArray.getJSONObject(0);
+                JSONArray data = results.getJSONArray("data");
+                if (_currentRowIndex < data.length()) {
+                    JSONObject row = data.getJSONObject(_currentRowIndex);
+                    JSONArray jsonValues = row.getJSONArray("row");
+
+                    Object[] objectValues = new Object[jsonValues.length()];
+                    for (int i = 0; i < jsonValues.length(); i++) {
+                        objectValues[i] = jsonValues.getString(i);
+                    }
+                    _row = new DefaultRow(new SimpleDataSetHeader(getSelectItems()), objectValues);
+                    _currentRowIndex++;
+                    return true;
+                }
+            } else {
+                JSONArray errorArray = _resultJSONObject.getJSONArray("errors");
+                JSONObject error = errorArray.getJSONObject(0);
+                throw new IllegalStateException(error.toString());
+            }
+        } catch (JSONException e) {
+            throw new IllegalStateException(e);
+        }
+        return false;
+    }
+
+    @Override
+    public Row getRow() {
+        return _row;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/metamodel/blob/0ddd0cdf/neo4j/src/main/java/org/apache/metamodel/neo4j/Neo4jRequestWrapper.java
----------------------------------------------------------------------
diff --git a/neo4j/src/main/java/org/apache/metamodel/neo4j/Neo4jRequestWrapper.java b/neo4j/src/main/java/org/apache/metamodel/neo4j/Neo4jRequestWrapper.java
new file mode 100644
index 0000000..864a2b9
--- /dev/null
+++ b/neo4j/src/main/java/org/apache/metamodel/neo4j/Neo4jRequestWrapper.java
@@ -0,0 +1,136 @@
+/**
+ * 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.metamodel.neo4j;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.List;
+
+import org.apache.http.HttpHost;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.methods.HttpRequestBase;
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.util.EntityUtils;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.io.BaseEncoding;
+
+/**
+ * The class takes care of sending an {@link HttpRequestBase} or a Cypher query
+ * to the specified Neo4j instance. Also takes care of the authentication.
+ * 
+ */
+public class Neo4jRequestWrapper {
+
+    private static final Logger logger = LoggerFactory.getLogger(Neo4jRequestWrapper.class);
+
+    private final CloseableHttpClient _httpClient;
+    private final HttpHost _httpHost;
+    private final HttpPost _cypherQueryHttpPost;
+    private final String _username;
+    private final String _password;
+
+    public Neo4jRequestWrapper(CloseableHttpClient httpClient, HttpHost httpHost, String username, String password,
+            String serviceRoot) {
+        _httpClient = httpClient;
+        _httpHost = httpHost;
+        _username = username;
+        _password = password;
+        _cypherQueryHttpPost = new HttpPost(serviceRoot + "/transaction/commit");
+    }
+
+    public Neo4jRequestWrapper(CloseableHttpClient httpClient, HttpHost httpHost, String serviceRoot) {
+        this(httpClient, httpHost, null, null, serviceRoot);
+    }
+
+    public String executeRestRequest(HttpRequestBase httpRequest) {
+        return executeRestRequest(httpRequest, _username, _password);
+    }
+
+    public String executeRestRequest(HttpRequestBase httpRequest, String username, String password) {
+        if ((username != null) && (password != null)) {
+            String base64credentials = BaseEncoding.base64().encode(
+                    (username + ":" + password).getBytes(StandardCharsets.UTF_8));
+            httpRequest.addHeader("Authorization", "Basic " + base64credentials);
+        }
+
+        try {
+            CloseableHttpResponse response = _httpClient.execute(_httpHost, httpRequest);
+            if (response.getEntity() != null) {
+                return EntityUtils.toString(response.getEntity());
+            }
+            return null;
+        } catch (ClientProtocolException e) {
+            logger.error("An error occured while executing " + httpRequest, e);
+            throw new IllegalStateException(e);
+        } catch (IOException e) {
+            logger.error("An error occured while executing " + httpRequest, e);
+            throw new IllegalStateException(e);
+        }
+    }
+
+    public String executeCypherQuery(String cypherQuery) {
+        JSONObject cypherQueryRequest = new JSONObject();
+        HashMap<String, String> statement = new HashMap<String, String>();
+        statement.put("statement", cypherQuery);
+
+        JSONArray statementsArray = new JSONArray();
+        statementsArray.put(statement);
+
+        return executeRequest(cypherQueryRequest, statementsArray);
+    }
+
+    public String executeCypherQueries(List<String> cypherQueries) {
+        JSONObject cypherQueryRequest = new JSONObject();
+        JSONArray statementsArray = new JSONArray();
+        for (String cypherQuery : cypherQueries) {
+            HashMap<String, String> statement = new HashMap<String, String>();
+            statement.put("statement", cypherQuery);
+
+            statementsArray.put(statement);
+        }
+
+        return executeRequest(cypherQueryRequest, statementsArray);
+    }
+
+    private String executeRequest(JSONObject cypherQueryRequest, JSONArray statementsArray) {
+        try {
+            cypherQueryRequest.put("statements", statementsArray);
+
+            String requestBody = cypherQueryRequest.toString();
+            _cypherQueryHttpPost.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
+
+            String responseJSONString = executeRestRequest(_cypherQueryHttpPost);
+            return responseJSONString;
+        } catch (JSONException e) {
+            logger.error("Error occured while constructing JSON request body for " + _cypherQueryHttpPost, e);
+            throw new IllegalStateException(e);
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/metamodel/blob/0ddd0cdf/neo4j/src/main/java/org/apache/metamodel/neo4j/package-info.java
----------------------------------------------------------------------
diff --git a/neo4j/src/main/java/org/apache/metamodel/neo4j/package-info.java b/neo4j/src/main/java/org/apache/metamodel/neo4j/package-info.java
new file mode 100644
index 0000000..55db480
--- /dev/null
+++ b/neo4j/src/main/java/org/apache/metamodel/neo4j/package-info.java
@@ -0,0 +1,23 @@
+/**
+ * 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.
+ */
+/**
+ * Module package for Neo4j support
+ */
+package org.apache.metamodel.neo4j;
+

http://git-wip-us.apache.org/repos/asf/metamodel/blob/0ddd0cdf/neo4j/src/test/java/org/apache/metamodel/neo4j/Neo4jDataContextTest.java
----------------------------------------------------------------------
diff --git a/neo4j/src/test/java/org/apache/metamodel/neo4j/Neo4jDataContextTest.java b/neo4j/src/test/java/org/apache/metamodel/neo4j/Neo4jDataContextTest.java
new file mode 100644
index 0000000..d4e3774
--- /dev/null
+++ b/neo4j/src/test/java/org/apache/metamodel/neo4j/Neo4jDataContextTest.java
@@ -0,0 +1,545 @@
+/**
+ * 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.metamodel.neo4j;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import org.apache.http.HttpHost;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.metamodel.DataContext;
+import org.apache.metamodel.data.DataSet;
+import org.apache.metamodel.data.Row;
+import org.apache.metamodel.query.CompiledQuery;
+import org.apache.metamodel.schema.Column;
+import org.apache.metamodel.schema.Schema;
+import org.apache.metamodel.schema.Table;
+import org.json.JSONObject;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class Neo4jDataContextTest extends Neo4jTestCase {
+
+    private static final Logger logger = LoggerFactory.getLogger(Neo4jDataContextTest.class);
+
+    Neo4jRequestWrapper requestWrapper;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        if (isConfigured()) {
+            final CloseableHttpClient httpClient = HttpClientBuilder.create().build();
+
+            final HttpHost httpHost = new HttpHost(getHostname(), getPort());
+            requestWrapper = new Neo4jRequestWrapper(httpClient, httpHost, getUsername(), getPassword(),
+                    getServiceRoot());
+        }
+    }
+
+    @Test
+    public void testTableDetection() throws Exception {
+        if (!isConfigured()) {
+            System.err.println(getInvalidConfigurationMessage());
+            return;
+        }
+
+        // Insert a node
+        requestWrapper.executeCypherQuery("CREATE (n:JUnitLabel { property1: 1, property2: 2 })");
+
+        // Adding a node, but deleting it afterwards - should not be included in
+        // the schema as the table would be empty
+        requestWrapper.executeCypherQuery("CREATE (n:JUnitLabelTemp { property1: 3, property2: 4 })");
+        requestWrapper.executeCypherQuery("MATCH (n:JUnitLabelTemp) DELETE n;");
+
+        Neo4jDataContext strategy = new Neo4jDataContext(getHostname(), getPort(), getUsername(), getPassword());
+        Schema schema = strategy.getSchemaByName(strategy.getDefaultSchemaName());
+
+        // Do not check the precise count, Neo4j keeps labels forever, there are
+        // probably many more than you imagine...
+        List<String> tableNames = Arrays.asList(schema.getTableNames());
+        logger.info("Tables (labels) detected: " + tableNames);
+        assertTrue(tableNames.contains("JUnitLabel"));
+        assertFalse(tableNames.contains("JUnitLabelTemp"));
+
+        Table table = schema.getTableByName("JUnitLabel");
+        List<String> columnNames = Arrays.asList(table.getColumnNames());
+        assertTrue(columnNames.contains("property1"));
+        assertTrue(columnNames.contains("property2"));
+    }
+
+    @Test
+    public void testTableDetectionWithRelationships() throws Exception {
+        if (!isConfigured()) {
+            System.err.println(getInvalidConfigurationMessage());
+            return;
+        }
+
+        // Insert nodes
+        requestWrapper.executeCypherQuery("CREATE (n:JUnitPerson { name: 'Tomasz', age: 26})");
+        requestWrapper.executeCypherQuery("CREATE (n:JUnitPerson { name: 'Philomeena', age: 18})");
+        requestWrapper.executeCypherQuery("CREATE (n:JUnitBook { title: 'Introduction to algorithms'})");
+        requestWrapper.executeCypherQuery("MATCH (a:JUnitPerson),(b:JUnitBook)"
+                + "WHERE a.name = 'Tomasz' AND b.title = 'Introduction to algorithms'"
+                + "CREATE (a)-[r:HAS_READ { rating : 5 }]->(b)");
+        requestWrapper.executeCypherQuery("MATCH (a:JUnitPerson),(b:JUnitBook)"
+                + "WHERE a.name = 'Philomeena' AND b.title = 'Introduction to algorithms'"
+                + "CREATE (a)-[r:HAS_BROWSED]->(b)");
+
+        Neo4jDataContext strategy = new Neo4jDataContext(getHostname(), getPort(), getUsername(), getPassword());
+        Schema schema = strategy.getSchemaByName(strategy.getDefaultSchemaName());
+
+        // Do not check the precise count, Neo4j keeps labels forever, there are
+        // probably many more than you imagine...
+        List<String> tableNames = Arrays.asList(schema.getTableNames());
+        logger.info("Tables (labels) detected: " + tableNames);
+        assertTrue(tableNames.contains("JUnitPerson"));
+        assertTrue(tableNames.contains("JUnitBook"));
+
+        Table tablePerson = schema.getTableByName("JUnitPerson");
+        List<String> personColumnNames = Arrays.asList(tablePerson.getColumnNames());
+        assertEquals("[_id, name, age, rel_HAS_READ, rel_HAS_READ#rating, rel_HAS_BROWSED]",
+                personColumnNames.toString());
+
+        Table tableBook = schema.getTableByName("JUnitBook");
+        List<String> bookColumnNames = Arrays.asList(tableBook.getColumnNames());
+        assertEquals("[_id, title]", bookColumnNames.toString());
+    }
+
+    @Test
+    public void testSelectQueryWithProjection() throws Exception {
+        if (!isConfigured()) {
+            System.err.println(getInvalidConfigurationMessage());
+            return;
+        }
+
+        requestWrapper.executeCypherQuery("CREATE (n:JUnitLabel { property1: 1, property2: 2 })");
+
+        Neo4jDataContext strategy = new Neo4jDataContext(getHostname(), getPort(), getUsername(), getPassword());
+
+        {
+            CompiledQuery query = strategy.query().from("JUnitLabel").select("property1").compile();
+            try (final DataSet dataSet = strategy.executeQuery(query)) {
+                assertTrue(dataSet.next());
+                assertEquals("Row[values=[1]]", dataSet.getRow().toString());
+                assertFalse(dataSet.next());
+            }
+        }
+        {
+            CompiledQuery query = strategy.query().from("JUnitLabel").select("property1").select("property2").compile();
+            try (final DataSet dataSet = strategy.executeQuery(query)) {
+                assertTrue(dataSet.next());
+                assertEquals("Row[values=[1, 2]]", dataSet.getRow().toString());
+                assertFalse(dataSet.next());
+            }
+        }
+    }
+
+    public void ignoredTestSelectQueryWithLargeDataset() throws Exception {
+        if (!isConfigured()) {
+            System.err.println(getInvalidConfigurationMessage());
+            return;
+        }
+
+        int rowCount = 100000;
+
+        for (int j = 0; j < rowCount / 10000; j++) {
+            List<String> cypherQueries = new ArrayList<>();
+            for (int i = 0; i < 10000; i++) {
+                cypherQueries.add("CREATE (n:JUnitLabel { i: " + (j * 10000 + i) + "})");
+            }
+            requestWrapper.executeCypherQueries(cypherQueries);
+        }
+        System.out.println("Inserted " + rowCount + " rows to the database.");
+
+        Neo4jDataContext strategy = new Neo4jDataContext(getHostname(), getPort(), getUsername(), getPassword());
+
+        {
+            CompiledQuery query = strategy.query().from("JUnitLabel").select("i").orderBy("i").compile();
+            try (final DataSet dataSet = strategy.executeQuery(query)) {
+                for (int i = 0; i < rowCount; i++) {
+                    assertTrue(dataSet.next());
+                }
+                assertFalse(dataSet.next());
+            }
+        }
+    }
+
+    @Test
+    public void testSelectQueryWithProjectionAndRelationships() throws Exception {
+        if (!isConfigured()) {
+            System.err.println(getInvalidConfigurationMessage());
+            return;
+        }
+
+        // Insert nodes
+        requestWrapper.executeCypherQuery("CREATE (n:JUnitPerson { name: 'Tomasz', age: 26})");
+        requestWrapper.executeCypherQuery("CREATE (n:JUnitPerson { name: 'Philomeena', age: 18})");
+        requestWrapper.executeCypherQuery("CREATE (n:JUnitPerson { name: 'Helena', age: 100})");
+        requestWrapper.executeCypherQuery("CREATE (n:JUnitBook { title: 'Introduction to algorithms'})");
+        requestWrapper.executeCypherQuery("MATCH (a:JUnitPerson),(b:JUnitBook)"
+                + "WHERE a.name = 'Tomasz' AND b.title = 'Introduction to algorithms'"
+                + "CREATE (a)-[r:HAS_READ { rating : 5 }]->(b)");
+        requestWrapper.executeCypherQuery("MATCH (a:JUnitPerson),(b:JUnitBook)"
+                + "WHERE a.name = 'Philomeena' AND b.title = 'Introduction to algorithms'"
+                + "CREATE (a)-[r:HAS_BROWSED]->(b)");
+
+        String bookNodeIdJSONObject = requestWrapper.executeCypherQuery("MATCH (n:JUnitBook)"
+                + " WHERE n.title = 'Introduction to algorithms'" + " RETURN id(n);");
+        String bookNodeId = new JSONObject(bookNodeIdJSONObject).getJSONArray("results").getJSONObject(0)
+                .getJSONArray("data").getJSONObject(0).getJSONArray("row").getString(0);
+
+        Neo4jDataContext strategy = new Neo4jDataContext(getHostname(), getPort(), getUsername(), getPassword());
+
+        {
+            CompiledQuery query = strategy.query().from("JUnitPerson").select("name", "rel_HAS_READ").compile();
+            try (DataSet dataSet = strategy.executeQuery(query)) {
+                List<Row> rows = new ArrayList<>();
+                while (dataSet.next()) {
+                    rows.add(dataSet.getRow());
+                }
+                // Sorting to have deterministic order
+                Collections.sort(rows, new Comparator<Row>() {
+
+                    @Override
+                    public int compare(Row arg0, Row arg1) {
+                        return arg0.toString().compareTo(arg1.toString());
+                    }
+                });
+                assertEquals(3, rows.size());
+                assertEquals("Row[values=[Helena, null]]", rows.get(0).toString());
+                assertEquals("Row[values=[Philomeena, null]]", rows.get(1).toString());
+                assertEquals("Row[values=[Tomasz, " + bookNodeId + "]]", rows.get(2).toString());
+            }
+        }
+        {
+            CompiledQuery query = strategy.query().from("JUnitPerson").select("rel_HAS_READ#rating").compile();
+            try (DataSet dataSet = strategy.executeQuery(query)) {
+                List<Row> rows = new ArrayList<>();
+                while (dataSet.next()) {
+                    rows.add(dataSet.getRow());
+                }
+                // Sorting to have deterministic order
+                Collections.sort(rows, new Comparator<Row>() {
+
+                    @Override
+                    public int compare(Row arg0, Row arg1) {
+                        return arg0.toString().compareTo(arg1.toString());
+                    }
+                });
+
+                assertEquals(3, rows.size());
+                assertEquals("Row[values=[5]]", rows.get(0).toString());
+                assertEquals("Row[values=[null]]", rows.get(1).toString());
+                assertEquals("Row[values=[null]]", rows.get(2).toString());
+            }
+        }
+    }
+
+    @Test
+    public void testSelectAllQueryWithRelationships() throws Exception {
+        if (!isConfigured()) {
+            System.err.println(getInvalidConfigurationMessage());
+            return;
+        }
+
+        // Insert nodes
+        requestWrapper.executeCypherQuery("CREATE (n:JUnitPerson { name: 'Tomasz', age: 26})");
+        requestWrapper.executeCypherQuery("CREATE (n:JUnitPerson { name: 'Philomeena', age: 18})");
+        requestWrapper.executeCypherQuery("CREATE (n:JUnitPerson { name: 'Helena', age: 100})");
+        requestWrapper.executeCypherQuery("CREATE (n:JUnitBook { title: 'Introduction to algorithms'})");
+        requestWrapper.executeCypherQuery("MATCH (a:JUnitPerson),(b:JUnitBook)"
+                + "WHERE a.name = 'Tomasz' AND b.title = 'Introduction to algorithms'"
+                + "CREATE (a)-[r:HAS_READ { rating : 5 }]->(b)");
+        requestWrapper.executeCypherQuery("MATCH (a:JUnitPerson),(b:JUnitBook)"
+                + "WHERE a.name = 'Philomeena' AND b.title = 'Introduction to algorithms'"
+                + "CREATE (a)-[r:HAS_BROWSED]->(b)");
+
+        String bookNodeIdJSONObject = requestWrapper.executeCypherQuery("MATCH (n:JUnitBook)"
+                + " WHERE n.title = 'Introduction to algorithms'" + " RETURN id(n);");
+        String bookNodeId = new JSONObject(bookNodeIdJSONObject).getJSONArray("results").getJSONObject(0)
+                .getJSONArray("data").getJSONObject(0).getJSONArray("row").getString(0);
+
+        String helenaNodeIdJSONObject = requestWrapper.executeCypherQuery("MATCH (n:JUnitPerson)"
+                + " WHERE n.name = 'Helena'" + " RETURN id(n);");
+        String helenaNodeId = new JSONObject(helenaNodeIdJSONObject).getJSONArray("results").getJSONObject(0)
+                .getJSONArray("data").getJSONObject(0).getJSONArray("row").getString(0);
+
+        String tomaszNodeIdJSONObject = requestWrapper.executeCypherQuery("MATCH (n:JUnitPerson)"
+                + " WHERE n.name = 'Tomasz'" + " RETURN id(n);");
+        String tomaszNodeId = new JSONObject(tomaszNodeIdJSONObject).getJSONArray("results").getJSONObject(0)
+                .getJSONArray("data").getJSONObject(0).getJSONArray("row").getString(0);
+
+        String philomeenaNodeIdJSONObject = requestWrapper.executeCypherQuery("MATCH (n:JUnitPerson)"
+                + " WHERE n.name = 'Philomeena'" + " RETURN id(n);");
+        String philomeenaNodeId = new JSONObject(philomeenaNodeIdJSONObject).getJSONArray("results").getJSONObject(0)
+                .getJSONArray("data").getJSONObject(0).getJSONArray("row").getString(0);
+
+        Neo4jDataContext strategy = new Neo4jDataContext(getHostname(), getPort(), getUsername(), getPassword());
+
+        CompiledQuery query = strategy.query().from("JUnitPerson").selectAll().compile();
+        try (DataSet dataSet = strategy.executeQuery(query)) {
+            List<Row> rows = new ArrayList<>();
+            while (dataSet.next()) {
+                rows.add(dataSet.getRow());
+            }
+            // Sorting to have deterministic order
+            Collections.sort(rows, new Comparator<Row>() {
+
+                @Override
+                public int compare(Row arg0, Row arg1) {
+                    return arg0.getValue(1).toString().compareTo(arg1.getValue(1).toString());
+                }
+            });
+            assertEquals(3, rows.size());
+            assertEquals("Row[values=[" + helenaNodeId + ", Helena, 100, null, null, null]]", rows.get(0).toString());
+            assertEquals("Row[values=[" + philomeenaNodeId + ", Philomeena, 18, null, null, " + bookNodeId + "]]", rows
+                    .get(1).toString());
+            assertEquals("Row[values=[" + tomaszNodeId + ", Tomasz, 26, " + bookNodeId + ", 5, null]]", rows.get(2)
+                    .toString());
+        }
+    }
+
+    @Test
+    public void testWhereClause() throws Exception {
+        if (!isConfigured()) {
+            System.err.println(getInvalidConfigurationMessage());
+            return;
+        }
+
+        requestWrapper.executeCypherQuery("CREATE (n:JUnitLabel { property1: 1, property2: 2 })");
+        requestWrapper.executeCypherQuery("CREATE (n:JUnitLabel { property1: 10, property2: 20 })");
+
+        requestWrapper.executeCypherQuery("CREATE (n:JUnitPerson { name: 'Tomasz', age: 26})");
+        requestWrapper.executeCypherQuery("CREATE (n:JUnitPerson { name: 'Philomeena', age: 18})");
+        requestWrapper.executeCypherQuery("CREATE (n:JUnitPerson { name: 'Helena', age: 100})");
+        requestWrapper.executeCypherQuery("CREATE (n:JUnitBook { title: 'Introduction to algorithms'})");
+        requestWrapper.executeCypherQuery("MATCH (a:JUnitPerson),(b:JUnitBook)"
+                + "WHERE a.name = 'Tomasz' AND b.title = 'Introduction to algorithms'"
+                + "CREATE (a)-[r:HAS_READ { rating : 5 }]->(b)");
+        requestWrapper.executeCypherQuery("MATCH (a:JUnitPerson),(b:JUnitBook)"
+                + "WHERE a.name = 'Philomeena' AND b.title = 'Introduction to algorithms'"
+                + "CREATE (a)-[r:HAS_BROWSED]->(b)");
+
+        Neo4jDataContext strategy = new Neo4jDataContext(getHostname(), getPort(), getUsername(), getPassword());
+        {
+            CompiledQuery query = strategy.query().from("JUnitLabel").select("property1").where("property2").eq(20)
+                    .compile();
+            try (final DataSet dataSet = strategy.executeQuery(query)) {
+                assertTrue(dataSet.next());
+                assertEquals("Row[values=[10]]", dataSet.getRow().toString());
+                assertFalse(dataSet.next());
+            }
+        }
+        {
+            CompiledQuery query = strategy.query().from("JUnitPerson").select("rel_HAS_READ#rating").where("name")
+                    .eq("Tomasz").compile();
+            try (final DataSet dataSet = strategy.executeQuery(query)) {
+                assertTrue(dataSet.next());
+                assertEquals("Row[values=[5]]", dataSet.getRow().toString());
+                assertFalse(dataSet.next());
+            }
+        }
+        {
+            CompiledQuery query = strategy.query().from("JUnitPerson").select("rel_HAS_READ#rating").where("name")
+                    .eq("Philomeena").compile();
+            try (final DataSet dataSet = strategy.executeQuery(query)) {
+                assertTrue(dataSet.next());
+                assertEquals("Row[values=[null]]", dataSet.getRow().toString());
+                assertFalse(dataSet.next());
+            }
+        }
+    }
+
+    @Test
+    public void testJoin() throws Exception {
+        if (!isConfigured()) {
+            System.err.println(getInvalidConfigurationMessage());
+            return;
+        }
+
+        requestWrapper.executeCypherQuery("CREATE (n:JUnitLabel1 { id2: 1, propertyTable1: \"prop-table1-row1\" })");
+        requestWrapper.executeCypherQuery("CREATE (n:JUnitLabel1 { id2: 2, propertyTable1: \"prop-table1-row2\" })");
+        requestWrapper.executeCypherQuery("CREATE (n:JUnitLabel2 { id1: 2, propertyTable2: \"prop-table2-row2\" })");
+        requestWrapper.executeCypherQuery("CREATE (n:JUnitLabel2 { id1: 1, propertyTable2: \"prop-table2-row1\" })");
+
+        Neo4jDataContext strategy = new Neo4jDataContext(getHostname(), getPort(), getUsername(), getPassword());
+
+        Table table1 = strategy.getTableByQualifiedLabel("JUnitLabel1");
+        Table table2 = strategy.getTableByQualifiedLabel("JUnitLabel2");
+        Column id1Column = table2.getColumnByName("id1");
+        Column id2Column = table1.getColumnByName("id2");
+        Column propertyTable1Column = table1.getColumnByName("propertyTable1");
+        Column propertyTable2Column = table2.getColumnByName("propertyTable2");
+
+        CompiledQuery query = strategy.query().from(table1).and(table2)
+                .select(id1Column, id2Column, propertyTable1Column, propertyTable2Column).where(id1Column)
+                .eq(id2Column).compile();
+
+        try (final DataSet dataSet = strategy.executeQuery(query)) {
+            List<Row> rows = new ArrayList<>();
+            while (dataSet.next()) {
+                rows.add(dataSet.getRow());
+            }
+            Collections.sort(rows, new Comparator<Row>() {
+
+                @Override
+                public int compare(Row o1, Row o2) {
+                    return o1.toString().compareTo(o2.toString());
+                }
+            });
+            assertEquals(2, rows.size());
+            assertEquals("Row[values=[1, 1, prop-table1-row1, prop-table2-row1]]", rows.get(0).toString());
+            assertEquals("Row[values=[2, 2, prop-table1-row2, prop-table2-row2]]", rows.get(1).toString());
+        }
+    }
+
+    @Test
+    public void testJoinWithRelationships() throws Exception {
+        if (!isConfigured()) {
+            System.err.println(getInvalidConfigurationMessage());
+            return;
+        }
+
+        requestWrapper.executeCypherQuery("CREATE (n:JUnitPerson { name: 'Tomasz', age: 26})");
+        requestWrapper.executeCypherQuery("CREATE (n:JUnitPerson { name: 'Philomeena', age: 18})");
+        requestWrapper.executeCypherQuery("CREATE (n:JUnitPerson { name: 'Helena', age: 100})");
+        requestWrapper.executeCypherQuery("CREATE (n:JUnitBook { title: 'Introduction to algorithms'})");
+        requestWrapper.executeCypherQuery("MATCH (a:JUnitPerson),(b:JUnitBook)"
+                + "WHERE a.name = 'Tomasz' AND b.title = 'Introduction to algorithms'"
+                + "CREATE (a)-[r:HAS_READ { rating : 5 }]->(b)");
+        requestWrapper.executeCypherQuery("MATCH (a:JUnitPerson),(b:JUnitBook)"
+                + "WHERE a.name = 'Philomeena' AND b.title = 'Introduction to algorithms'"
+                + "CREATE (a)-[r:HAS_BROWSED]->(b)");
+
+        String bookNodeIdJSONObject = requestWrapper.executeCypherQuery("MATCH (n:JUnitBook)"
+                + " WHERE n.title = 'Introduction to algorithms'" + " RETURN id(n);");
+        String bookNodeId = new JSONObject(bookNodeIdJSONObject).getJSONArray("results").getJSONObject(0)
+                .getJSONArray("data").getJSONObject(0).getJSONArray("row").getString(0);
+
+        Neo4jDataContext strategy = new Neo4jDataContext(getHostname(), getPort(), getUsername(), getPassword());
+
+        Table table1 = strategy.getTableByQualifiedLabel("JUnitPerson");
+        Table table2 = strategy.getTableByQualifiedLabel("JUnitBook");
+        Column personNameColumn = table1.getColumnByName("name");
+        Column personHasReadColumn = table1.getColumnByName("rel_HAS_READ");
+        Column bookIdColumn = table2.getColumnByName("_id");
+        Column bookTitleColumn = table2.getColumnByName("title");
+
+        CompiledQuery query = strategy.query().from(table1).and(table2)
+                .select(personNameColumn, bookIdColumn, bookTitleColumn).where(personHasReadColumn).eq(bookIdColumn)
+                .compile();
+
+        try (final DataSet dataSet = strategy.executeQuery(query)) {
+            List<Row> rows = new ArrayList<>();
+            while (dataSet.next()) {
+                rows.add(dataSet.getRow());
+            }
+            assertEquals(1, rows.size());
+            assertEquals("Row[values=[Tomasz, " + bookNodeId + ", Introduction to algorithms]]", rows.get(0).toString());
+        }
+    }
+
+    @Test
+    public void testFirstRowAndLastRow() throws Exception {
+        if (!isConfigured()) {
+            System.err.println(getInvalidConfigurationMessage());
+            return;
+        }
+
+        // insert a few records
+        {
+            requestWrapper.executeCypherQuery("CREATE (n:JUnitLabel { name: 'John Doe', age: 30 })");
+            requestWrapper.executeCypherQuery("CREATE (n:JUnitLabel { name: 'Jane Doe', gender: 'F' })");
+        }
+
+        // create datacontext using detected schema
+        final DataContext dc = new Neo4jDataContext(getHostname(), getPort(), getUsername(), getPassword());
+
+        try (final DataSet ds = dc.query().from("JUnitLabel").select("name").and("age").firstRow(2).execute()) {
+            assertTrue("Class: " + ds.getClass().getName(), ds instanceof Neo4jDataSet);
+            assertTrue(ds.next());
+            final Row row = ds.getRow();
+            assertEquals("Row[values=[Jane Doe, null]]", row.toString());
+            assertFalse(ds.next());
+        }
+
+        try (final DataSet ds = dc.query().from("JUnitLabel").select("name").and("age").maxRows(1).execute()) {
+            assertTrue("Class: " + ds.getClass().getName(), ds instanceof Neo4jDataSet);
+            assertTrue(ds.next());
+            final Row row = ds.getRow();
+            assertEquals("Row[values=[John Doe, 30]]", row.toString());
+            assertFalse(ds.next());
+        }
+    }
+
+    @Test
+    public void testCountQuery() throws Exception {
+        if (!isConfigured()) {
+            System.err.println(getInvalidConfigurationMessage());
+            return;
+        }
+
+        // insert a few records
+        {
+            requestWrapper.executeCypherQuery("CREATE (n:JUnitLabel { name: 'John Doe', age: 30 })");
+            requestWrapper.executeCypherQuery("CREATE (n:JUnitLabel { name: 'Sofia Unknown', gender: 'F' })");
+        }
+
+        // create datacontext using detected schema
+        final DataContext dc = new Neo4jDataContext(getHostname(), getPort(), getUsername(), getPassword());
+
+        try (final DataSet ds = dc.query().from("JUnitLabel").selectCount().where("name").eq("John Doe").execute()) {
+            assertTrue(ds.next());
+            final Row row = ds.getRow();
+            assertFalse(ds.next());
+            assertEquals("Row[values=[1]]", row.toString());
+
+        }
+
+        try (final DataSet ds = dc.query().from("JUnitLabel").selectCount().execute()) {
+            assertTrue(ds.next());
+            final Row row = ds.getRow();
+            assertFalse(ds.next());
+            assertEquals("Row[values=[2]]", row.toString());
+        }
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        if (isConfigured()) {
+            // Delete the test nodes
+            requestWrapper.executeCypherQuery("MATCH (n:JUnitLabel) DELETE n");
+            requestWrapper.executeCypherQuery("MATCH (n:JUnitLabelTemp) DELETE n");
+            requestWrapper.executeCypherQuery("MATCH (n:JUnitLabel1) DELETE n");
+            requestWrapper.executeCypherQuery("MATCH (n:JUnitLabel2) DELETE n");
+            requestWrapper.executeCypherQuery("MATCH (n:JUnitPerson)-[r]-() DELETE n,r");
+            requestWrapper.executeCypherQuery("MATCH (n:JUnitPerson) DELETE n");
+            requestWrapper.executeCypherQuery("MATCH (n:JUnitBook) DELETE n");
+        }
+
+        super.tearDown();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/metamodel/blob/0ddd0cdf/neo4j/src/test/java/org/apache/metamodel/neo4j/Neo4jRequestWrapperTest.java
----------------------------------------------------------------------
diff --git a/neo4j/src/test/java/org/apache/metamodel/neo4j/Neo4jRequestWrapperTest.java b/neo4j/src/test/java/org/apache/metamodel/neo4j/Neo4jRequestWrapperTest.java
new file mode 100644
index 0000000..d7fa77f
--- /dev/null
+++ b/neo4j/src/test/java/org/apache/metamodel/neo4j/Neo4jRequestWrapperTest.java
@@ -0,0 +1,172 @@
+/**
+ * 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.metamodel.neo4j;
+
+import java.io.IOException;
+import java.io.StringBufferInputStream;
+import java.nio.charset.StandardCharsets;
+
+import org.apache.http.Header;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpVersion;
+import org.apache.http.ProtocolVersion;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.conn.ClientConnectionManager;
+import org.apache.http.entity.BasicHttpEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.message.BasicHttpResponse;
+import org.apache.http.params.HttpParams;
+import org.apache.http.protocol.HttpContext;
+import org.apache.http.util.EntityUtils;
+import org.junit.Test;
+
+import com.google.common.io.BaseEncoding;
+
+@SuppressWarnings("deprecation")
+public class Neo4jRequestWrapperTest extends Neo4jTestCase {
+
+    private class MockClosableHttpResponse extends BasicHttpResponse implements CloseableHttpResponse {
+
+        public MockClosableHttpResponse(ProtocolVersion ver, int code, String reason) {
+            super(ver, code, reason);
+        }
+
+        @Override
+        public HttpEntity getEntity() {
+            BasicHttpEntity basicHttpEntity = new BasicHttpEntity();
+            basicHttpEntity.setContent(new StringBufferInputStream("MockContent for BasicHttpEntity"));
+            return basicHttpEntity;
+        }
+
+        @Override
+        public void close() throws IOException {
+            // Do nothing
+        }
+
+    }
+
+    @Test
+    public void testCreateCypherQueryWithAuthentication() {
+        if (!isConfigured()) {
+            System.err.println(getInvalidConfigurationMessage());
+            return;
+        }
+
+        CloseableHttpClient mockHttpClient = new CloseableHttpClient() {
+
+            @Override
+            public void close() throws IOException {
+                // Do nothing
+            }
+
+            @Override
+            public HttpParams getParams() {
+                // Do nothing
+                return null;
+            }
+
+            @Override
+            public ClientConnectionManager getConnectionManager() {
+                // Do nothing
+                return null;
+            }
+
+            @Override
+            protected CloseableHttpResponse doExecute(HttpHost target, HttpRequest request, HttpContext context)
+                    throws IOException, ClientProtocolException {
+                assertTrue(request instanceof HttpPost);
+                HttpPost httpPost = (HttpPost) request;
+
+                Header[] headers = httpPost.getHeaders("Authorization");
+                assertNotNull(headers);
+                assertEquals(1, headers.length);
+                String base64Encoded = headers[0].getValue();
+                base64Encoded = base64Encoded.replace("Basic ", "");
+                String decoded = new String(BaseEncoding.base64().decode(base64Encoded), StandardCharsets.UTF_8);
+                assertEquals("testUsername:testPassword", decoded);
+
+                assertEquals("{\"statements\":[{\"statement\":\"MATCH (n) RETURN n;\"}]}",
+                        EntityUtils.toString(httpPost.getEntity()));
+
+                CloseableHttpResponse mockResponse = new MockClosableHttpResponse(HttpVersion.HTTP_1_1, 200, "OK");
+                return mockResponse;
+            }
+        };
+
+        Neo4jRequestWrapper wrapper = new Neo4jRequestWrapper(mockHttpClient, new HttpHost(getHostname(), getPort()),
+                "testUsername", "testPassword", getServiceRoot());
+        wrapper.executeCypherQuery("MATCH (n) RETURN n;");
+        // Assertions are in the HttpClient
+    }
+
+    @Test
+    public void testCreateCypherQueryWithoutAuthentication() {
+        if (!isConfigured()) {
+            System.err.println(getInvalidConfigurationMessage());
+            return;
+        }
+
+        CloseableHttpClient mockHttpClient = new CloseableHttpClient() {
+
+            @Override
+            public void close() throws IOException {
+                // Do nothing
+            }
+
+            @Override
+            public HttpParams getParams() {
+                // Do nothing
+                return null;
+            }
+
+            @Override
+            public ClientConnectionManager getConnectionManager() {
+                // Do nothing
+                return null;
+            }
+
+            @Override
+            protected CloseableHttpResponse doExecute(HttpHost target, HttpRequest request, HttpContext context)
+                    throws IOException, ClientProtocolException {
+                assertTrue(request instanceof HttpPost);
+                HttpPost httpPost = (HttpPost) request;
+
+                Header[] headers = httpPost.getHeaders("Authorization");
+                assertNotNull(headers);
+                assertEquals(0, headers.length);
+
+                assertEquals("{\"statements\":[{\"statement\":\"MATCH (n) RETURN n;\"}]}",
+                        EntityUtils.toString(httpPost.getEntity()));
+
+                CloseableHttpResponse mockResponse = new MockClosableHttpResponse(HttpVersion.HTTP_1_1, 200, "OK");
+                return mockResponse;
+            }
+        };
+
+        Neo4jRequestWrapper wrapper = new Neo4jRequestWrapper(mockHttpClient, new HttpHost(getHostname(), getPort()),
+                getServiceRoot());
+        wrapper.executeCypherQuery("MATCH (n) RETURN n;");
+        // Assertions are in the HttpClient
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/metamodel/blob/0ddd0cdf/neo4j/src/test/java/org/apache/metamodel/neo4j/Neo4jTestCase.java
----------------------------------------------------------------------
diff --git a/neo4j/src/test/java/org/apache/metamodel/neo4j/Neo4jTestCase.java b/neo4j/src/test/java/org/apache/metamodel/neo4j/Neo4jTestCase.java
new file mode 100644
index 0000000..e3353e4
--- /dev/null
+++ b/neo4j/src/test/java/org/apache/metamodel/neo4j/Neo4jTestCase.java
@@ -0,0 +1,117 @@
+/**
+ * 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.metamodel.neo4j;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.Properties;
+
+import junit.framework.TestCase;
+
+public abstract class Neo4jTestCase extends TestCase {
+
+    private String _hostname;
+    private int _port = Neo4jDataContext.DEFAULT_PORT;
+    private String _username;
+    private String _password;
+    private String _serviceRoot = "/db/data";
+    private boolean _configured;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        File file = new File(getPropertyFilePath());
+        if (file.exists()) {
+            loadPropertyFile(file);
+        } else {
+            // Continuous integration case
+            if (System.getenv("CONTINUOUS_INTEGRATION") != null) {
+                File travisFile = new File("../travis-metamodel-integrationtest-configuration.properties");
+                if (travisFile.exists()) {
+                    loadPropertyFile(travisFile);
+                } else {
+                    _configured = false;
+                }
+            } else {
+                _configured = false;
+            }
+        }
+    }
+
+    private String getPropertyFilePath() {
+        String userHome = System.getProperty("user.home");
+        return userHome + "/metamodel-integrationtest-configuration.properties";
+    }
+
+    protected String getInvalidConfigurationMessage() {
+        return "!!! WARN !!! Neo4j module ignored\n" + "Please configure Neo4j connection locally ("
+                + getPropertyFilePath() + "), to run integration tests";
+    }
+
+    private void loadPropertyFile(File file) throws IOException, FileNotFoundException {
+        Properties properties = new Properties();
+        properties.load(new FileReader(file));
+        _hostname = properties.getProperty("neo4j.hostname");
+        String portString = properties.getProperty("neo4j.port");
+        if (portString != null) {
+            _port = Integer.parseInt(portString);
+        }
+        _username = properties.getProperty("neo4j.username");
+        _password = properties.getProperty("neo4j.password");
+        String serviceRoot = properties.getProperty("neo4j.serviceroot");
+        if (serviceRoot != null) {
+            _serviceRoot = serviceRoot;
+        }
+
+        _configured = (_hostname != null && !_hostname.isEmpty());
+
+        if (_configured) {
+            System.out.println("Loaded Neo4j configuration. Hostname=" + _hostname + ", port=" + _port + ", username="
+                    + _username + ", _serviceRoot=" + _serviceRoot);
+        }
+    }
+
+    public boolean isConfigured() {
+        return _configured;
+    }
+
+    public String getHostname() {
+        return _hostname;
+    }
+
+    public int getPort() {
+        return _port;
+    }
+
+    public String getUsername() {
+        return _username;
+    }
+
+    public String getPassword() {
+        return _password;
+    }
+
+    public String getServiceRoot() {
+        return _serviceRoot;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/metamodel/blob/0ddd0cdf/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index ce1e4d6..be4763e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -17,7 +17,8 @@ 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">
+<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>
 	<properties>
 		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
@@ -27,7 +28,7 @@ under the License.
 		<junit.version>4.11</junit.version>
 		<guava.version>16.0.1</guava.version>
 		<hadoop.version>2.6.0</hadoop.version>
-		<jackson.version>2.4.6</jackson.version>
+		<jackson.version>2.6.3</jackson.version>
 		<easymock.version>3.2</easymock.version>
 		<httpcomponents.version>4.4.1</httpcomponents.version>
 		<checksum-maven-plugin.version>1.2</checksum-maven-plugin.version>
@@ -76,6 +77,7 @@ under the License.
 		<module>sugarcrm</module>
 		<module>full</module>
 		<module>spring</module>
+		<module>neo4j</module>
 	</modules>
 	<issueManagement>
 		<system>Jira</system>
@@ -273,14 +275,15 @@ under the License.
 									<excludes>
 										<exclude>commons-logging:commons-logging:compile</exclude>
 										<exclude>org.codehaus.jackson:compile</exclude>
-										
-										<!-- commons-beanutils-core is redundant when we already depend on commons-beanutils -->
+
+										<!-- commons-beanutils-core is redundant when we already depend 
+											on commons-beanutils -->
 										<exclude>commons-beanutils:commons-beanutils-core:*</exclude>
-										
+
 										<!-- stax-api is overlapping with xml-apis -->
 										<exclude>stax:stax-api:*</exclude>
 										<exclude>javax.xml.stream:stax-api</exclude>
-										
+
 										<!-- findbugs-annotations is overlapping with annotations -->
 										<exclude>com.github.stephenc.findbugs:findbugs-annotations:*</exclude>
 									</excludes>
@@ -354,7 +357,8 @@ under the License.
 					<artifactId>apache-rat-plugin</artifactId>
 					<configuration>
 						<licenses>
-							<license implementation="org.apache.rat.analysis.license.SimplePatternBasedLicense">
+							<license
+								implementation="org.apache.rat.analysis.license.SimplePatternBasedLicense">
 								<licenseFamilyCategory>ASL20</licenseFamilyCategory>
 								<licenseFamilyName>Apache Software License, 2.0</licenseFamilyName>
 								<notes>Single licensed ASL v2.0</notes>
@@ -388,6 +392,7 @@ under the License.
 							<exclude>**/tattletale-filters.properties</exclude>
 							<exclude>DEPENDENCIES</exclude>
 							<exclude>DISCLAIMER</exclude>
+							<exclude>neo4j-community-*/**</exclude>
 						</excludes>
 					</configuration>
 				</plugin>
@@ -512,6 +517,21 @@ under the License.
 				<version>${jackson.version}</version>
 			</dependency>
 			<dependency>
+				<groupId>com.fasterxml.jackson.datatype</groupId>
+				<artifactId>jackson-datatype-json-org</artifactId>
+				<version>${jackson.version}</version>
+				<exclusions>
+					<exclusion>
+						<groupId>org.codehaus.jackson</groupId>
+						<artifactId>jackson-core-asl</artifactId>
+					</exclusion>
+					<exclusion>
+						<groupId>org.codehaus.jackson</groupId>
+						<artifactId>jackson-mapper-asl</artifactId>
+					</exclusion>
+				</exclusions>
+			</dependency>
+			<dependency>
 				<groupId>hsqldb</groupId>
 				<artifactId>hsqldb</artifactId>
 				<version>1.8.0.10</version>

http://git-wip-us.apache.org/repos/asf/metamodel/blob/0ddd0cdf/travis-metamodel-integrationtest-configuration.properties
----------------------------------------------------------------------
diff --git a/travis-metamodel-integrationtest-configuration.properties b/travis-metamodel-integrationtest-configuration.properties
index 9b52f5d..f551fef 100644
--- a/travis-metamodel-integrationtest-configuration.properties
+++ b/travis-metamodel-integrationtest-configuration.properties
@@ -26,6 +26,13 @@ mongodb.collectionName=my_collection
 #hbase.zookeeper.hostname=localhost
 #hbase.zookeeper.port=2181
 
+# ------------------------
+# Neo4j module properties:
+# ------------------------
+
+neo4j.hostname=localhost
+neo4j.port=7474
+
 # -----------------------
 # JDBC module properties:
 # -----------------------