Skip to content

Commit

Permalink
Implement createArrayOf and setArray
Browse files Browse the repository at this point in the history
Currently java.sql.Array is only supported when returned from the
database. Passing arrays to the database is not supported.
Connection#createArrayOf, PreparedStatement#setArray and
PreparedStatement#setObject with a java.sql.Array are not supported.

This pull requests implements passing java.sql.Array objects to the
database and includes the following changes:

 - implement Connection#createArrayOf
 - implement PreparedStatement#setArray
 - implement array support in ResultSet#getObject
 - implement conversion from java.sql.Array to Value
 - update DataType#convertTo to support arrays as well
 - add tests for #createArrayOf, #setArray and #setObject
 - add tests for #getObject with an array argument
 - remove the test for Connection#createArrayOf being unsupported
 - remove the test for PreparedStatement#setArray being unsupported

The typeName passed to #createArrayOf is ignored, this is in accordance
with JdbcArray#getBaseTypeName returning "NULL" and
JdbcArray#getBaseType returning Types.NULL even if the backing array is
homogeneous.
  • Loading branch information
marschall committed Jan 11, 2017
1 parent 2696d56 commit 3143a1e
Show file tree
Hide file tree
Showing 8 changed files with 157 additions and 11 deletions.
2 changes: 1 addition & 1 deletion h2/src/main/org/h2/jdbc/JdbcArray.java
Expand Up @@ -28,7 +28,7 @@ public class JdbcArray extends TraceObject implements Array {
/**
* INTERNAL
*/
JdbcArray(JdbcConnection conn, Value value, int id) {
public JdbcArray(JdbcConnection conn, Value value, int id) {
setTrace(conn.getSession().getTrace(), TraceObject.ARRAY, id);
this.conn = conn;
this.value = value;
Expand Down
15 changes: 13 additions & 2 deletions h2/src/main/org/h2/jdbc/JdbcConnection.java
Expand Up @@ -48,6 +48,7 @@
import org.h2.util.JdbcUtils;
import org.h2.util.Utils;
import org.h2.value.CompareMode;
import org.h2.value.DataType;
import org.h2.value.Value;
import org.h2.value.ValueInt;
import org.h2.value.ValueNull;
Expand Down Expand Up @@ -1634,12 +1635,22 @@ public SQLXML createSQLXML() throws SQLException {
}

/**
* [Not supported] Create a new empty Array object.
* Create a new Array object.
*
* @return the array
*/
@Override
public Array createArrayOf(String typeName, Object[] elements)
throws SQLException {
throw unsupported("createArray");
try {
int id = getNextId(TraceObject.ARRAY);
debugCodeAssign("Array", TraceObject.ARRAY, id, "createArrayOf()");
checkClosed();
Value value = DataType.convertToValue(session, elements, Value.ARRAY);
return new JdbcArray(this, value, id);
} catch (Exception e) {
throw logAndConvert(e);
}
}

/**
Expand Down
22 changes: 20 additions & 2 deletions h2/src/main/org/h2/jdbc/JdbcPreparedStatement.java
Expand Up @@ -875,11 +875,29 @@ public void setClob(int parameterIndex, Reader x) throws SQLException {
}

/**
* [Not supported] Sets the value of a parameter as a Array.
* Sets the value of a parameter as an Array.
*
* @param parameterIndex the parameter index (1, 2, ...)
* @param x the value
* @throws SQLException if this object is closed
*/
@Override
public void setArray(int parameterIndex, Array x) throws SQLException {
throw unsupported("setArray");
try {
if (isDebugEnabled()) {
debugCode("setArray("+parameterIndex+", x);");
}
checkClosed();
Value v;
if (x == null) {
v = ValueNull.INSTANCE;
} else {
v = DataType.convertToValue(session, x.getArray(), Value.ARRAY);
}
setParameter(parameterIndex, v);
} catch (Exception e) {
throw logAndConvert(e);
}
}

/**
Expand Down
3 changes: 3 additions & 0 deletions h2/src/main/org/h2/jdbc/JdbcResultSet.java
Expand Up @@ -3782,6 +3782,9 @@ private <T> T extractObjectOfType(Class<T> type, Value value) throws SQLExceptio
return type.cast(value.getObject());
} else if (type == byte[].class) {
return type.cast(value.getBytes());
} else if (type == java.sql.Array.class) {
int id = getNextId(TraceObject.ARRAY);
return type.cast(value == ValueNull.INSTANCE ? null : new JdbcArray(conn, value, id));
} else if (type == TimestampWithTimeZone.class) {
return type.cast(value.getObject());
} else if (DataType.isGeometryClass(type)) {
Expand Down
10 changes: 10 additions & 0 deletions h2/src/main/org/h2/value/DataType.java
Expand Up @@ -28,6 +28,7 @@
import org.h2.engine.Constants;
import org.h2.engine.SessionInterface;
import org.h2.engine.SysProperties;
import org.h2.jdbc.JdbcArray;
import org.h2.jdbc.JdbcBlob;
import org.h2.jdbc.JdbcClob;
import org.h2.jdbc.JdbcConnection;
Expand Down Expand Up @@ -1051,6 +1052,13 @@ private static Value convertToValue1(SessionInterface session, Object x,
} catch (SQLException e) {
throw DbException.convert(e);
}
} else if (x instanceof java.sql.Array) {
java.sql.Array array = (java.sql.Array) x;
try {
return convertToValue(session, array.getArray(), Value.ARRAY);
} catch (SQLException e) {
throw DbException.convert(e);
}
} else if (x instanceof ResultSet) {
if (x instanceof SimpleResultSet) {
return ValueResultSet.get((ResultSet) x);
Expand Down Expand Up @@ -1238,6 +1246,8 @@ public static Object convertTo(JdbcConnection conn, Value v,
return new JdbcBlob(conn, v, 0);
} else if (paramClass == Clob.class) {
return new JdbcClob(conn, v, 0);
} else if (paramClass == Array.class) {
return new JdbcArray(conn, v, 0);
}
if (v.getType() == Value.JAVA_OBJECT) {
Object o = SysProperties.serializeJavaObject ? JdbcUtils.deserialize(v.getBytes(),
Expand Down
6 changes: 5 additions & 1 deletion h2/src/test/org/h2/test/TestBase.java
Expand Up @@ -662,7 +662,9 @@ public void assertEquals(java.util.Date expected, java.util.Date actual) {
}

/**
* Check if two values are equal, and if not throw an exception.
* Check if two arrays are equal, and if not throw an exception.
* If some of the elements in the arrays are themselves arrays this
* check is called recursively.
*
* @param expected the expected value
* @param actual the actual value
Expand All @@ -679,6 +681,8 @@ public void assertEquals(Object[] expected, Object[] actual) {
if (expected[i] != actual[i]) {
fail("[" + i + "]: expected: " + expected[i] + " actual: " + actual[i]);
}
} else if (expected[i] instanceof Object[] && actual[i] instanceof Object[]) {
assertEquals((Object[]) expected[i], (Object[]) actual[i]);
} else if (!expected[i].equals(actual[i])) {
fail("[" + i + "]: expected: " + expected[i] + " actual: " + actual[i]);
}
Expand Down
105 changes: 105 additions & 0 deletions h2/src/test/org/h2/test/jdbc/TestCallableStatement.java
Expand Up @@ -10,6 +10,7 @@
import java.io.StringReader;
import java.math.BigDecimal;
import java.net.URL;
import java.sql.Array;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.Ref;
Expand Down Expand Up @@ -56,6 +57,8 @@ public void test() throws Exception {
testCallWithResult(conn);
testPrepare(conn);
testClassLoader(conn);
testArrayArgument(conn);
testArrayReturnValue(conn);
conn.close();
deleteDb("callableStatement");
}
Expand Down Expand Up @@ -419,6 +422,88 @@ private void testClassLoader(Connection conn) throws SQLException {
}
}

private void testArrayArgument(Connection connection) throws SQLException {
Array array = connection.createArrayOf("Int", new Object[] {0, 1, 2});
try (Statement statement = connection.createStatement()) {
statement.execute("CREATE ALIAS getArrayLength FOR \"" +
getClass().getName() + ".getArrayLength\"");

// test setArray
try (CallableStatement callableStatement = connection.prepareCall("{call getArrayLength(?)}")) {
callableStatement.setArray(1, array);
assertTrue(callableStatement.execute());

try (ResultSet resultSet = callableStatement.getResultSet()) {
assertTrue(resultSet.next());
assertEquals(3, resultSet.getInt(1));
assertFalse(resultSet.next());
}
}

// test setObject
try (CallableStatement callableStatement = connection.prepareCall("{call getArrayLength(?)}")) {
callableStatement.setObject(1, array);
assertTrue(callableStatement.execute());

try (ResultSet resultSet = callableStatement.getResultSet()) {
assertTrue(resultSet.next());
assertEquals(3, resultSet.getInt(1));
assertFalse(resultSet.next());
}
}
} finally {
array.free();
}
}

private void testArrayReturnValue(Connection connection) throws SQLException {
Object[][] arraysToTest = new Object[][] {
new Object[] {0, 1, 2},
new Object[] {0, "1", 2},
new Object[] {0, null, 2},
new Object[] {0, new Object[] {"s", 1}, new Object[] {null, 1L}},
};
try (Statement statement = connection.createStatement()) {
statement.execute("CREATE ALIAS arrayIdentiy FOR \"" +
getClass().getName() + ".arrayIdentiy\"");

for (Object[] arrayToTest : arraysToTest) {
Array sqlInputArray = connection.createArrayOf("ignored",arrayToTest);
try {
try (CallableStatement callableStatement = connection.prepareCall("{call arrayIdentiy(?)}")) {
callableStatement.setArray(1, sqlInputArray);
assertTrue(callableStatement.execute());

try (ResultSet resultSet = callableStatement.getResultSet()) {
assertTrue(resultSet.next());

// test getArray()
Array sqlReturnArray = resultSet.getArray(1);
try {
assertEquals((Object[]) sqlInputArray.getArray(), (Object[]) sqlReturnArray.getArray());
} finally {
sqlReturnArray.free();
}

// test getObject(Array.class)
sqlReturnArray = resultSet.getObject(1, Array.class);
try {
assertEquals((Object[]) sqlInputArray.getArray(), (Object[]) sqlReturnArray.getArray());
} finally {
sqlReturnArray.free();
}

assertFalse(resultSet.next());
}
}
} finally {
sqlInputArray.free();
}

}
}
}

/**
* Class factory unit test
* @param b boolean value
Expand All @@ -428,6 +513,26 @@ public static Boolean testClassF(Boolean b) {
return !b;
}

/**
* This method is called via reflection from the database.
*
* @param array the array
* @return the length of the array
*/
public static int getArrayLength(Object[] array) {
return array == null ? 0 : array.length;
}

/**
* This method is called via reflection from the database.
*
* @param array the array
* @return the array
*/
public static Object[] arrayIdentiy(Object[] array) {
return array;
}

/**
* This method is called via reflection from the database.
*
Expand Down
5 changes: 0 additions & 5 deletions h2/src/test/org/h2/test/jdbc/TestPreparedStatement.java
Expand Up @@ -12,7 +12,6 @@
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URL;
import java.sql.Array;
import java.sql.Connection;
import java.sql.ParameterMetaData;
import java.sql.PreparedStatement;
Expand Down Expand Up @@ -142,15 +141,11 @@ private void testUnsupportedOperations(Connection conn) throws Exception {
setRowId(1, (RowId) null);
assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, prep).
setUnicodeStream(1, (InputStream) null, 0);
assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, prep).
setArray(1, (Array) null);

ParameterMetaData meta = prep.getParameterMetaData();
assertTrue(meta.toString(), meta.toString().endsWith("parameterCount=1"));
assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, conn).
createSQLXML();
assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, conn).
createArrayOf("Integer", new Object[0]);
assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, conn).
createStruct("Integer", new Object[0]);
}
Expand Down

0 comments on commit 3143a1e

Please sign in to comment.