diff --git a/fdb-relational-grpc/src/main/java/com/apple/foundationdb/relational/jdbc/TypeConversion.java b/fdb-relational-grpc/src/main/java/com/apple/foundationdb/relational/jdbc/TypeConversion.java index bef4cb0759..2c4ce1f469 100644 --- a/fdb-relational-grpc/src/main/java/com/apple/foundationdb/relational/jdbc/TypeConversion.java +++ b/fdb-relational-grpc/src/main/java/com/apple/foundationdb/relational/jdbc/TypeConversion.java @@ -241,6 +241,64 @@ private static Struct toStruct(RelationalStruct relationalStruct) throws SQLExce return Struct.newBuilder().setColumns(listColumnBuilder.build()).build(); } + /** + * Return the Java object stored within the proto. + * @param columnType the type of object in the column + * @param column the column to process + * @return the Java object from the Column representation + * @throws SQLException in case of an error + */ + public static Object fromColumn(int columnType, Column column) throws SQLException { + switch (columnType) { + case Types.ARRAY: + checkColumnType(columnType, column.hasArray()); + return fromArray(column.getArray()); + case Types.BIGINT: + checkColumnType(columnType, column.hasLong()); + return column.getLong(); + case Types.INTEGER: + checkColumnType(columnType, column.hasInteger()); + return column.getInteger(); + case Types.BOOLEAN: + checkColumnType(columnType, column.hasBoolean()); + return column.getBoolean(); + case Types.VARCHAR: + checkColumnType(columnType, column.hasString()); + return column.getString(); + case Types.BINARY: + checkColumnType(columnType, column.hasBinary()); + return column.getBinary().toByteArray(); + case Types.DOUBLE: + checkColumnType(columnType, column.hasDouble()); + return column.getDouble(); + default: + // NULL (java.sql.Types value 0) is not a valid column type for an array and is likely the result of a default value for the + // (optional) array.getElementType() protobuf field. + throw new SQLException("java.sql.Type=" + columnType + " not supported", ErrorCode.CANNOT_CONVERT_TYPE.getErrorCode()); + } + } + + private static void checkColumnType(final int expectedColumnType, final boolean columnHasType) throws SQLException { + if (!columnHasType) { + throw new SQLException("Column has wrong type (expected " + expectedColumnType + ")", ErrorCode.WRONG_OBJECT_TYPE.getErrorCode()); + } + } + + /** + * Return the Java array stored within the proto. + * @param array the array to process + * @return the Java array from the proto representation + * @throws SQLException in case of an error + */ + public static Object[] fromArray(Array array) throws SQLException { + Object[] result = new Object[array.getElementCount()]; + final List elements = array.getElementList(); + for (int i = 0 ; i < elements.size() ; i++) { + result[i] = fromColumn(array.getElementType(), elements.get(i)); + } + return result; + } + private static Column toColumn(RelationalStruct relationalStruct, int oneBasedIndex) throws SQLException { int columnType = relationalStruct.getMetaData().getColumnType(oneBasedIndex); Column column; diff --git a/fdb-relational-grpc/src/main/proto/grpc/relational/jdbc/v1/column.proto b/fdb-relational-grpc/src/main/proto/grpc/relational/jdbc/v1/column.proto index 33054bc1f3..d79f44bc52 100644 --- a/fdb-relational-grpc/src/main/proto/grpc/relational/jdbc/v1/column.proto +++ b/fdb-relational-grpc/src/main/proto/grpc/relational/jdbc/v1/column.proto @@ -50,6 +50,9 @@ message Struct { // Relational Array. message Array { repeated Column element = 1; + // The java.sql.Types of the elements of the array. This is somewhat redundant with the type of the + // columns, but it makes it easier to verify correctness. + int32 elementType = 2; } // `Column` represents a dynamically typed column which can be either @@ -59,7 +62,7 @@ message Array { message Column { // The kind/type of column. oneof kind { - // Represents a null column. + // Deprecated NullColumn null = 1; // Represents a double column. double double = 2; @@ -75,15 +78,13 @@ message Column { Struct struct = 7; Array array = 8; bytes binary = 9; - float float = 10; + // Represents a null value. These can be typed, so the value is the java.sql.Types of the parameter + int32 nullType = 11; } } -// `NullValue` is a singleton enumeration to represent the null value for the -// `Column` type union. -// -// The JSON representation for `NullValue` is JSON `null`. +// Deprecated enum NullColumn { // Null value. NULL_COLUMN = 0; diff --git a/fdb-relational-server/src/main/java/com/apple/foundationdb/relational/server/FRL.java b/fdb-relational-server/src/main/java/com/apple/foundationdb/relational/server/FRL.java index 4867ac0016..25c9912e78 100644 --- a/fdb-relational-server/src/main/java/com/apple/foundationdb/relational/server/FRL.java +++ b/fdb-relational-server/src/main/java/com/apple/foundationdb/relational/server/FRL.java @@ -35,6 +35,7 @@ import com.apple.foundationdb.relational.api.RelationalResultSet; import com.apple.foundationdb.relational.api.RelationalStatement; import com.apple.foundationdb.relational.api.RelationalStruct; +import com.apple.foundationdb.relational.api.SqlTypeNamesSupport; import com.apple.foundationdb.relational.api.catalog.StoreCatalog; import com.apple.foundationdb.relational.api.exceptions.RelationalException; import com.apple.foundationdb.relational.api.metrics.NoOpMetricRegistry; @@ -55,6 +56,7 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.net.URI; +import java.sql.Array; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; @@ -230,11 +232,14 @@ private static void addPreparedStatementParameter(RelationalPreparedStatement re relationalPreparedStatement.setString(index, parameter.getParameter().getString()); break; case Types.BIGINT: - relationalPreparedStatement.setInt(index, parameter.getParameter().getInteger()); + relationalPreparedStatement.setLong(index, parameter.getParameter().getLong()); break; case Types.INTEGER: relationalPreparedStatement.setInt(index, parameter.getParameter().getInteger()); break; + case Types.FLOAT: + relationalPreparedStatement.setFloat(index, parameter.getParameter().getFloat()); + break; case Types.DOUBLE: relationalPreparedStatement.setDouble(index, parameter.getParameter().getDouble()); break; @@ -244,6 +249,16 @@ private static void addPreparedStatementParameter(RelationalPreparedStatement re case Types.BINARY: relationalPreparedStatement.setBytes(index, parameter.getParameter().getBinary().toByteArray()); break; + case Types.NULL: + relationalPreparedStatement.setNull(index, parameter.getParameter().getNullType()); + break; + case Types.ARRAY: + final com.apple.foundationdb.relational.jdbc.grpc.v1.column.Array arrayProto = parameter.getParameter().getArray(); + final Array relationalArray = relationalPreparedStatement.getConnection().createArrayOf( + SqlTypeNamesSupport.getSqlTypeName(arrayProto.getElementType()), + TypeConversion.fromArray(arrayProto)); + relationalPreparedStatement.setArray(index, relationalArray); + break; default: throw new SQLException("Unsupported type " + type); }