diff --git a/neo4j-jdbc-http/pom.xml b/neo4j-jdbc-http/pom.xml
index f4b2e17ff..25ec2420d 100644
--- a/neo4j-jdbc-http/pom.xml
+++ b/neo4j-jdbc-http/pom.xml
@@ -20,6 +20,22 @@
neo4j-jdbc-http
Http implementation for the Neo4j JDBC Driver
+
+
+
+ src/test/resources
+ false
+
+
+ src/test/resources
+
+ neo4j.version
+
+ true
+
+
+
+
@@ -44,6 +60,22 @@
opencsv
4.0
+
+
+ org.testcontainers
+ testcontainers
+ test
+
+
+ org.testcontainers
+ neo4j
+ test
+
+
+ org.neo4j.driver
+ neo4j-java-driver
+ test
+
diff --git a/neo4j-jdbc-http/src/main/java/org/neo4j/jdbc/http/driver/CypherExecutor.java b/neo4j-jdbc-http/src/main/java/org/neo4j/jdbc/http/driver/CypherExecutor.java
index bd692035f..4ea62d9cc 100644
--- a/neo4j-jdbc-http/src/main/java/org/neo4j/jdbc/http/driver/CypherExecutor.java
+++ b/neo4j-jdbc-http/src/main/java/org/neo4j/jdbc/http/driver/CypherExecutor.java
@@ -92,12 +92,17 @@ public class CypherExecutor {
*/
private String currentTransactionUrl;
+ /**
+ * Name of the database (default: neo4j)
+ */
+ private final String databaseName;
+
/**
* Jackson mapper object.
*/
private static final ObjectMapper mapper = new ObjectMapper();
- private static final String DB_DATA_TRANSACTION = "/db/data/transaction";
+ private static final String DB_DATA_TRANSACTION_TEMPLATE = "/db/%s/tx";
static {
mapper.configure(DeserializationFeature.USE_LONG_FOR_INTS, true);
@@ -158,7 +163,8 @@ public CypherExecutor(String host, Integer port, Boolean secure, Properties prop
this.http = builder.build();
// Create the url endpoint
- this.transactionUrl = createTransactionUrl(host, port, this.secure);
+ this.databaseName = String.valueOf(properties.getOrDefault("database", "neo4j"));
+ this.transactionUrl = createTransactionUrl(this.databaseName, host, port, this.secure);
// Setting autocommit
this.setAutoCommit(Boolean.valueOf(properties.getProperty("autoCommit", "true")));
@@ -182,13 +188,15 @@ public boolean isAuthenticationRequired(String host, Integer port, Boolean secur
}
}
- private String createTransactionUrl(String host, Integer port, Boolean secure) throws SQLException {
+ private String createTransactionUrl(String databaseName, String host, Integer port, Boolean secure) throws SQLException {
+ String transactionPath = transactionPath(databaseName);
try {
- if(secure)
- return new URL("https", host, port, DB_DATA_TRANSACTION).toString();
- else
- return new URL("http", host, port, DB_DATA_TRANSACTION).toString();
- } catch (MalformedURLException e) {
+ if (secure) {
+ return new URL("https", host, port, transactionPath).toString();
+ }
+ return new URL("http", host, port, transactionPath).toString();
+ }
+ catch (MalformedURLException e) {
throw new SQLException("Invalid server URL", e);
}
}
@@ -312,7 +320,8 @@ public String getServerVersion() {
String result = null;
// Prepare the headers query
- HttpGet request = new HttpGet(this.transactionUrl.replace(DB_DATA_TRANSACTION, "/db/data"));
+ HttpGet request = new HttpGet(this.transactionUrl
+ .replace(transactionPath(this.databaseName), "/db/data"));
// Adding default headers to the request
for (Header header : this.getDefaultHeaders()) {
@@ -377,6 +386,10 @@ public Integer getOpenTransactionId() {
return getTransactionId(this.currentTransactionUrl);
}
+ private String transactionPath(String databaseName) {
+ return String.format(DB_DATA_TRANSACTION_TEMPLATE, databaseName);
+ }
+
/**
* Give the default http client default header for Neo4j API.
*
diff --git a/neo4j-jdbc-http/src/test/java/org/neo4j/jdbc/http/driver/CypherExecutorContainerIT.java b/neo4j-jdbc-http/src/test/java/org/neo4j/jdbc/http/driver/CypherExecutorContainerIT.java
new file mode 100644
index 000000000..c133bef12
--- /dev/null
+++ b/neo4j-jdbc-http/src/test/java/org/neo4j/jdbc/http/driver/CypherExecutorContainerIT.java
@@ -0,0 +1,176 @@
+package org.neo4j.jdbc.http.driver;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.URI;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Properties;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+
+import org.junit.AfterClass;
+import org.junit.Assume;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.neo4j.driver.AuthTokens;
+import org.neo4j.driver.Driver;
+import org.neo4j.driver.GraphDatabase;
+import org.neo4j.driver.Session;
+import org.neo4j.driver.SessionConfig;
+import org.testcontainers.containers.Neo4jContainer;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.collection.IsEmptyCollection.empty;
+import static org.junit.Assert.assertThat;
+import static org.neo4j.driver.SessionConfig.forDatabase;
+
+public class CypherExecutorContainerIT {
+
+ private static final String EXTRA_DATABASE_NAME = "extra";
+
+ private static final String USERNAME = "neo4j";
+
+ private static final String PASSWORD = "password";
+
+ private static Neo4jContainer> neo4jContainer;
+
+ @BeforeClass
+ public static void startDockerImage() {
+ neo4jContainer = startEnterpriseDockerContainer(imageCoordinates(), USERNAME, PASSWORD);
+ Assume.assumeTrue("neo4j container should be not null", neo4jContainer != null);
+ Assume.assumeTrue("neo4j container should be up and running", neo4jContainer.isRunning());
+ }
+
+ @BeforeClass
+ public static void createExtraDatabase() throws Exception {
+ createDatabase(EXTRA_DATABASE_NAME);
+ }
+
+ @AfterClass
+ public static void dropExtraDatabase() throws Exception {
+ dropDatabase(EXTRA_DATABASE_NAME);
+ }
+
+ @Test
+ public void testDefaultDatabaseExecution() throws Exception {
+ String host = neo4jContainer.getContainerIpAddress();
+ Integer port = neo4jContainer.getMappedPort(7474);
+ CypherExecutor executor = new CypherExecutor(host, port, false, baseProperties());
+
+ Neo4jResponse neo4jResponse = executor
+ .executeQuery(new Neo4jStatement("CREATE (:InDefault)", new HashMap<>(0), false));
+
+ assertThat(neo4jResponse.getErrors(), empty());
+ assertThat(neo4jResponse.getCode(), equalTo(200));
+ doInSession(forDatabase("neo4j"), (session) -> {
+ long count = session
+ .readTransaction(tx -> tx.run("MATCH (n:InDefault) RETURN COUNT(n) AS count").single().get("count")
+ .asLong());
+ assertThat(count, equalTo(1L));
+ });
+ doInSession(forDatabase(EXTRA_DATABASE_NAME), (session) -> {
+ long count = session
+ .readTransaction(tx -> tx.run("MATCH (n:InDefault) RETURN COUNT(n) AS count").single().get("count")
+ .asLong());
+ assertThat(count, equalTo(0L));
+ });
+ }
+
+ @Test
+ public void testNonDefaultDatabaseExecution() throws Exception {
+ String host = neo4jContainer.getContainerIpAddress();
+ Integer port = neo4jContainer.getMappedPort(7474);
+ Properties properties = new Properties();
+ properties.putAll(baseProperties());
+ properties.setProperty("database", EXTRA_DATABASE_NAME);
+ CypherExecutor executor = new CypherExecutor(host, port, false, properties);
+
+ Neo4jResponse neo4jResponse = executor
+ .executeQuery(new Neo4jStatement("CREATE (:InNonDefault)", new HashMap<>(0), false));
+
+ assertThat(neo4jResponse.getErrors(), empty());
+ assertThat(neo4jResponse.getCode(), equalTo(200));
+ doInSession(forDatabase("neo4j"), (session) -> {
+ long count = session
+ .readTransaction(tx -> tx.run("MATCH (n:InNonDefault) RETURN COUNT(n) AS count").single().get("count")
+ .asLong());
+ assertThat(count, equalTo(0L));
+ });
+ doInSession(forDatabase(EXTRA_DATABASE_NAME), (session) -> {
+ long count = session
+ .readTransaction(tx -> tx.run("MATCH (n:InNonDefault) RETURN COUNT(n) AS count").single().get("count")
+ .asLong());
+ assertThat(count, equalTo(1L));
+ });
+ }
+
+ private static Neo4jContainer> startEnterpriseDockerContainer(String version, String username, String password) {
+ try {
+ Neo4jContainer> container = new Neo4jContainer<>(version)
+ .withEnv("NEO4J_AUTH", String.format("%s/%s", username, password))
+ .withEnv("NEO4J_ACCEPT_LICENSE_AGREEMENT", "yes");
+ container.start();
+ return container;
+ }
+ catch (Exception exception) {
+ exception.printStackTrace();
+ }
+ return null;
+ }
+
+ private static String imageCoordinates() {
+ return String.format("neo4j:%s-enterprise", projectNeo4jVersion());
+ }
+
+ private static String projectNeo4jVersion() {
+ String filteredClasspathResource = "/neo4j.version";
+ List lines = readLines(filteredClasspathResource);
+ int lineCount = lines.size();
+ if (lineCount != 1) {
+ throw new RuntimeException(String
+ .format("%s should have only 1 (filtered) line, found: %d", filteredClasspathResource, lineCount));
+ }
+ return lines.iterator().next();
+ }
+
+ private static List readLines(String classpathResource) {
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(CypherExecutorContainerIT.class
+ .getResourceAsStream(classpathResource)))) {
+
+ return reader.lines().collect(Collectors.toList());
+ }
+ catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static void createDatabase(String databaseName) throws Exception {
+ doInSession(forDatabase("system"), (Session session) -> session
+ .writeTransaction((tx) -> tx.run(String.format("CREATE DATABASE %s", databaseName))));
+ }
+
+ private static void dropDatabase(String databaseName) throws Exception {
+ doInSession(forDatabase("system"), (Session session) -> session
+ .writeTransaction((tx) -> tx.run(String.format("DROP DATABASE %s", databaseName))));
+ }
+
+ private static void doInSession(SessionConfig sessionConfig, Consumer sessionConsumer) throws Exception {
+ try (Driver driver = GraphDatabase
+ .driver(new URI(neo4jContainer.getBoltUrl()), AuthTokens.basic(USERNAME, PASSWORD));
+ Session session = driver.session(sessionConfig)) {
+
+ sessionConsumer.accept(session);
+ }
+ }
+
+ private static Properties baseProperties() {
+ Properties properties = new Properties();
+ properties.setProperty("userAgent", "Integration test");
+ properties.setProperty("user", USERNAME);
+ properties.setProperty("password", PASSWORD);
+ return properties;
+ }
+
+}
\ No newline at end of file
diff --git a/neo4j-jdbc-http/src/test/resources/neo4j.version b/neo4j-jdbc-http/src/test/resources/neo4j.version
new file mode 100644
index 000000000..42cf0caf9
--- /dev/null
+++ b/neo4j-jdbc-http/src/test/resources/neo4j.version
@@ -0,0 +1 @@
+${neo4j.version}
\ No newline at end of file