Repository: neo4j-contrib/neo4j-jdbc
Pull request #217 (2020-03-09 14:08:55)

4.0 rebase


Patch:
diff --git a/README.adoc b/README.adoc
index 0761663e..62886f9c 100644
--- a/README.adoc
+++ b/README.adoc
@@ -1,5 +1,5 @@
 == Overview: Neo4j JDBC Driver
-:neo4j-jdbc-version: 3.3.1
+:neo4j-jdbc-version: 4.0.0
 
 ifndef::env-docs[]
 image:https://travis-ci.org/neo4j-contrib/neo4j-jdbc.svg?branch=master["Build Status", link="https://travis-ci.org/neo4j-contrib/neo4j-jdbc"]
@@ -150,6 +150,25 @@ try (Connection con = DriverManager.getConnection("jdbc:neo4j:bolt://localhost",
 * Get a connection from `DriverManager`
 * Execute queries and transactions using the Cypher graph query language
 
+==== List of Supported Neo4j Configuration parameters
+
+|===
+|Property Name |Supported Values |Description
+
+|trust.strategy |TRUST_ALL_CERTIFICATES, TRUST_CUSTOM_CA_SIGNED_CERTIFICATES, TRUST_SYSTEM_CA_SIGNED_CERTIFICATES |The supported trusted strategies
+|trusted.certificate.file |File Path |The path of the certificate file
+|connection.acquisition.timeout |Any Long |The acquisition time
+|connection.liveness.check.timeout |Any Long |The liveness check timeout
+|connection.timeout |Any Long |The connection timeout
+|encryption |true/false |Activate the application encryption
+|leaked.sessions.logging |true/false |If log leaked session
+|max.connection.lifetime |Any Long |The connection lifetime
+|max.connection.poolsize |Any Int |The max pool size
+|max.transaction.retry.time |Any Long | The retry time for a transaction transient error
+|database |String |The database name, if not specified connects to the default instance
+|===
+
+
 === Flattening
 
 As most JDBC clients and tools don't support complex objects, the driver can flatten returned nodes and relationships by providing all their properties as individual columns with names like `u.name`,`r.since` if you just return a node `u` or relationship `r`.
diff --git a/neo4j-jdbc-bolt/pom.xml b/neo4j-jdbc-bolt/pom.xml
index 5538b345..ec95803b 100644
--- a/neo4j-jdbc-bolt/pom.xml
+++ b/neo4j-jdbc-bolt/pom.xml
@@ -55,5 +55,16 @@
             <artifactId>jmh-generator-annprocess</artifactId>
             <scope>test</scope>
         </dependency>
+
+        <dependency>
+            <groupId>org.testcontainers</groupId>
+            <artifactId>testcontainers</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.testcontainers</groupId>
+            <artifactId>neo4j</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 </project>
diff --git a/neo4j-jdbc-bolt/src/main/java/org/neo4j/jdbc/bolt/impl/BoltNeo4jConnectionImpl.java b/neo4j-jdbc-bolt/src/main/java/org/neo4j/jdbc/bolt/impl/BoltNeo4jConnectionImpl.java
index 24a0ddd5..dc31e9b2 100644
--- a/neo4j-jdbc-bolt/src/main/java/org/neo4j/jdbc/bolt/impl/BoltNeo4jConnectionImpl.java
+++ b/neo4j-jdbc-bolt/src/main/java/org/neo4j/jdbc/bolt/impl/BoltNeo4jConnectionImpl.java
@@ -121,7 +121,7 @@ public class BoltNeo4jConnectionImpl extends Neo4jConnectionImpl implements Bolt
 			} else {
 				bookmarks = null;
 			}
-			String database = this.getClientInfo(BoltNeo4jDriverImpl.DATABASE);
+			String database = (String) this.getProperties().get(BoltNeo4jDriverImpl.DATABASE);
 			final SessionConfig.Builder config = SessionConfig.builder()
 					.withBookmarks(bookmarks)
 					.withDefaultAccessMode(getReadOnly() ? AccessMode.READ : AccessMode.WRITE);
diff --git a/neo4j-jdbc-examples/pom.xml b/neo4j-jdbc-examples/pom.xml
index 8e6665a8..e3d973f5 100644
--- a/neo4j-jdbc-examples/pom.xml
+++ b/neo4j-jdbc-examples/pom.xml
@@ -37,6 +37,14 @@
 			<version>4.0.0</version>
 			<scope>runtime</scope>
 		</dependency>
+		<dependency>
+			<groupId>org.testcontainers</groupId>
+			<artifactId>testcontainers</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.testcontainers</groupId>
+			<artifactId>neo4j</artifactId>
+		</dependency>
     </dependencies>
 
 </project>
diff --git a/neo4j-jdbc/src/main/java/org/neo4j/jdbc/Neo4jDatabaseMetaData.java b/neo4j-jdbc/src/main/java/org/neo4j/jdbc/Neo4jDatabaseMetaData.java
index 2ce8cbd6..16cd7db3 100644
--- a/neo4j-jdbc/src/main/java/org/neo4j/jdbc/Neo4jDatabaseMetaData.java
+++ b/neo4j-jdbc/src/main/java/org/neo4j/jdbc/Neo4jDatabaseMetaData.java
@@ -770,9 +770,39 @@ public abstract class Neo4jDatabaseMetaData implements java.sql.DatabaseMetaData
 		return emptyResultSet();
 	}
 
