Repository: neo4j-contrib/neo4j-jdbc
Pull request #201 (2019-05-03 09:48:44)

Allow '&' and ',' in JDBC URL parameters
Fixes issue #200


Patch:
diff --git a/neo4j-jdbc-bolt/src/main/java/org/neo4j/jdbc/bolt/BoltNeo4jCallableStatement.java b/neo4j-jdbc-bolt/src/main/java/org/neo4j/jdbc/bolt/BoltNeo4jCallableStatement.java
index 3b96111d..df756d33 100644
--- a/neo4j-jdbc-bolt/src/main/java/org/neo4j/jdbc/bolt/BoltNeo4jCallableStatement.java
+++ b/neo4j-jdbc-bolt/src/main/java/org/neo4j/jdbc/bolt/BoltNeo4jCallableStatement.java
@@ -30,7 +30,7 @@ import java.sql.SQLException;
  * @author AgileLARUS
  * @since 3.0.0
  */
-class BoltNeo4jCallableStatement extends Neo4jCallableStatement {
+abstract class BoltNeo4jCallableStatement extends Neo4jCallableStatement { // TODO not used
 	/**
 	 * Default constructor with connection and statement.
 	 *
diff --git a/neo4j-jdbc-bolt/src/main/java/org/neo4j/jdbc/bolt/BoltNeo4jPreparedStatement.java b/neo4j-jdbc-bolt/src/main/java/org/neo4j/jdbc/bolt/BoltNeo4jPreparedStatement.java
index b8c79a91..1543fa4a 100644
--- a/neo4j-jdbc-bolt/src/main/java/org/neo4j/jdbc/bolt/BoltNeo4jPreparedStatement.java
+++ b/neo4j-jdbc-bolt/src/main/java/org/neo4j/jdbc/bolt/BoltNeo4jPreparedStatement.java
@@ -187,4 +187,10 @@ public class BoltNeo4jPreparedStatement extends Neo4jPreparedStatement implement
 	public void setTime(int parameterIndex, Time x, Calendar cal) throws SQLException {
 		setTemporal(parameterIndex, x.getTime(),cal.getTimeZone().toZoneId(), (zdt)-> zdt.toOffsetDateTime().toOffsetTime());
 	}
+
+	@Override
+	public void setArray(int parameterIndex, Array x) throws SQLException {
+		checkClosed();
+		insertParameter(parameterIndex, x.getArray());
+	}
 }
diff --git a/neo4j-jdbc-http/src/main/java/org/neo4j/jdbc/http/HttpNeo4jConnection.java b/neo4j-jdbc-http/src/main/java/org/neo4j/jdbc/http/HttpNeo4jConnection.java
index 3acd9804..9e1206b1 100644
--- a/neo4j-jdbc-http/src/main/java/org/neo4j/jdbc/http/HttpNeo4jConnection.java
+++ b/neo4j-jdbc-http/src/main/java/org/neo4j/jdbc/http/HttpNeo4jConnection.java
@@ -28,6 +28,7 @@ import org.neo4j.jdbc.utils.ExceptionBuilder;
 import org.neo4j.jdbc.utils.Neo4jJdbcRuntimeException;
 import org.neo4j.jdbc.utils.TimeLimitedCodeBlock;
 
+import java.sql.CallableStatement;
 import java.sql.PreparedStatement;
 import java.sql.SQLException;
 import java.util.ArrayList;
@@ -129,18 +130,6 @@ public class HttpNeo4jConnection extends Neo4jConnectionImpl implements Loggable
 		executor.rollback();
 	}
 
-	/*-------------------------*/
-	/*       Holdability       */
-	/*-------------------------*/
-
-	@Override public void setHoldability(int holdability) throws SQLException {
-		throw ExceptionBuilder.buildUnsupportedOperationException();
-	}
-
-	@Override public int getHoldability() throws SQLException {
-		throw ExceptionBuilder.buildUnsupportedOperationException();
-	}
-
 	/*------------------------------*/
 	/*       Create Statement       */
 	/*------------------------------*/
diff --git a/neo4j-jdbc-http/src/main/java/org/neo4j/jdbc/http/HttpNeo4jPreparedStatement.java b/neo4j-jdbc-http/src/main/java/org/neo4j/jdbc/http/HttpNeo4jPreparedStatement.java
index 221bb5de..e4a60314 100644
--- a/neo4j-jdbc-http/src/main/java/org/neo4j/jdbc/http/HttpNeo4jPreparedStatement.java
+++ b/neo4j-jdbc-http/src/main/java/org/neo4j/jdbc/http/HttpNeo4jPreparedStatement.java
@@ -22,11 +22,15 @@ package org.neo4j.jdbc.http;
 import org.neo4j.jdbc.*;
 import org.neo4j.jdbc.http.driver.Neo4jResponse;
 
-import java.sql.BatchUpdateException;
-import java.sql.ResultSet;
-import java.sql.SQLException;
+import java.sql.*;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.time.temporal.Temporal;
 import java.util.ArrayList;
+import java.util.Calendar;
 import java.util.List;
+import java.util.function.Function;
 
 public class HttpNeo4jPreparedStatement extends Neo4jPreparedStatement implements Loggable {
 
@@ -125,4 +129,10 @@ public class HttpNeo4jPreparedStatement extends Neo4jPreparedStatement implement
 
 		return result;
 	}
+
+	@Override
+	public void setArray(int parameterIndex, Array x) throws SQLException {
+		checkClosed();
+		insertParameter(parameterIndex, x.getArray());
+	}
 }
diff --git a/neo4j-jdbc/src/main/java/org/neo4j/jdbc/Neo4jDataSource.java b/neo4j-jdbc/src/main/java/org/neo4j/jdbc/Neo4jDataSource.java
index 7cfc44ed..e24f70e4 100644
--- a/neo4j-jdbc/src/main/java/org/neo4j/jdbc/Neo4jDataSource.java
+++ b/neo4j-jdbc/src/main/java/org/neo4j/jdbc/Neo4jDataSource.java
@@ -20,8 +20,11 @@
 package org.neo4j.jdbc;
 
 import org.neo4j.jdbc.utils.ExceptionBuilder;
+import org.neo4j.jdbc.utils.Neo4jJdbcRuntimeException;
 
 import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
 import java.sql.SQLException;
 import java.sql.SQLFeatureNotSupportedException;
 import java.util.logging.Logger;
@@ -122,7 +125,8 @@ public abstract class Neo4jDataSource implements javax.sql.DataSource {
 		if (null != getUser()) {
 			url += "user=" + getUser();
 			if (null != getPassword()) {
-				url += ",password=" + getPassword();
+				// Passwords may contain URL unsafe characters.
+				url += ",password=" + encodeUrlComponent(getPassword());
 			}
 		}
 		return url;
@@ -135,4 +139,13 @@ public abstract class Neo4jDataSource implements javax.sql.DataSource {
 	public void setIsSsl(boolean ssl) {
 		this.isSsl = ssl;
 	}
+
+	private String encodeUrlComponent(String urlProps) {
+		try {
+			return URLEncoder.encode(urlProps, "UTF-8");
+		} catch (UnsupportedEncodingException e) {
+			throw new Neo4jJdbcRuntimeException(e);
+		}
+	}
+
 }
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 395b6533..9e6aa9f4 100644
--- a/neo4j-jdbc/src/main/java/org/neo4j/jdbc/Neo4jDatabaseMetaData.java
+++ b/neo4j-jdbc/src/main/java/org/neo4j/jdbc/Neo4jDatabaseMetaData.java
@@ -642,7 +642,7 @@ public abstract class Neo4jDatabaseMetaData implements java.sql.DatabaseMetaData
 	}
 
 	@Override public int getMaxColumnsInGroupBy() throws SQLException {
-		throw ExceptionBuilder.buildUnsupportedOperationException();
+		return 0;
 	}
 
 	@Override public int getMaxColumnsInIndex() throws SQLException {
diff --git a/neo4j-jdbc/src/main/java/org/neo4j/jdbc/Neo4jDriver.java b/neo4j-jdbc/src/main/java/org/neo4j/jdbc/Neo4jDriver.java
index ab0601f2..99d3b90f 100644
--- a/neo4j-jdbc/src/main/java/org/neo4j/jdbc/Neo4jDriver.java
+++ b/neo4j-jdbc/src/main/java/org/neo4j/jdbc/Neo4jDriver.java
@@ -115,9 +115,9 @@ public abstract class Neo4jDriver implements java.sql.Driver {
 		}
 		if (url.contains("?")) {
 			String urlProps = url.substring(url.indexOf('?') + 1);
-			urlProps = decodeUrlComponent(urlProps);
 			String[] props = urlProps.split("[,&]");
 			for (String prop : props) {
+				prop = decodeUrlComponent(prop);
 				int idx1 = prop.indexOf('=');
 				int idx2 = prop.indexOf(':');
 				int idx = (idx1 != -1 && idx2 != -1) ? Math.min(idx1, idx2) : Math.max(idx1, idx2);
diff --git a/neo4j-jdbc/src/main/java/org/neo4j/jdbc/Neo4jParameterMetaData.java b/neo4j-jdbc/src/main/java/org/neo4j/jdbc/Neo4jParameterMetaData.java
index d168f5c8..d9282c40 100644
--- a/neo4j-jdbc/src/main/java/org/neo4j/jdbc/Neo4jParameterMetaData.java
+++ b/neo4j-jdbc/src/main/java/org/neo4j/jdbc/Neo4jParameterMetaData.java
@@ -29,24 +29,29 @@ import java.sql.SQLException;
  */
 public abstract class Neo4jParameterMetaData implements java.sql.ParameterMetaData {
 
-	@Override public int getParameterCount() throws SQLException {
+	@Override
+	public int getParameterCount() throws SQLException {
 		throw ExceptionBuilder.buildUnsupportedOperationException();
 	}
 
-	@Override public int isNullable(int param) throws SQLException {
-		throw ExceptionBuilder.buildUnsupportedOperationException();
+	@Override
+	public int isNullable(int param) throws SQLException {
+		return parameterNullable;
 	}
 
-	@Override public boolean isSigned(int param) throws SQLException {
-		throw ExceptionBuilder.buildUnsupportedOperationException();
+	@Override
+	public boolean isSigned(int param) throws SQLException {
+		return false;
 	}
 
-	@Override public int getPrecision(int param) throws SQLException {
-		throw ExceptionBuilder.buildUnsupportedOperationException();
+	@Override
+	public int getPrecision(int column) throws SQLException {
+		return 0;
 	}
 
-	@Override public int getScale(int param) throws SQLException {
-		throw ExceptionBuilder.buildUnsupportedOperationException();
+	@Override
+	public int getScale(int column) throws SQLException {
+		return 0;
 	}
 
 	@Override public int getParameterType(int param) throws SQLException {
@@ -57,19 +62,23 @@ public abstract class Neo4jParameterMetaData implements java.sql.ParameterMetaDa
 		throw ExceptionBuilder.buildUnsupportedOperationException();
 	}
 
-	@Override public String getParameterClassName(int param) throws SQLException {
+	@Override
+	public String getParameterClassName(int param) throws SQLException {
 		throw ExceptionBuilder.buildUnsupportedOperationException();
 	}
 
-	@Override public int getParameterMode(int param) throws SQLException {
-		throw ExceptionBuilder.buildUnsupportedOperationException();
+	@Override
+	public int getParameterMode(int param) throws SQLException {
+		return parameterModeUnknown;
 	}
 
-	@Override public <T> T unwrap(Class<T> iface) throws SQLException {
+	@Override
+	public <T> T unwrap(Class<T> iface) throws SQLException {
 		return Wrapper.unwrap(iface, this);
 	}
 
-	@Override public boolean isWrapperFor(Class<?> iface) throws SQLException {
+	@Override
+	public boolean isWrapperFor(Class<?> iface) throws SQLException {
 		return Wrapper.isWrapperFor(iface, this.getClass());
 	}
 }
diff --git a/neo4j-jdbc/src/main/java/org/neo4j/jdbc/Neo4jPreparedStatement.java b/neo4j-jdbc/src/main/java/org/neo4j/jdbc/Neo4jPreparedStatement.java
index ecef5061..b9691cd8 100644
--- a/neo4j-jdbc/src/main/java/org/neo4j/jdbc/Neo4jPreparedStatement.java
+++ b/neo4j-jdbc/src/main/java/org/neo4j/jdbc/Neo4jPreparedStatement.java
@@ -310,10 +310,6 @@ public abstract class Neo4jPreparedStatement extends Neo4jStatement implements P
 		throw ExceptionBuilder.buildUnsupportedOperationException();
 	}
 
-	@Override public void setArray(int parameterIndex, java.sql.Array x) throws SQLException {
-		throw ExceptionBuilder.buildUnsupportedOperationException();
-	}
-
 	@Override public void setDate(int parameterIndex, Date x, Calendar cal) throws SQLException {
 		throw ExceptionBuilder.buildUnsupportedOperationException();
 	}
@@ -327,7 +323,7 @@ public abstract class Neo4jPreparedStatement extends Neo4jStatement implements P
 	}
 
 	@Override public void setNull(int parameterIndex, int sqlType, String typeName) throws SQLException {
-		throw ExceptionBuilder.buildUnsupportedOperationException();
+		setNull(parameterIndex, sqlType); // simply store a null
 	}
 
 	@Override public void setURL(int parameterIndex, URL x) throws SQLException {
@@ -410,8 +406,4 @@ public abstract class Neo4jPreparedStatement extends Neo4jStatement implements P
 		throw ExceptionBuilder.buildUnsupportedOperationException();
 	}
 
-	@Override public void addBatch(String sql) throws SQLException {
-		throw new SQLException("Method addBatch(String sql) cannot be called on PreparedStatement");
-	}
-
 }
diff --git a/neo4j-jdbc/src/main/java/org/neo4j/jdbc/Neo4jResultSetMetaData.java b/neo4j-jdbc/src/main/java/org/neo4j/jdbc/Neo4jResultSetMetaData.java
index 7ac0e29d..56ce70ba 100644
--- a/neo4j-jdbc/src/main/java/org/neo4j/jdbc/Neo4jResultSetMetaData.java
+++ b/neo4j-jdbc/src/main/java/org/neo4j/jdbc/Neo4jResultSetMetaData.java
@@ -19,8 +19,6 @@
  */
 package org.neo4j.jdbc;
 
-import org.neo4j.jdbc.utils.ExceptionBuilder;
-
 import java.sql.SQLException;
 import java.sql.Types;
 import java.util.ArrayList;
@@ -183,11 +181,11 @@ public abstract class Neo4jResultSetMetaData implements java.sql.ResultSetMetaDa
 	/*---------------------------------*/
 
 	@Override public boolean isWritable(int column) throws SQLException {
-		throw ExceptionBuilder.buildUnsupportedOperationException();
+		return false;
 	}
 
 	@Override public boolean isDefinitelyWritable(int column) throws SQLException {
-		throw ExceptionBuilder.buildUnsupportedOperationException();
+		return false;
 	}
 
 }
diff --git a/neo4j-jdbc/src/main/java/org/neo4j/jdbc/Neo4jStatement.java b/neo4j-jdbc/src/main/java/org/neo4j/jdbc/Neo4jStatement.java
index 7518867c..2ae31116 100644
--- a/neo4j-jdbc/src/main/java/org/neo4j/jdbc/Neo4jStatement.java
+++ b/neo4j-jdbc/src/main/java/org/neo4j/jdbc/Neo4jStatement.java
@@ -19,12 +19,15 @@
  */
 package org.neo4j.jdbc;
 
+import org.neo4j.jdbc.impl.ListNeo4jResultSet;
 import org.neo4j.jdbc.utils.ExceptionBuilder;
 
+import javax.xml.transform.Result;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.SQLWarning;
 import java.sql.Statement;
+import java.util.Collections;
 import java.util.List;
 
 /**
@@ -271,63 +274,50 @@ public abstract class Neo4jStatement implements Statement, Loggable {
 	}
 
 	@Override public int getMaxFieldSize() throws SQLException {
-		throw ExceptionBuilder.buildUnsupportedOperationException();
+		return 0;
 	}
 
-	@Override public void setMaxFieldSize(int max) throws SQLException {
-		throw ExceptionBuilder.buildUnsupportedOperationException();
-	}
+	@Override public void setMaxFieldSize(int max) throws SQLException {} // do nothing
 
-	@Override public void setEscapeProcessing(boolean enable) throws SQLException {
-		throw ExceptionBuilder.buildUnsupportedOperationException();
-	}
+	@Override public void setEscapeProcessing(boolean enable) throws SQLException {} // do nothing
 
-	@Override public void setCursorName(String name) throws SQLException {
-		throw ExceptionBuilder.buildUnsupportedOperationException();
-	}
+	@Override public void setCursorName(String name) throws SQLException {} // do nothing
 
-	@Override public void setFetchDirection(int direction) throws SQLException {
-		throw ExceptionBuilder.buildUnsupportedOperationException();
-	}
+	@Override public void setFetchDirection(int direction) throws SQLException {}
 
 	@Override public int getFetchDirection() throws SQLException {
-		throw ExceptionBuilder.buildUnsupportedOperationException();
+		return ResultSet.FETCH_FORWARD;
 	}
 
 	@Override public int getFetchSize() throws SQLException {
-		throw ExceptionBuilder.buildUnsupportedOperationException();
+		return 0;
 	}
 
 	@Override public boolean getMoreResults(int current) throws SQLException {
-		throw ExceptionBuilder.buildUnsupportedOperationException();
+		return getMoreResults();
 	}
 
 	@Override public ResultSet getGeneratedKeys() throws SQLException {
-		throw ExceptionBuilder.buildUnsupportedOperationException();
+		// TODO should at least return the <id>?
+		return ListNeo4jResultSet.newInstance(false, Collections.<List<Object>>emptyList(), Collections.<String>emptyList());
 	}
 
-	@Override public void cancel() throws SQLException {
-		throw ExceptionBuilder.buildUnsupportedOperationException();
-	}
+	@Override public void cancel() throws SQLException {} // do nothing
 
-	@Override public void setPoolable(boolean poolable) throws SQLException {
-		throw ExceptionBuilder.buildUnsupportedOperationException();
-	}
+	@Override public void setPoolable(boolean poolable) throws SQLException {} // do nothing
 
 	@Override public boolean isPoolable() throws SQLException {
-		throw ExceptionBuilder.buildUnsupportedOperationException();
-	}
-
-	@Override public int[] executeBatch() throws SQLException {
-		throw ExceptionBuilder.buildUnsupportedOperationException();
+		return false;
 	}
 
 	@Override public void closeOnCompletion() throws SQLException {
-		throw ExceptionBuilder.buildUnsupportedOperationException();
+		this.checkClosed();
+		// do nothing
 	}
 
 	@Override public boolean isCloseOnCompletion() throws SQLException {
-		throw ExceptionBuilder.buildUnsupportedOperationException();
+		this.checkClosed();
+		return false;
 	}
 
 	@Override public boolean hasDebug() {
diff --git a/neo4j-jdbc/src/main/java/org/neo4j/jdbc/impl/ListArray.java b/neo4j-jdbc/src/main/java/org/neo4j/jdbc/impl/ListArray.java
index f0250ec9..1d4fc30b 100644
--- a/neo4j-jdbc/src/main/java/org/neo4j/jdbc/impl/ListArray.java
+++ b/neo4j-jdbc/src/main/java/org/neo4j/jdbc/impl/ListArray.java
@@ -23,8 +23,12 @@ import org.neo4j.jdbc.Neo4jArray;
 
 import java.sql.SQLException;
 import java.sql.Types;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Objects;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 /**
  * @author AgileLARUS
@@ -42,6 +46,31 @@ public class ListArray extends Neo4jArray {
 		this.type = type;
 	}
 
+	public ListArray(String typeName, Object[] elements) throws SQLException {
+		int type;
+		switch (typeName) {
+			case "VARCHAR":
+				type = Types.VARCHAR;
+				break;
+			case "INTEGER":
+				type = Types.INTEGER;
+				break;
+			case "BOOLEAN":
+				type = Types.BOOLEAN;
+				break;
+			case "DOUBLE":
+				type = Types.DOUBLE;
+				break;
+			case "JAVA_OBJECT":
+				type = Types.JAVA_OBJECT;
+				break;
+			default:
+				throw new SQLException(String.format(TYPE_NOT_SUPPORTED, this.type));
+		}
+		this.list = Arrays.asList(elements);
+		this.type = type;
+	}
+
 	@Override public String getBaseTypeName() throws SQLException {
 		String name;
 
diff --git a/neo4j-jdbc/src/main/java/org/neo4j/jdbc/impl/Neo4jConnectionImpl.java b/neo4j-jdbc/src/main/java/org/neo4j/jdbc/impl/Neo4jConnectionImpl.java
index 7b19fdf2..f93b1dbc 100644
--- a/neo4j-jdbc/src/main/java/org/neo4j/jdbc/impl/Neo4jConnectionImpl.java
+++ b/neo4j-jdbc/src/main/java/org/neo4j/jdbc/impl/Neo4jConnectionImpl.java
@@ -26,6 +26,7 @@ import org.neo4j.jdbc.utils.ExceptionBuilder;
 
 import java.sql.*;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.Map;
 import java.util.Properties;
 import java.util.concurrent.Executor;
@@ -383,12 +384,10 @@ public abstract class Neo4jConnectionImpl implements Neo4jConnection {
 	}
 
 	@Override public Map<String, Class<?>> getTypeMap() throws SQLException {
-		throw ExceptionBuilder.buildUnsupportedOperationException();
+		return Collections.emptyMap();
 	}
 
-	@Override public void setTypeMap(Map<String, Class<?>> map) throws SQLException {
-		throw ExceptionBuilder.buildUnsupportedOperationException();
-	}
+	@Override public void setTypeMap(Map<String, Class<?>> map) throws SQLException {} // do nothing
 
 	@Override public Savepoint setSavepoint() throws SQLException {
 		throw ExceptionBuilder.buildUnsupportedOperationException();
@@ -456,16 +455,14 @@ public abstract class Neo4jConnectionImpl implements Neo4jConnection {
 	}
 
 	@Override public Neo4jArray createArrayOf(String typeName, Object[] elements) throws SQLException {
-		throw ExceptionBuilder.buildUnsupportedOperationException();
+		return new ListArray(typeName, elements);
 	}
 
 	@Override public Struct createStruct(String typeName, Object[] attributes) throws SQLException {
 		throw ExceptionBuilder.buildUnsupportedOperationException();
 	}
 
-	@Override public void setSchema(String schema) throws SQLException {
-		throw ExceptionBuilder.buildUnsupportedOperationException();
-	}
+	@Override public void setSchema(String schema) throws SQLException {} // do nothing
 
 	@Override public void abort(Executor executor) throws SQLException {
 		throw ExceptionBuilder.buildUnsupportedOperationException();


Test patch:
diff --git a/neo4j-jdbc-bolt/src/test/java/org/neo4j/jdbc/bolt/BoltNeo4jPreparedStatementIT.java b/neo4j-jdbc-bolt/src/test/java/org/neo4j/jdbc/bolt/BoltNeo4jPreparedStatementIT.java
index 79eb226b..8fbc6431 100644
--- a/neo4j-jdbc-bolt/src/test/java/org/neo4j/jdbc/bolt/BoltNeo4jPreparedStatementIT.java
+++ b/neo4j-jdbc-bolt/src/test/java/org/neo4j/jdbc/bolt/BoltNeo4jPreparedStatementIT.java
@@ -26,9 +26,13 @@ import org.junit.Test;
 import org.neo4j.graphdb.Result;
 import org.neo4j.jdbc.bolt.data.StatementData;
 import org.neo4j.jdbc.bolt.utils.JdbcConnectionTestUtils;
+import org.neo4j.jdbc.impl.ListArray;
 
 import java.sql.*;
+import java.util.Arrays;
+import java.util.Map;
 
+import static java.sql.Types.INTEGER;
 import static org.junit.Assert.*;
 
 /**
@@ -290,5 +294,21 @@ public class BoltNeo4jPreparedStatementIT {
 
 		statement.close();
 	}
+
+	@Test public void shouldInsertArrayType() throws SQLException {
+		PreparedStatement statement = connection.prepareStatement("CREATE (:TestArrayType {name:?, props:?})");
+		statement.setString(1, "Andrea Santurbano");
+		statement.setArray(2, new ListArray(Arrays.asList(1L,2L,4L), INTEGER));
+		assertEquals(1, statement.executeUpdate());
+		statement.close();
+
+		Statement search = connection.createStatement();
+		ResultSet rs = search.executeQuery("MATCH (n:TestArrayType) return n");
+		assertTrue(rs.next());
+		Map<String, Object> map = (Map<String, Object>) rs.getObject("n");
+		assertEquals("Andrea Santurbano", map.get("name"));
+		assertEquals(Arrays.asList(1L,2L,4L), map.get("props"));
+		search.close();
+	}
 }
 
diff --git a/neo4j-jdbc-bolt/src/test/java/org/neo4j/jdbc/bolt/BoltNeo4jPreparedStatementTest.java b/neo4j-jdbc-bolt/src/test/java/org/neo4j/jdbc/bolt/BoltNeo4jPreparedStatementTest.java
index b3c1fa09..aa73938c 100644
--- a/neo4j-jdbc-bolt/src/test/java/org/neo4j/jdbc/bolt/BoltNeo4jPreparedStatementTest.java
+++ b/neo4j-jdbc-bolt/src/test/java/org/neo4j/jdbc/bolt/BoltNeo4jPreparedStatementTest.java
@@ -34,6 +34,7 @@ import org.neo4j.jdbc.Neo4jStatement;
 import org.neo4j.jdbc.bolt.data.StatementData;
 import org.neo4j.jdbc.bolt.impl.BoltNeo4jConnectionImpl;
 import org.neo4j.jdbc.bolt.utils.Mocker;
+import org.neo4j.jdbc.impl.ListArray;
 import org.neo4j.jdbc.utils.PreparedStatementBuilder;
 import org.powermock.api.mockito.PowerMockito;
 import org.powermock.core.classloader.annotations.PrepareForTest;
@@ -981,4 +982,15 @@ public class BoltNeo4jPreparedStatementTest {
 
 		stmt.getConnection();
 	}
+
+	/*------------------------------*/
+	/*           setArray           */
+	/*------------------------------*/
+
+	@Test public void setArrayShouldInsertTheCorrectArray() throws SQLException {
+		ListArray array = new ListArray(Arrays.asList(1L,2L,4L), INTEGER);
+		this.preparedStatementOneParam.setArray(1, array);
+		HashMap<String, Object> value = Whitebox.getInternalState(this.preparedStatementOneParam, "parameters");
+		assertArrayEquals(new Long[]{1L,2L,4L}, (Long[]) value.get("1"));
+	}
 }
diff --git a/neo4j-jdbc-http/src/test/java/org/neo4j/jdbc/http/HttpNeo4jConnectionIT.java b/neo4j-jdbc-http/src/test/java/org/neo4j/jdbc/http/HttpNeo4jConnectionIT.java
index a883dc78..38845137 100644
--- a/neo4j-jdbc-http/src/test/java/org/neo4j/jdbc/http/HttpNeo4jConnectionIT.java
+++ b/neo4j-jdbc-http/src/test/java/org/neo4j/jdbc/http/HttpNeo4jConnectionIT.java
@@ -172,16 +172,6 @@ public class HttpNeo4jConnectionIT extends Neo4jHttpITUtil {
 		reader.close();
 	}
 
-	@Test public void holdabilityShouldFail() throws SQLException {
-		expectedEx.expect(UnsupportedOperationException.class);
-		expectedEx.expectMessage("Method setHoldability in class org.neo4j.jdbc.http.HttpNeo4jConnection is not yet implemented.");
-
-		// Write something
-		Connection writer = DriverManager.getConnection(getJDBCUrl());
-		writer.setHoldability(1);
-		writer.close();
-	}
-
 	@Test public void isValidShouldWork() throws SQLException {
 		Connection writer = DriverManager.getConnection(getJDBCUrl());
 		assertTrue(writer.isValid(1));
diff --git a/neo4j-jdbc-http/src/test/java/org/neo4j/jdbc/http/HttpNeo4jPreparedStatementIT.java b/neo4j-jdbc-http/src/test/java/org/neo4j/jdbc/http/HttpNeo4jPreparedStatementIT.java
index 6cca48f7..6bf8d6b0 100644
--- a/neo4j-jdbc-http/src/test/java/org/neo4j/jdbc/http/HttpNeo4jPreparedStatementIT.java
+++ b/neo4j-jdbc-http/src/test/java/org/neo4j/jdbc/http/HttpNeo4jPreparedStatementIT.java
@@ -22,9 +22,13 @@ package org.neo4j.jdbc.http;
 import org.neo4j.jdbc.http.test.Neo4jHttpITUtil;
 import org.junit.Test;
 import org.neo4j.graphdb.Result;
+import org.neo4j.jdbc.impl.ListArray;
 
 import java.sql.*;
+import java.util.Arrays;
+import java.util.Map;
 
+import static java.sql.Types.INTEGER;
 import static org.junit.Assert.*;
 
 public class HttpNeo4jPreparedStatementIT extends Neo4jHttpITUtil {
@@ -154,5 +158,23 @@ public class HttpNeo4jPreparedStatementIT extends Neo4jHttpITUtil {
 
 		connection.close();
 	}
+
+	@Test public void shouldInsertArrayType() throws SQLException {
+		try (Connection connection = DriverManager.getConnection(getJDBCUrl())) {
+			PreparedStatement statement = connection.prepareStatement("CREATE (:TestArrayType {name:?, props:?})");
+			statement.setString(1, "Andrea Santurbano");
+			statement.setArray(2, new ListArray(Arrays.asList(1L,2L,4L), INTEGER));
+			assertEquals(1, statement.executeUpdate());
+			statement.close();
+
+			Statement search = connection.createStatement();
+			ResultSet rs = search.executeQuery("MATCH (n:TestArrayType) return n");
+			assertTrue(rs.next());
+			Map<String, Object> map = (Map<String, Object>) rs.getObject("n");
+			assertEquals("Andrea Santurbano", map.get("name"));
+			assertEquals(Arrays.asList(1L,2L,4L), map.get("props"));
+			search.close();
+		}
+	}
 }
 
diff --git a/neo4j-jdbc/src/test/java/org/neo4j/jdbc/Neo4jConnectionTest.java b/neo4j-jdbc/src/test/java/org/neo4j/jdbc/Neo4jConnectionTest.java
index 79ca177e..07145a5b 100644
--- a/neo4j-jdbc/src/test/java/org/neo4j/jdbc/Neo4jConnectionTest.java
+++ b/neo4j-jdbc/src/test/java/org/neo4j/jdbc/Neo4jConnectionTest.java
@@ -25,7 +25,10 @@ import org.junit.rules.ExpectedException;
 import org.mockito.Mockito;
 import org.neo4j.jdbc.impl.Neo4jConnectionImpl;
 
+import java.sql.Array;
 import java.sql.SQLException;
+import java.util.HashMap;
+import java.util.Map;
 
 import static org.junit.Assert.*;
 import static org.mockito.Mockito.mock;
@@ -80,4 +83,33 @@ public class Neo4jConnectionTest {
 
 		connection.unwrap(Neo4jStatement.class);
 	}
+
+	@Test public void createArrayOfShouldThrowException() throws SQLException {
+		Neo4jConnection connection = mock(Neo4jConnectionImpl.class, Mockito.CALLS_REAL_METHODS);
+
+		Object[] booleans = {true, false, false, true};
+		Array booleansArr = connection.createArrayOf("BOOLEAN", booleans);
+		assertArrayEquals(booleans, (Object[]) booleansArr.getArray());
+
+		Object[] strings = {"foo", "false", "bar", "1"};
+		Array stringsArr = connection.createArrayOf("VARCHAR", strings);
+		assertArrayEquals(strings, (Object[]) stringsArr.getArray());
+		assertArrayEquals(booleans, (Object[]) booleansArr.getArray());
+
+		Object[] integers = {1L, 100L, Long.MAX_VALUE, Long.MIN_VALUE};
+		Array integersArr = connection.createArrayOf("INTEGER", integers);
+		assertArrayEquals(integers, (Object[]) integersArr.getArray());
+
+		Object[] doubles = {1D, 100D, Double.MAX_VALUE, Double.MIN_VALUE};
+		Array doublesArr = connection.createArrayOf("DOUBLE", doubles);
+		assertArrayEquals(doubles, (Object[]) doublesArr.getArray());
+
+		Map<String, Object> first = new HashMap<>();
+		first.put("first", 1L);
+		Map<String, Object> second = new HashMap<>();
+		second.put("second", 2L);
+		Object[] maps = {first, second};
+		Array mapsArr = connection.createArrayOf("JAVA_OBJECT", maps);
+		assertArrayEquals(maps, (Object[]) mapsArr.getArray());
+	}
 }
diff --git a/neo4j-jdbc/src/test/java/org/neo4j/jdbc/Neo4jDataSourceTest.java b/neo4j-jdbc/src/test/java/org/neo4j/jdbc/Neo4jDataSourceTest.java
index 9eaabebc..bd32897f 100644
--- a/neo4j-jdbc/src/test/java/org/neo4j/jdbc/Neo4jDataSourceTest.java
+++ b/neo4j-jdbc/src/test/java/org/neo4j/jdbc/Neo4jDataSourceTest.java
@@ -99,6 +99,12 @@ public class Neo4jDataSourceTest {
 
 		this.dataSource.setPortNumber(7687);
 		assertEquals("jdbc:neo4j:bolt://localhost:7687?nossl,user=test,password=password", this.dataSource.getUrl("bolt"));
+
+		this.dataSource.setPassword("pa&word");
+		this.dataSource.setIsSsl(false);
+		this.dataSource.setPortNumber(0);
+		assertEquals("jdbc:neo4j:bolt://localhost?nossl,user=test,password=pa%26word", this.dataSource.getUrl("bolt"));
+		assertEquals("jdbc:neo4j:http://localhost?nossl,user=test,password=pa%26word", this.dataSource.getUrl("http"));
 	}
 
 }
diff --git a/neo4j-jdbc/src/test/java/org/neo4j/jdbc/Neo4jDriverTest.java b/neo4j-jdbc/src/test/java/org/neo4j/jdbc/Neo4jDriverTest.java
index fe102400..d1a21867 100644
--- a/neo4j-jdbc/src/test/java/org/neo4j/jdbc/Neo4jDriverTest.java
+++ b/neo4j-jdbc/src/test/java/org/neo4j/jdbc/Neo4jDriverTest.java
@@ -46,6 +46,18 @@ public class Neo4jDriverTest {
         props.setProperty("password","a test/paßword");
         assertEquals(props, neo4jDriver.parseUrlProperties("jdbc:neo4j:http://localhost?user=neo4j&password=a%20test%2Fpa%C3%9Fword",null));
         assertEquals(props, neo4jDriver.parseUrlProperties("jdbc:neo4j:http://localhost?user=neo4j,password=a%20test%2Fpa%C3%9Fword",null));
+        props.setProperty("password","a test pa&word");
+        assertEquals(props, neo4jDriver.parseUrlProperties("jdbc:neo4j:http://localhost?user=neo4j&password=a%20test%20pa%26word",null));
+        assertEquals(props, neo4jDriver.parseUrlProperties("jdbc:neo4j:http://localhost?user=neo4j,password=a%20test%20pa%26word",null));
+        props.setProperty("password","a test pa,word");
+        assertEquals(props, neo4jDriver.parseUrlProperties("jdbc:neo4j:http://localhost?user=neo4j&password=a%20test%20pa%2cword",null));
+        assertEquals(props, neo4jDriver.parseUrlProperties("jdbc:neo4j:http://localhost?user=neo4j,password=a%20test%20pa%2cword",null));
+        props.setProperty("password","a test pa=word");
+        assertEquals(props, neo4jDriver.parseUrlProperties("jdbc:neo4j:http://localhost?user:neo4j&password%3da%20test%20pa%3dword",null));
+        assertEquals(props, neo4jDriver.parseUrlProperties("jdbc:neo4j:http://localhost?user:neo4j,password%3da%20test%20pa%3dword",null));
+        props.setProperty("password","a test pa=word");
+        assertEquals(props, neo4jDriver.parseUrlProperties("jdbc:neo4j:http://localhost?user:neo4j&password:a%20test%20pa%3dword",null));
+        assertEquals(props, neo4jDriver.parseUrlProperties("jdbc:neo4j:http://localhost?user:neo4j,password:a%20test%20pa%3dword",null));
     }
 
 }