diff --git a/fdb-relational-api/src/main/java/com/apple/foundationdb/relational/api/SqlTypeNamesSupport.java b/fdb-relational-api/src/main/java/com/apple/foundationdb/relational/api/SqlTypeNamesSupport.java index c2afc3f991..34fa5be774 100644 --- a/fdb-relational-api/src/main/java/com/apple/foundationdb/relational/api/SqlTypeNamesSupport.java +++ b/fdb-relational-api/src/main/java/com/apple/foundationdb/relational/api/SqlTypeNamesSupport.java @@ -24,6 +24,8 @@ import com.apple.foundationdb.relational.util.ExcludeFromJacocoGeneratedReport; +import java.sql.Array; +import java.sql.Struct; import java.sql.Types; /** @@ -95,4 +97,30 @@ public static int getSqlTypeCode(String sqlTypeName) { throw new IllegalStateException("Unexpected sql type name:" + sqlTypeName); } } + + public static int getSqlTypeCodeFromObject(Object obj) { + if (obj == null) { + return Types.NULL; + } else if (obj instanceof Long) { + return Types.BIGINT; + } else if (obj instanceof Integer) { + return Types.INTEGER; + } else if (obj instanceof Boolean) { + return Types.BOOLEAN; + } else if (obj instanceof byte[]) { + return Types.BINARY; + } else if (obj instanceof Float) { + return Types.FLOAT; + } else if (obj instanceof Double) { + return Types.DOUBLE; + } else if (obj instanceof String) { + return Types.VARCHAR; + } else if (obj instanceof Array) { + return Types.ARRAY; + } else if (obj instanceof Struct) { + return Types.STRUCT; + } else { + throw new IllegalStateException("Unexpected object type: " + obj.getClass().getName()); + } + } } diff --git a/fdb-relational-api/src/main/java/com/apple/foundationdb/relational/api/exceptions/ErrorCode.java b/fdb-relational-api/src/main/java/com/apple/foundationdb/relational/api/exceptions/ErrorCode.java index 2278a0446e..a145bbc2a9 100644 --- a/fdb-relational-api/src/main/java/com/apple/foundationdb/relational/api/exceptions/ErrorCode.java +++ b/fdb-relational-api/src/main/java/com/apple/foundationdb/relational/api/exceptions/ErrorCode.java @@ -81,6 +81,7 @@ public enum ErrorCode { CANNOT_CONVERT_TYPE("22000"), INVALID_ROW_COUNT_IN_LIMIT_CLAUSE("2201W"), INVALID_PARAMETER("22023"), + ARRAY_ELEMENT_ERROR("2202E"), INVALID_BINARY_REPRESENTATION("22F03"), INVALID_ARGUMENT_FOR_FUNCTION("22F00"), diff --git a/fdb-relational-grpc/src/main/java/com/apple/foundationdb/relational/jdbc/RelationalStructFacade.java b/fdb-relational-grpc/src/main/java/com/apple/foundationdb/relational/jdbc/RelationalStructFacade.java index 16683a5d95..11fe6116ab 100644 --- a/fdb-relational-grpc/src/main/java/com/apple/foundationdb/relational/jdbc/RelationalStructFacade.java +++ b/fdb-relational-grpc/src/main/java/com/apple/foundationdb/relational/jdbc/RelationalStructFacade.java @@ -311,7 +311,7 @@ public boolean wasNull() throws SQLException { private Column getColumnInternal(int oneBasedColumn) { Column c = this.delegate.getColumns().getColumn(PositionalIndex.toProtobuf(oneBasedColumn)); - wasNull = c.hasNull() || c.getKindCase().equals(Column.KindCase.KIND_NOT_SET); + wasNull = c.hasNullType() || c.getKindCase().equals(Column.KindCase.KIND_NOT_SET); return c; } 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..10b1b1f6b1 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 @@ -24,6 +24,7 @@ import com.apple.foundationdb.relational.api.ArrayMetaData; import com.apple.foundationdb.relational.api.Continuation; +import com.apple.foundationdb.relational.api.SqlTypeNamesSupport; import com.apple.foundationdb.relational.api.StructMetaData; import com.apple.foundationdb.relational.api.RelationalArray; import com.apple.foundationdb.relational.api.RelationalResultSet; @@ -241,6 +242,129 @@ 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.ARRAY_ELEMENT_ERROR.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; + } + + /** + * Return the protobuf {@link Array} for a SQL {@link java.sql.Array}. + * @param array the SQL array + * @return the resulting protobuf array + */ + public static Array toArray(@Nonnull java.sql.Array array) throws SQLException { + Array.Builder builder = Array.newBuilder(); + builder.setElementType(array.getBaseType()); + for (Object o: (Object[])array.getArray()) { + builder.addElement(toColumn(array.getBaseType(), o)); + } + return builder.build(); + } + + /** + * Create {@link Column} from a Java object. + * Note: In case the column is of a composite type (array) then the actual type has to be a SQL flavor + * ({@link java.sql.Array}. + * Note: In case {@code columnType} is of value {@link Types#NULL}, the {@code obj} parameter is expected to be the + * type of null. That is, the {@code obj} will represent the {@link Types} constant for the type of variable whose + * value is null. + * @param columnType the SQL type to create (from {@link Types}) + * @param obj the value to use for the column + * @return the created column + * @throws SQLException in case of error + */ + public static Column toColumn(int columnType, @Nonnull Object obj) throws SQLException { + if (columnType != SqlTypeNamesSupport.getSqlTypeCodeFromObject(obj)) { + throw new SQLException("Column element type does not match object type: " + columnType + " / " + obj.getClass().getSimpleName(), + ErrorCode.WRONG_OBJECT_TYPE.getErrorCode()); + } + + Column.Builder builder = Column.newBuilder(); + switch (columnType) { + case Types.BIGINT: + builder = builder.setLong((Long)obj); + break; + case Types.INTEGER: + builder = builder.setInteger((Integer)obj); + break; + case Types.BOOLEAN: + builder = builder.setBoolean((Boolean)obj); + break; + case Types.VARCHAR: + builder = builder.setString((String)obj); + break; + case Types.BINARY: + builder = builder.setBinary((ByteString)obj); + break; + case Types.DOUBLE: + builder = builder.setDouble((Double)obj); + break; + case Types.ARRAY: + builder = builder.setArray(toArray((java.sql.Array)obj)); + break; + case Types.NULL: + builder = builder.setNullType((Integer)obj); + break; + default: + throw new SQLException("java.sql.Type=" + columnType + " not supported", + ErrorCode.UNSUPPORTED_OPERATION.getErrorCode()); + } + return builder.build(); + } + 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-jdbc/src/main/java/com/apple/foundationdb/relational/jdbc/JDBCArrayImpl.java b/fdb-relational-jdbc/src/main/java/com/apple/foundationdb/relational/jdbc/JDBCArrayImpl.java new file mode 100644 index 0000000000..3edb9d0505 --- /dev/null +++ b/fdb-relational-jdbc/src/main/java/com/apple/foundationdb/relational/jdbc/JDBCArrayImpl.java @@ -0,0 +1,109 @@ +/* + * JDBCArrayImpl.java + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2015-2025 Apple Inc. and the FoundationDB project authors + * + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ + +package com.apple.foundationdb.relational.jdbc; + +import com.apple.foundationdb.relational.api.SqlTypeNamesSupport; + +import javax.annotation.Nonnull; +import java.sql.Array; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.util.Map; + +/** + * A simplistic implementation of a {@link Array} that wraps around a + * {@link com.apple.foundationdb.relational.jdbc.grpc.v1.column.Array}. + * TODO: We can't use the other implementation of array in {@link RelationalArrayFacade} as it makes several assumptions + * that would be incompatible with the need to transfer an array across the wire (e.g. the type of element is missing, + * {@link RelationalArrayFacade#getArray()} returns a type other than a Java array). + */ +public class JDBCArrayImpl implements Array { + @Nonnull + private final com.apple.foundationdb.relational.jdbc.grpc.v1.column.Array underlying; + + public JDBCArrayImpl(@Nonnull final com.apple.foundationdb.relational.jdbc.grpc.v1.column.Array underlying) { + this.underlying = underlying; + } + + @Override + public String getBaseTypeName() throws SQLException { + return SqlTypeNamesSupport.getSqlTypeName(getBaseType()); + } + + @Override + public int getBaseType() throws SQLException { + return underlying.getElementType(); + } + + @Override + public Object getArray() throws SQLException { + return TypeConversion.fromArray(underlying); + } + + @Override + public Object getArray(final Map> map) throws SQLException { + throw new SQLFeatureNotSupportedException("Custom type mapping is not supported"); + } + + @Override + public Object getArray(final long index, final int count) throws SQLException { + throw new SQLFeatureNotSupportedException("Array slicing is not supported"); + } + + @Override + public Object getArray(final long index, final int count, final Map> map) throws SQLException { + throw new SQLFeatureNotSupportedException("Custom type mapping is not supported"); + } + + @Override + public ResultSet getResultSet() throws SQLException { + throw new SQLFeatureNotSupportedException("Array as result set is not supported"); + } + + @Override + public ResultSet getResultSet(final Map> map) throws SQLException { + throw new SQLFeatureNotSupportedException("Array as result set is not supported"); + } + + @Override + public ResultSet getResultSet(final long index, final int count) throws SQLException { + throw new SQLFeatureNotSupportedException("Array as result set is not supported"); + } + + @Override + public ResultSet getResultSet(final long index, final int count, final Map> map) throws SQLException { + throw new SQLFeatureNotSupportedException("Array as result set is not supported"); + } + + @Override + public void free() throws SQLException { + } + + /** + * Package protected getter. + * @return the underlying protobuf struct + */ + @Nonnull + com.apple.foundationdb.relational.jdbc.grpc.v1.column.Array getUnderlying() { + return underlying; + } +} diff --git a/fdb-relational-jdbc/src/main/java/com/apple/foundationdb/relational/jdbc/JDBCRelationalConnection.java b/fdb-relational-jdbc/src/main/java/com/apple/foundationdb/relational/jdbc/JDBCRelationalConnection.java index 111e51683f..beebdb9803 100644 --- a/fdb-relational-jdbc/src/main/java/com/apple/foundationdb/relational/jdbc/JDBCRelationalConnection.java +++ b/fdb-relational-jdbc/src/main/java/com/apple/foundationdb/relational/jdbc/JDBCRelationalConnection.java @@ -24,6 +24,7 @@ import com.apple.foundationdb.relational.api.RelationalConnection; import com.apple.foundationdb.relational.api.RelationalPreparedStatement; import com.apple.foundationdb.relational.api.RelationalStatement; +import com.apple.foundationdb.relational.api.SqlTypeNamesSupport; import com.apple.foundationdb.relational.api.exceptions.ErrorCode; import com.apple.foundationdb.relational.jdbc.grpc.GrpcConstants; import com.apple.foundationdb.relational.jdbc.grpc.v1.DatabaseMetaDataRequest; @@ -228,8 +229,13 @@ public void clearWarnings() throws SQLException { @Override public Array createArrayOf(String typeName, Object[] elements) throws SQLException { - // TODO: Implement - return null; + int elementType = SqlTypeNamesSupport.getSqlTypeCode(typeName); + final com.apple.foundationdb.relational.jdbc.grpc.v1.column.Array.Builder builder = com.apple.foundationdb.relational.jdbc.grpc.v1.column.Array.newBuilder(); + builder.setElementType(elementType); + for (Object element: elements) { + builder.addElement(TypeConversion.toColumn(elementType, element)); + } + return new JDBCArrayImpl(builder.build()); } @Override diff --git a/fdb-relational-jdbc/src/main/java/com/apple/foundationdb/relational/jdbc/JDBCRelationalPreparedStatement.java b/fdb-relational-jdbc/src/main/java/com/apple/foundationdb/relational/jdbc/JDBCRelationalPreparedStatement.java index db92974d93..56bae31021 100644 --- a/fdb-relational-jdbc/src/main/java/com/apple/foundationdb/relational/jdbc/JDBCRelationalPreparedStatement.java +++ b/fdb-relational-jdbc/src/main/java/com/apple/foundationdb/relational/jdbc/JDBCRelationalPreparedStatement.java @@ -24,17 +24,13 @@ import com.apple.foundationdb.relational.api.RelationalResultSet; import com.apple.foundationdb.relational.api.exceptions.ErrorCode; import com.apple.foundationdb.relational.jdbc.grpc.v1.Parameter; -import com.apple.foundationdb.relational.jdbc.grpc.v1.column.Column; import com.apple.foundationdb.relational.util.ExcludeFromJacocoGeneratedReport; -import com.google.protobuf.ByteString; - import javax.annotation.Nonnull; import java.sql.Array; import java.sql.Connection; import java.sql.SQLException; import java.sql.SQLWarning; -import java.sql.Types; import java.util.Map; import java.util.TreeMap; @@ -83,71 +79,52 @@ public int executeUpdate() throws SQLException { @Override public void setBoolean(int parameterIndex, boolean b) throws SQLException { - parameters.put(parameterIndex, - Parameter.newBuilder().setJavaSqlTypesCode(Types.BOOLEAN).setParameter(Column.newBuilder() - .setBoolean(b).build()).build()); + parameters.put(parameterIndex, ParameterHelper.ofBoolean(b)); } @Override public void setInt(int parameterIndex, int i) throws SQLException { - parameters.put(parameterIndex, - Parameter.newBuilder().setJavaSqlTypesCode(Types.INTEGER).setParameter(Column.newBuilder() - .setInteger(i).build()).build()); + parameters.put(parameterIndex, ParameterHelper.ofInt(i)); } @Override public void setLong(int parameterIndex, long l) throws SQLException { - parameters.put(parameterIndex, - Parameter.newBuilder().setJavaSqlTypesCode(Types.BIGINT).setParameter(Column.newBuilder() - .setLong(l).build()).build()); + parameters.put(parameterIndex, ParameterHelper.ofLong(l)); } @Override public void setFloat(int parameterIndex, float f) throws SQLException { - parameters.put(parameterIndex, - Parameter.newBuilder().setJavaSqlTypesCode(Types.FLOAT).setParameter(Column.newBuilder() - .setFloat(f).build()).build()); + parameters.put(parameterIndex, ParameterHelper.ofFloat(f)); } @Override public void setDouble(int parameterIndex, double d) throws SQLException { - parameters.put(parameterIndex, - Parameter.newBuilder().setJavaSqlTypesCode(Types.DOUBLE).setParameter(Column.newBuilder() - .setDouble(d).build()).build()); + parameters.put(parameterIndex, ParameterHelper.ofDouble(d)); } @Override public void setString(int parameterIndex, String s) throws SQLException { - parameters.put(parameterIndex, - Parameter.newBuilder().setJavaSqlTypesCode(Types.VARCHAR).setParameter(Column.newBuilder() - .setString(s).build()).build()); + parameters.put(parameterIndex, ParameterHelper.ofString(s)); } @Override public void setBytes(int parameterIndex, byte[] bytes) throws SQLException { - parameters.put(parameterIndex, - Parameter.newBuilder().setJavaSqlTypesCode(Types.BINARY).setParameter(Column.newBuilder() - .setBinary(ByteString.copyFrom(bytes)).build()).build()); + parameters.put(parameterIndex, ParameterHelper.ofBytes(bytes)); } @Override public void setObject(int parameterIndex, Object x) throws SQLException { - throw new SQLException("Not implemented in the relational layer " + - Thread.currentThread().getStackTrace()[1].getMethodName(), - ErrorCode.UNSUPPORTED_OPERATION.getErrorCode()); + parameters.put(parameterIndex, ParameterHelper.ofObject(x)); } @Override public void setNull(int parameterIndex, int sqlType) throws SQLException { - parameters.put(parameterIndex, - Parameter.newBuilder().setJavaSqlTypesCode(Types.NULL).build()); + parameters.put(parameterIndex, ParameterHelper.ofNull(sqlType)); } @Override - public void setArray(int parameterIndex, Array x) throws SQLException { - throw new SQLException("Not implemented in the relational layer " + - Thread.currentThread().getStackTrace()[1].getMethodName(), - ErrorCode.UNSUPPORTED_OPERATION.getErrorCode()); + public void setArray(int parameterIndex, Array a) throws SQLException { + parameters.put(parameterIndex, ParameterHelper.ofArray(a)); } @Override diff --git a/fdb-relational-jdbc/src/main/java/com/apple/foundationdb/relational/jdbc/ParameterHelper.java b/fdb-relational-jdbc/src/main/java/com/apple/foundationdb/relational/jdbc/ParameterHelper.java new file mode 100644 index 0000000000..b6becf17ef --- /dev/null +++ b/fdb-relational-jdbc/src/main/java/com/apple/foundationdb/relational/jdbc/ParameterHelper.java @@ -0,0 +1,145 @@ +/* + * ParameterHelper.java + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2015-2025 Apple Inc. and the FoundationDB project authors + * + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ + +package com.apple.foundationdb.relational.jdbc; + +import com.apple.foundationdb.relational.api.SqlTypeNamesSupport; +import com.apple.foundationdb.relational.api.exceptions.ErrorCode; +import com.apple.foundationdb.relational.jdbc.grpc.v1.Parameter; +import com.apple.foundationdb.relational.jdbc.grpc.v1.column.Column; +import com.google.protobuf.ByteString; + +import java.sql.Array; +import java.sql.SQLException; +import java.sql.Types; +import java.util.ArrayList; +import java.util.List; + +public class ParameterHelper { + + public static Parameter ofBoolean(boolean b) { + return Parameter.newBuilder() + .setJavaSqlTypesCode(Types.BOOLEAN) + .setParameter(Column.newBuilder().setBoolean(b)) + .build(); + } + + public static Parameter ofInt(int i) { + return Parameter.newBuilder() + .setJavaSqlTypesCode(Types.INTEGER) + .setParameter(Column.newBuilder().setInteger(i)) + .build(); + } + + public static Parameter ofLong(long l) { + return Parameter.newBuilder() + .setJavaSqlTypesCode(Types.BIGINT) + .setParameter(Column.newBuilder().setLong(l)) + .build(); + } + + public static Parameter ofFloat(float f) { + return Parameter.newBuilder() + .setJavaSqlTypesCode(Types.FLOAT) + .setParameter(Column.newBuilder().setFloat(f)) + .build(); + } + + public static Parameter ofDouble(double d) { + return Parameter.newBuilder() + .setJavaSqlTypesCode(Types.DOUBLE) + .setParameter(Column.newBuilder().setDouble(d)) + .build(); + } + + public static Parameter ofString(String s) { + return Parameter.newBuilder() + .setJavaSqlTypesCode(Types.VARCHAR) + .setParameter(Column.newBuilder().setString(s)) + .build(); + } + + public static Parameter ofBytes(byte[] bytes) { + return Parameter.newBuilder() + .setJavaSqlTypesCode(Types.BINARY) + .setParameter(Column.newBuilder().setBinary(ByteString.copyFrom(bytes))) + .build(); + } + + public static Parameter ofNull(int sqlType) throws SQLException { + return Parameter.newBuilder() + .setJavaSqlTypesCode(Types.NULL) + .setParameter(Column.newBuilder().setNullType(sqlType)) + .build(); + } + + public static Parameter ofArray(final Array a) throws SQLException { + List elements = new ArrayList<>(); + if (a instanceof JDBCArrayImpl) { + // we can shortcut the process and use the existing columns + JDBCArrayImpl arrayImpl = (JDBCArrayImpl)a; + elements.addAll(arrayImpl.getUnderlying().getElementList()); + } else { + // TODO: Do we even want to allow creation of parameter from an array created by another connection? + Object[] arrayElements = (Object[])a.getArray(); + for (Object o : arrayElements) { + Parameter p = ofObject(o); + if (p.getJavaSqlTypesCode() != a.getBaseType()) { + throw new SQLException("Array base type does not match element type: " + a.getBaseType() + ":" + p.getJavaSqlTypesCode(), ErrorCode.ARRAY_ELEMENT_ERROR.getErrorCode()); + } + elements.add(p.getParameter()); + } + } + return Parameter.newBuilder() + .setJavaSqlTypesCode(Types.ARRAY) + .setParameter(Column.newBuilder() + .setArray(com.apple.foundationdb.relational.jdbc.grpc.v1.column.Array.newBuilder() + .setElementType(a.getBaseType()) + .addAllElement(elements))) + .build(); + } + + public static Parameter ofObject(Object x) throws SQLException { + final int typeCodeFromObject = SqlTypeNamesSupport.getSqlTypeCodeFromObject(x); + switch (typeCodeFromObject) { + case Types.BIGINT: + return ofLong((Long)x); + case Types.INTEGER: + return ofInt((Integer)x); + case Types.BOOLEAN: + return ofBoolean((Boolean)x); + case Types.BINARY: + return ofBytes((byte[])x); + case Types.FLOAT: + return ofFloat((Float)x); + case Types.DOUBLE: + return ofDouble((Double)x); + case Types.VARCHAR: + return ofString((String)x); + case Types.NULL: + return ofNull(Types.NULL); // TODO: THis would be generic null... + case Types.ARRAY: + return ofArray((Array)x); + default: + throw new SQLException("setObject Not supported for type " + typeCodeFromObject, + ErrorCode.UNSUPPORTED_OPERATION.getErrorCode()); + } + } +} 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..c884b4b091 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 @@ -21,7 +21,6 @@ package com.apple.foundationdb.relational.server; import com.apple.foundationdb.annotation.API; - import com.apple.foundationdb.record.RecordCoreException; import com.apple.foundationdb.record.provider.foundationdb.FDBDatabase; import com.apple.foundationdb.record.provider.foundationdb.FDBDatabaseFactory; @@ -29,12 +28,13 @@ import com.apple.foundationdb.relational.api.EmbeddedRelationalDriver; import com.apple.foundationdb.relational.api.KeySet; import com.apple.foundationdb.relational.api.Options; -import com.apple.foundationdb.relational.api.Transaction; import com.apple.foundationdb.relational.api.RelationalDriver; import com.apple.foundationdb.relational.api.RelationalPreparedStatement; 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.Transaction; 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 +55,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 +231,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 +248,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); } diff --git a/yaml-tests/src/test/java/YamlIntegrationTests.java b/yaml-tests/src/test/java/YamlIntegrationTests.java index e8757de0e7..999a532ff1 100644 --- a/yaml-tests/src/test/java/YamlIntegrationTests.java +++ b/yaml-tests/src/test/java/YamlIntegrationTests.java @@ -259,7 +259,6 @@ public void insertEnum(YamlTest.Runner runner) throws Exception { } @TestTemplate - @ExcludeYamlTestConfig(value = YamlTestConfigFilters.DO_NOT_USE_JDBC, reason = "setObject is not supported by JDBC") public void prepared(YamlTest.Runner runner) throws Exception { runner.runYamsql("prepared.yamsql"); } diff --git a/yaml-tests/src/test/resources/prepared.yamsql b/yaml-tests/src/test/resources/prepared.yamsql index f67f074593..30a10497aa 100644 --- a/yaml-tests/src/test/resources/prepared.yamsql +++ b/yaml-tests/src/test/resources/prepared.yamsql @@ -17,6 +17,9 @@ # See the License for the specific language governing permissions and # limitations under the License. +--- +options: + supported_version: !current_version --- schema_template: create table ta(a bigint, b double, c boolean, d integer, e integer array, f string, primary key(a))