-	@Override public ResultSet getIndexInfo(String catalog, String schema, String table, boolean unique, boolean approximate) throws SQLException {
+	private String getIndexesV3X(String table, boolean unique, boolean hasTable) {
+		List<String> filters = new ArrayList<>();
+		if (hasTable) {
+			filters.add("label = ?");
+		}
+		if (unique) {
+			filters.add("type = 'node_unique_property'");
+		}
+		StringBuilder queryBuilder = new StringBuilder();
+		queryBuilder.append("CALL db.indexes() YIELD description, label, properties, state, type\n");
+		if (!filters.isEmpty()) {
+			queryBuilder.append("WHERE " + String.join(" AND ", filters) + "\n");
+		}
+		queryBuilder.append("WITH description, label, properties, state, type\n");
+		queryBuilder.append("UNWIND range(0, size(properties) - 1) AS index\n");
+		queryBuilder.append("RETURN null AS TABLE_CAT,\n");
+		queryBuilder.append("null AS TABLE_SCHEM,\n");
+		queryBuilder.append("label AS TABLE_NAME,\n");
+		queryBuilder.append("type <> 'node_unique_property' AS NON_UNIQUE,\n");
+		queryBuilder.append("description AS INDEX_QUALIFIER,\n");
+		queryBuilder.append("description AS INDEX_NAME,\n");
+		queryBuilder.append(DatabaseMetaData.tableIndexOther + " AS TYPE,\n");
+		queryBuilder.append("index + 1 AS ORDINAL_POSITION,\n");
+		queryBuilder.append("properties[index] AS COLUMN_NAME,\n");
+		queryBuilder.append("null AS ASC_OR_DESC,\n");
+		queryBuilder.append("null AS CARDINALITY,\n");
+		queryBuilder.append("null AS PAGES,\n");
+		queryBuilder.append("null AS FILTER_CONDITION");
+		return queryBuilder.toString();
+	}
+
+	private String getIndexesV4X(String table, boolean unique, boolean hasTable) {
 		List<String> filters = new ArrayList<>();
-		boolean hasTable = table != null && !table.trim().isEmpty();
 		if (hasTable) {
 			filters.add("ANY(tokenName IN labelsOrTypes WHERE tokenName = ?)");
 		}
@@ -803,7 +833,15 @@ public abstract class Neo4jDatabaseMetaData implements java.sql.DatabaseMetaData
 		queryBuilder.append("null AS CARDINALITY,\n");
 		queryBuilder.append("null AS PAGES,\n");
 		queryBuilder.append("null AS FILTER_CONDITION");
-		PreparedStatement ps = this.connection.prepareStatement(queryBuilder.toString());
+		return queryBuilder.toString();
+	}
+
+	@Override
+	public ResultSet getIndexInfo(String catalog, String schema, String table, boolean unique, boolean approximate) throws SQLException {
+		boolean hasTable = table != null && !table.trim().isEmpty();
+		final String query = databaseVersion.startsWith("4") ?
+				getIndexesV4X(table, unique, hasTable) : getIndexesV3X(table, unique, hasTable);
+		PreparedStatement ps = this.connection.prepareStatement(query);
 		if (hasTable) {
 			ps.setString(1, table);
 		}
diff --git a/pom.xml b/pom.xml
index 758f49aa..73aba7af 100644
--- a/pom.xml
+++ b/pom.xml
@@ -85,6 +85,7 @@
 		<neo4j.version>4.0.0</neo4j.version>
 		<httpclient.version>4.5.3</httpclient.version>
 		<jackson.version>2.9.2</jackson.version>
+		<testcontainers.version>1.12.3</testcontainers.version>
 
 		<!-- Plugin version -->
 		<maven.plugin.compiler>3.6.1</maven.plugin.compiler>
@@ -184,6 +185,18 @@
 				<artifactId>jmh-generator-annprocess</artifactId>
 				<version>${jmh.version}</version>
 			</dependency>
+
+			<!-- Deps for Testcontainers -->
+			<dependency>
+				<groupId>org.testcontainers</groupId>
+				<artifactId>testcontainers</artifactId>
+				<version>${testcontainers.version}</version>
+			</dependency>
+			<dependency>
+				<groupId>org.testcontainers</groupId>
+				<artifactId>neo4j</artifactId>
+				<version>${testcontainers.version}</version>
+			</dependency>
 		</dependencies>
 	</dependencyManagement>
 


Test patch:
diff --git a/neo4j-jdbc-bolt/src/test/java/org/neo4j/TestcontainersCausalCluster.java b/neo4j-jdbc-bolt/src/test/java/org/neo4j/TestcontainersCausalCluster.java
new file mode 100644
index 00000000..cddb6959
--- /dev/null
+++ b/neo4j-jdbc-bolt/src/test/java/org/neo4j/TestcontainersCausalCluster.java
@@ -0,0 +1,218 @@
+package org.neo4j;
+
+import org.jetbrains.annotations.NotNull;
+import org.neo4j.driver.AuthTokens;
+import org.neo4j.driver.Driver;
+import org.neo4j.driver.GraphDatabase;
+import org.neo4j.driver.Session;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.Neo4jContainer;
+import org.testcontainers.containers.Network;
+import org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy;
+import org.testcontainers.containers.wait.strategy.WaitStrategy;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.time.Duration;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CountDownLatch;
+import java.util.function.Function;
+import java.util.function.IntFunction;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+
+import static java.util.stream.Collectors.joining;
+import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toMap;
+
+public class TestcontainersCausalCluster {
+    private static final Logger logger = LoggerFactory.getLogger(TestcontainersCausalCluster.class);
+    private static final int DEFAULT_BOLT_PORT = 7687;
+
+    public enum ClusterInstanceType {CORE, READ_REPLICA}
+
+    private static Stream<String> iterateMembers(int numOfMembers, ClusterInstanceType instanceType) {
+        final IntFunction<String> generateInstanceName = i -> String.format("neo4j-%s-%d", instanceType.toString(), i);
+
+        return IntStream.rangeClosed(1, numOfMembers).mapToObj(generateInstanceName);
+    }
+
+    public static TestcontainersCausalCluster create(int numberOfCoreMembers, int numberOfReadReplica, Duration timeout, Map<String, Object> neo4jConfig) {
+        if (numberOfCoreMembers < 3) {
+            throw new IllegalArgumentException("numberOfCoreMembers must be >= 3");
+        }
+        if (numberOfReadReplica < 0) {
+            throw new IllegalArgumentException("numberOfReadReplica must be >= 0");
+        }
+
+        // Setup a naming strategy and the initial discovery members
+        final String initialDiscoveryMembers = iterateMembers(numberOfCoreMembers, ClusterInstanceType.CORE)
+                .map(n -> String.format("%s:5000", n))
+                .collect(joining(","));
+
+        // Prepare one shared network for those containers
+        Network network = Network.newNetwork();
+
+        // Prepare proxys as sidecars
+        Map<String, GenericContainer> sidecars = createSidecars(numberOfCoreMembers, network, ClusterInstanceType.CORE);
+        sidecars.putAll(createSidecars(numberOfReadReplica, network, ClusterInstanceType.READ_REPLICA));
+
+        // Start the sidecars so that the exposed ports are available
+        sidecars.values().forEach(GenericContainer::start);
+
+        // Build the core/read_replica
+        List<Neo4jContainer> members = getClusterMembers(numberOfCoreMembers, ClusterInstanceType.CORE, sidecars, network, initialDiscoveryMembers, neo4jConfig, timeout);
+        members.addAll(getClusterMembers(numberOfReadReplica, ClusterInstanceType.READ_REPLICA, sidecars, network, initialDiscoveryMembers, neo4jConfig, timeout));
+
+        // Start all of them in parallel
+        final CountDownLatch latch = new CountDownLatch(numberOfCoreMembers);
+        members.forEach(instance -> CompletableFuture.runAsync(() -> {
+            instance.start();
+            latch.countDown();
+        }));
+
+        try {
+            latch.await();
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+
+        return new TestcontainersCausalCluster(members, sidecars.values().stream().collect(toList()));
+    }
+
+    @NotNull
+    private static List<Neo4jContainer> getClusterMembers(int numberOfCoreMembers,
+                                                                   ClusterInstanceType instanceType,
+                                                                   Map<String, GenericContainer> sidecars,
+                                                                   Network network,
+                                                                   String initialDiscoveryMembers,
+                                                                   Map<String, Object> neo4jConfig,
+                                                                   Duration timeout) {
+        // Currently needed as a whole new waiting strategy due to a bug in test containers
+        WaitStrategy waitForBolt = new LogMessageWaitStrategy()
+                .withRegEx(String.format(".*Bolt enabled on 0\\.0\\.0\\.0:%d\\.\n", DEFAULT_BOLT_PORT))
+                .withStartupTimeout(timeout);
+        Function<GenericContainer, String> getProxyUrl = instance ->
+                String.format("%s:%d", instance.getContainerIpAddress(), instance.getMappedPort(DEFAULT_BOLT_PORT));
+        return iterateMembers(numberOfCoreMembers, instanceType)
+                .map(name -> getNeo4jContainer(waitForBolt, network, initialDiscoveryMembers, sidecars, getProxyUrl, instanceType, neo4jConfig, name))
+                .collect(toList());
+    }
+
+    @NotNull
+    private static Map<String, GenericContainer> createSidecars(int numOfMembers, Network network, ClusterInstanceType instanceType) {
+        return iterateMembers(numOfMembers, instanceType)
+                .collect(toMap(
+                        Function.identity(),
+                        name -> new GenericContainer("alpine/socat")
+                                .withNetwork(network)
+                                .withLabel("memberType", instanceType.toString())
+                                // Expose the default bolt port on the sidecar
+                                .withExposedPorts(DEFAULT_BOLT_PORT)
+                                // And redirect that port to the corresponding Neo4j instance
+                                .withCommand(String
+                                        .format("tcp-listen:%d,fork,reuseaddr tcp-connect:%s:%1$d", DEFAULT_BOLT_PORT, name))
+                ));
+    }
+
+    private static Neo4jContainer getNeo4jContainer(WaitStrategy waitForBolt,
+                                                                      Network network,
+                                                                      String initialDiscoveryMembers,
+                                                                      Map<String, GenericContainer> sidecars,
+                                                                      Function<GenericContainer, String> getProxyUrl,
+                                                                      ClusterInstanceType instanceType,
+                                                                      Map<String, Object> neo4jConfig,
+                                                                      String name) {
+        Neo4jContainer container = (Neo4jContainer) new Neo4jContainer("neo4j:3.5-enterprise")
+                .withNeo4jConfig("dbms.mode", instanceType.toString())
+                .withNeo4jConfig("dbms.connectors.default_listen_address", "0.0.0.0")
+                .withNeo4jConfig("dbms.connectors.default_advertised_address", name)
+                .withNeo4jConfig("dbms.connector.bolt.advertised_address", getProxyUrl.apply(sidecars.get(name)))
+                .withNeo4jConfig("causal_clustering.initial_discovery_members", initialDiscoveryMembers)
+                .withAdminPassword("jdbc")
+                .withEnv("NEO4J_ACCEPT_LICENSE_AGREEMENT", "yes")
+                .withLabel("memberType", instanceType.toString())
+                .withNetwork(network)
+                .withNetworkAliases(name)
+//                .withLogConsumer(new Slf4jLogConsumer(logger))
+                .waitingFor(waitForBolt);
+        neo4jConfig.forEach((conf, value) -> container.withNeo4jConfig(conf, String.valueOf(value)));
+        return container;
+    }
+
+    private final List<Neo4jContainer> clusterMembers;
+    private final List<GenericContainer> sidecars;
+
+    private Driver driver;
+    private Session session;
+
+    public TestcontainersCausalCluster(List<Neo4jContainer> clusterMembers,
+                                       List<GenericContainer> sidecars) {
+        this.clusterMembers = clusterMembers;
+        this.sidecars = sidecars;
+        this.driver = GraphDatabase.driver(getURI(), AuthTokens.basic("neo4j", "jdbc"));
+        this.session = driver.session();
+    }
+
+    public Driver getDriver() {
+        return driver;
+    }
+
+    public Session getSession() {
+        return session;
+    }
+
+    public URI getURI() {
+        return this.sidecars.stream().findAny()
+                .map(instance -> String.format("bolt+routing://%s:%d", instance.getContainerIpAddress(),
+                        instance.getMappedPort(DEFAULT_BOLT_PORT)))
+                .map(uri -> {
+                    try {
+                        return new URI(uri);
+                    } catch (URISyntaxException e) {
+                        throw new RuntimeException(e);
+                    }
+                })
+                .orElseThrow(() -> new IllegalStateException("No sidecar as entrypoint into the cluster available."));
+    }
+
+    public URI getURIByType(ClusterInstanceType instanceType) {
+        return this.sidecars.stream()
+                .filter(instance -> instance.getLabels().getOrDefault("memberType", "").equals(instanceType.toString()))
+                .findAny()
+                .map(instance -> String.format("bolt+routing://%s:%d", instance.getContainerIpAddress(),
+                        instance.getMappedPort(DEFAULT_BOLT_PORT)))
+                .map(uri -> {
+                    try {
+                        return new URI(uri);
+                    } catch (URISyntaxException e) {
+                        throw new RuntimeException(e);
+                    }
+                })
+                .orElseThrow(() -> new IllegalStateException("No sidecar as entrypoint into the cluster available."));
+    }
+
+    public URI getAllMembersURI() {
+        try {
+            return new URI("bolt+routing://" + this.sidecars.stream()
+                    .map(instance -> String.format("%s:%d", instance.getContainerIpAddress(),
+                            instance.getMappedPort(DEFAULT_BOLT_PORT)))
+                    .collect(Collectors.joining(",")));
+        } catch (URISyntaxException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public void close() {
+        getSession().close();
+        getDriver().close();
+        sidecars.forEach(GenericContainer::stop);
+        clusterMembers.forEach(Neo4jContainer::stop);
+    }
+}
+
diff --git a/neo4j-jdbc-bolt/src/test/java/org/neo4j/jdbc/boltrouting/BoltRoutingPT.java b/neo4j-jdbc-bolt/src/test/java/org/neo4j/jdbc/boltrouting/BoltRoutingIT.java
similarity index 55%
rename from neo4j-jdbc-bolt/src/test/java/org/neo4j/jdbc/boltrouting/BoltRoutingPT.java
rename to neo4j-jdbc-bolt/src/test/java/org/neo4j/jdbc/boltrouting/BoltRoutingIT.java
index 74009e95..5622e974 100644
--- a/neo4j-jdbc-bolt/src/test/java/org/neo4j/jdbc/boltrouting/BoltRoutingPT.java
+++ b/neo4j-jdbc-bolt/src/test/java/org/neo4j/jdbc/boltrouting/BoltRoutingIT.java
@@ -20,22 +20,49 @@ package org.neo4j.jdbc.boltrouting;
 
 import org.junit.*;
 import org.junit.rules.ExpectedException;
+import org.neo4j.TestcontainersCausalCluster;
 
 import java.sql.*;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import static org.junit.Assume.assumeNotNull;
 
 /**
  * @author AgileLARUS
  * @since 3.3.1
  */
-public class BoltRoutingPT {
+@Ignore
+public class BoltRoutingIT {
+    private static final String NEO4J_USER = "neo4j";
+    private static final String NEO4J_PASSWORD = "jdbc";
+
+//    private String connectionUrl = "jdbc:neo4j:bolt+routing://localhost:17681?noSsl&debug=true&routing:policy=EU&routing:servers=localhost:17682;localhost:17683;localhost:17684;localhost:17685;localhost:17686;localhost:17687";
+//    private String connectionUrl2 = "jdbc:neo4j:bolt+routing://localhost:17681,localhost:17682,localhost:17683,localhost:17684,localhost:17685,localhost:17686,localhost:17687?noSsl&debug=true&routing:policy=EU";
+
+    private static TestcontainersCausalCluster cluster;
+
+    @BeforeClass
+    public static void beforeClass() {
+        try {
+            cluster = TestcontainersCausalCluster.create(3, 1, Duration.ofMinutes(4), Collections.emptyMap());
+            assumeNotNull(cluster);
+        } catch (Exception ignored) {}
+    }
 
-    private String connectionUrl = "jdbc:neo4j:neo4j://localhost:17681?noSsl&debug=true&routing:policy=EU&routing:servers=localhost:17682;localhost:17683;localhost:17684;localhost:17685;localhost:17686;localhost:17687";
-    private String connectionUrl2 = "jdbc:neo4j:neo4j://localhost:17681,localhost:17682,localhost:17683,localhost:17684,localhost:17685,localhost:17686,localhost:17687?noSsl&debug=true&routing:policy=EU";
+    @AfterClass
+    public static void afterClass() {
+        if (cluster != null) {
+            cluster.close();
+        }
+    }
 
     @Rule public ExpectedException expectedEx = ExpectedException.none();
 
     @After public void tearDown() throws Exception {
-        try  (Connection connection = DriverManager.getConnection(connectionUrl, "neo4j", "larus")) {
+        try  (Connection connection = DriverManager.getConnection("jdbc:neo4j:" + cluster.getURI().toString(), NEO4J_USER, NEO4J_PASSWORD)) {
             connection.setAutoCommit(false);
             try (Statement statement = connection.createStatement()) {
                 statement.execute("match (t) detach delete t");
@@ -47,7 +74,7 @@ public class BoltRoutingPT {
     //@Ignore
     @Test public void shouldAccessReadReplicaNodes() throws SQLException {
 
-        try  (Connection connection = DriverManager.getConnection(connectionUrl, "neo4j", "larus")) {
+        try  (Connection connection = DriverManager.getConnection("jdbc:neo4j:" + cluster.getURI().toString(), NEO4J_USER, NEO4J_PASSWORD)) {
             connection.setReadOnly(true);
             try (Statement statement = connection.createStatement()) {
                 try (ResultSet resultSet = statement.executeQuery("match (t:BoltRoutingTest) return count(t) as tot")) {
@@ -61,7 +88,7 @@ public class BoltRoutingPT {
 
     @Test public void shouldAccessReadReplicaNodes2() throws SQLException {
 
-        try  (Connection connection = DriverManager.getConnection(connectionUrl2, "neo4j", "larus")) {
+        try  (Connection connection = DriverManager.getConnection("jdbc:neo4j:" + cluster.getAllMembersURI().toString(), NEO4J_USER, NEO4J_PASSWORD)) {
             connection.setReadOnly(true);
             try (Statement statement = connection.createStatement()) {
                 try (ResultSet resultSet = statement.executeQuery("match (t:BoltRoutingTest) return count(t) as tot")) {
@@ -78,7 +105,7 @@ public class BoltRoutingPT {
 
         expectedEx.expect(SQLException.class);
 
-        try  (Connection connection = DriverManager.getConnection(connectionUrl, "neo4j", "larus")) {
+        try  (Connection connection = DriverManager.getConnection("jdbc:neo4j:" + cluster.getURI().toString(), NEO4J_USER, NEO4J_PASSWORD)) {
             connection.setReadOnly(true);
 
             try (Statement statement = connection.createStatement()) {
@@ -92,7 +119,7 @@ public class BoltRoutingPT {
     //@Ignore
     @Test public void shouldUseBookmarkToReadYourOwnWrites() throws SQLException {
 
-        try  (Connection connection = DriverManager.getConnection(connectionUrl, "neo4j", "larus")) {
+        try  (Connection connection = DriverManager.getConnection("jdbc:neo4j:" + cluster.getURI().toString(), NEO4J_USER, NEO4J_PASSWORD)) {
 
             connection.setAutoCommit(false);
 
@@ -117,4 +144,42 @@ public class BoltRoutingPT {
             connection.commit();
         }
     }
+
+    //@Ignore
+    @Test public void shouldPassTheBookMark() throws SQLException {
+        List<String> bookmarks = new ArrayList<>();
+        bookmarks.add(insertAndGetBookmark());
+        bookmarks.add(insertAndGetBookmark());
+
+        String bookmarksAsString = String.join(",", bookmarks);
+        try  (Connection connection = DriverManager.getConnection("jdbc:neo4j:" + cluster.getURI().toString(), NEO4J_USER, NEO4J_PASSWORD)) {
+
+            connection.setClientInfo(BoltRoutingNeo4jDriver.BOOKMARK, bookmarksAsString);
+            connection.setReadOnly(true);
+
+            try (Statement statement = connection.createStatement()) {
+                try (ResultSet resultSet = statement.executeQuery("match (t:BoltRoutingTest) return count(t) as tot")) {
+                    if (resultSet.next()) {
+                        long tot = resultSet.getLong("tot");
+                        Assert.assertEquals(2L, tot);
+                    }
+                }
+            }
+        }
+    }
+
+    private String insertAndGetBookmark() throws SQLException {
+        try (Connection connection = DriverManager.getConnection("jdbc:neo4j:" + cluster.getURI().toString(), NEO4J_USER, NEO4J_PASSWORD)) {
+            connection.setAutoCommit(false);
+
+            try (Statement statement = connection.createStatement()) {
+                statement.execute("create (:BoltRoutingTest { protocol: 'BOLT+ROUTING' })");
+            }
+            connection.commit();
+
+            final String bookmark = connection.getClientInfo(BoltRoutingNeo4jDriver.BOOKMARK);
+            Assert.assertNotNull(bookmark);
+            return bookmark;
+        }
+    }
 }
diff --git a/neo4j-jdbc-examples/mybatis/src/test/java/org/neo4j/jdbc/example/mybatis/ActorManagerContainerIT.java b/neo4j-jdbc-examples/mybatis/src/test/java/org/neo4j/jdbc/example/mybatis/ActorManagerContainerIT.java
new file mode 100644
index 00000000..ced95804
--- /dev/null
+++ b/neo4j-jdbc-examples/mybatis/src/test/java/org/neo4j/jdbc/example/mybatis/ActorManagerContainerIT.java
@@ -0,0 +1,101 @@
+/*
+ *
+ * Copyright (c) 2016 LARUS Business Automation [http://www.larus-ba.it]
+ * <p>
+ * This file is part of the "LARUS Integration Framework for Neo4j".
+ * <p>
+ * The "LARUS Integration Framework for Neo4j" is licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.
+ * <p>
+ * Created on 24/4/2016
+ *
+ */
+package org.neo4j.jdbc.example.mybatis;
+
+import org.apache.ibatis.datasource.unpooled.UnpooledDataSource;
+import org.apache.ibatis.mapping.Environment;
+import org.apache.ibatis.session.Configuration;
+import org.apache.ibatis.transaction.TransactionFactory;
+import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Ignore;
+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.neo4j.graphdb.Node;
+import org.neo4j.harness.junit.rule.Neo4jRule;
+import org.neo4j.jdbc.example.mybatis.bean.Actor;
+import org.neo4j.jdbc.example.mybatis.mapper.ActorMapper;
+import org.neo4j.jdbc.example.mybatis.util.ActorManager;
+import org.neo4j.jdbc.example.mybatis.util.ConnectionFactory;
+import org.testcontainers.containers.Neo4jContainer;
+
+import javax.sql.DataSource;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Properties;
+
+/**
+ * @author AgileLARUS
+ * @since 3.0.2
+ */
+public class ActorManagerContainerIT {
+
+	private void buildMybatisConfiguration(String uri,
+											 String user,
+											 String password) {
+		Properties prop = new Properties();
+		prop.setProperty("user", user);
+		prop.setProperty("password", password);
+		DataSource dataSource = new UnpooledDataSource("org.neo4j.jdbc.Driver", "jdbc:neo4j:" + uri, prop);
+		TransactionFactory transactionFactory = new JdbcTransactionFactory();
+		Environment environment = new Environment("development", transactionFactory, dataSource);
+
+		Configuration configuration = new Configuration(environment);
+		configuration.getMapperRegistry().addMapper(ActorMapper.class);
+		configuration.addLoadedResource("org/neo4j/jdbc/example/mybatis/mapper/ActorMapper.xml");
+
+		ConnectionFactory.getSqlSessionFactory(configuration);
+	}
+
+	@ClassRule
+	public static Neo4jContainer neo4jContainer = (Neo4jContainer) new Neo4jContainer("neo4j:4.0.0-enterprise")
+			.withEnv("NEO4J_AUTH", "neo4j/password")
+			.withEnv("NEO4J_ACCEPT_LICENSE_AGREEMENT", "yes");
+
+	@BeforeClass
+	public static void setUp() throws URISyntaxException {
+		try (final Driver driver = GraphDatabase.driver(new URI(neo4jContainer.getBoltUrl()), AuthTokens.basic("neo4j", neo4jContainer.getAdminPassword()))) {
+			final String foo = "foo";
+			try (final Session session = driver.session(SessionConfig.forDatabase("system"))) {
+				session.writeTransaction(tx -> tx.run("create database " + foo + " if not exists"));
+			}
+			try (final Session session = driver.session(SessionConfig.forDatabase(foo))) {
+				session.writeTransaction(tx -> tx.run("create (p:Person{name: 'Dave Chappelle', born: 1973})"));
+			}
+		}
+	}
+
+	@Test
+	public void testMybatisViaBolt() {
+		buildMybatisConfiguration(neo4jContainer.getBoltUrl() + "?database=foo&nossl", "neo4j", neo4jContainer.getAdminPassword());
+		Actor actor = ActorManager.selectActorByBorn(1973);
+		Assert.assertNotNull(actor);
+		Assert.assertEquals(1973, actor.getBorn());
+		Assert.assertEquals("Dave Chappelle", actor.getName());
+	}
+}
diff --git a/neo4j-jdbc-examples/mybatis/src/test/java/org/neo4j/jdbc/example/mybatis/ActorManagerIT.java b/neo4j-jdbc-examples/mybatis/src/test/java/org/neo4j/jdbc/example/mybatis/ActorManagerIT.java
index 6e77699e..135a590e 100644
--- a/neo4j-jdbc-examples/mybatis/src/test/java/org/neo4j/jdbc/example/mybatis/ActorManagerIT.java
+++ b/neo4j-jdbc-examples/mybatis/src/test/java/org/neo4j/jdbc/example/mybatis/ActorManagerIT.java
@@ -24,6 +24,7 @@ package org.neo4j.jdbc.example.mybatis;
 import org.junit.Assert;
 import org.junit.BeforeClass;
 import org.junit.ClassRule;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.neo4j.harness.junit.rule.Neo4jRule;
 import org.neo4j.jdbc.example.mybatis.bean.Actor;
@@ -33,6 +34,7 @@ import org.neo4j.jdbc.example.mybatis.util.ActorManager;
  * @author AgileLARUS
  * @since 3.0.2
  */
+@Ignore
 public class ActorManagerIT extends MybatisTestUtil {
 
 	@ClassRule