提交 790cf87b authored 作者: Philippe Marschall's avatar Philippe Marschall 提交者: Sergi Vladykin

Implement createArrayOf and setArray (#418)

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.
上级 b7cd026b
......@@ -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;
......
......@@ -48,6 +48,7 @@ import org.h2.util.CloseWatcher;
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;
......@@ -1634,12 +1635,22 @@ public class JdbcConnection extends TraceObject implements Connection,
}
/**
* [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);
}
}
/**
......
......@@ -875,11 +875,29 @@ public class JdbcPreparedStatement extends JdbcStatement implements
}
/**
* [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);
}
}
/**
......
......@@ -3782,6 +3782,9 @@ public class JdbcResultSet extends TraceObject implements ResultSet, JdbcResultS
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)) {
......
......@@ -28,6 +28,7 @@ import org.h2.api.TimestampWithTimeZone;
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;
......@@ -1051,6 +1052,13 @@ public class DataType {
} 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);
......@@ -1238,6 +1246,8 @@ public class DataType {
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(),
......
......@@ -662,7 +662,9 @@ public abstract class TestBase {
}
/**
* 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
......@@ -679,6 +681,8 @@ public abstract class TestBase {
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]);
}
......
......@@ -10,6 +10,7 @@ import java.io.Reader;
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;
......@@ -56,6 +57,8 @@ public class TestCallableStatement extends TestBase {
testCallWithResult(conn);
testPrepare(conn);
testClassLoader(conn);
testArrayArgument(conn);
testArrayReturnValue(conn);
conn.close();
deleteDb("callableStatement");
}
......@@ -419,6 +422,88 @@ public class TestCallableStatement extends TestBase {
}
}
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
......@@ -428,6 +513,26 @@ public class TestCallableStatement extends TestBase {
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.
*
......
......@@ -12,7 +12,6 @@ import java.io.StringReader;
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;
......@@ -142,15 +141,11 @@ public class TestPreparedStatement extends TestBase {
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]);
}
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论