Skip to content

Commit 35faa4b

Browse files
committed
Add implementation for typed reads
1 parent 26c3539 commit 35faa4b

File tree

5 files changed

+222
-9
lines changed

5 files changed

+222
-9
lines changed

jdbc/src/main/java/tech/ydb/jdbc/YdbConst.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ public final class YdbConst {
100100
public static final String UNABLE_TO_CAST = "Cannot cast [%s] to [%s]";
101101
public static final String UNABLE_TO_CONVERT = "Cannot cast [%s] with value [%s] to [%s]";
102102
public static final String UNABLE_TO_CONVERT_AS_URL = "Cannot cast as URL: ";
103+
public static final String UNABLE_TO_CAST_TO_CLASS = "Cannot cast [%s] to class [%s]";
103104

104105
public static final String MISSING_VALUE_FOR_PARAMETER = "Missing value for parameter: ";
105106
public static final String MISSING_REQUIRED_VALUE = "Missing required value for parameter: ";

jdbc/src/main/java/tech/ydb/jdbc/common/MappingGetters.java

Lines changed: 212 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,18 @@
1111
import java.time.Instant;
1212
import java.time.LocalDate;
1313
import java.time.LocalDateTime;
14+
import java.time.ZoneId;
1415
import java.time.ZoneOffset;
1516
import java.time.ZonedDateTime;
17+
import java.util.HashMap;
18+
import java.util.Map;
1619
import java.util.Objects;
20+
import java.util.UUID;
1721
import java.util.concurrent.TimeUnit;
22+
import java.util.function.Function;
1823

1924
import tech.ydb.jdbc.YdbConst;
20-
import tech.ydb.jdbc.impl.YdbTypesImpl;
25+
import tech.ydb.jdbc.impl.YdbTypes;
2126
import tech.ydb.table.result.PrimitiveReader;
2227
import tech.ydb.table.result.ValueReader;
2328
import tech.ydb.table.values.DecimalValue;
@@ -54,6 +59,7 @@
5459
public class MappingGetters {
5560
private MappingGetters() { }
5661

62+
@SuppressWarnings("Convert2Lambda")
5763
static Getters buildGetters(Type type) {
5864
Type.Kind kind = type.getKind();
5965
String clazz = kind.toString();
@@ -71,6 +77,7 @@ static Getters buildGetters(Type type) {
7177
valueToDouble(id),
7278
valueToBytes(id),
7379
valueToObject(id),
80+
valueToClass(id),
7481
valueToInstant(id),
7582
valueToNString(id),
7683
valueToURL(id),
@@ -89,6 +96,7 @@ static Getters buildGetters(Type type) {
8996
value -> value.getDecimal().toBigDecimal().doubleValue(),
9097
castToBytesNotSupported(clazz),
9198
PrimitiveReader::getDecimal,
99+
castToClassNotSupported(clazz),
92100
castToInstantNotSupported(clazz),
93101
castToNStringNotSupported(clazz),
94102
castToUrlNotSupported(clazz),
@@ -108,6 +116,12 @@ static Getters buildGetters(Type type) {
108116
value -> 0,
109117
value -> null,
110118
value -> null,
119+
new ValueToClass() {
120+
@Override
121+
public <T> T fromValue(ValueReader reader, Class<T> clazz) throws SQLException {
122+
return null;
123+
}
124+
},
111125
value -> Instant.ofEpochSecond(0),
112126
value -> null,
113127
value -> null,
@@ -126,6 +140,7 @@ static Getters buildGetters(Type type) {
126140
castToDoubleNotSupported(clazz),
127141
castToBytesNotSupported(clazz),
128142
ValueReader::getValue,
143+
castToClassNotSupported(clazz),
129144
castToInstantNotSupported(clazz),
130145
castToNStringNotSupported(clazz),
131146
castToUrlNotSupported(clazz),
@@ -356,6 +371,8 @@ private static ValueToInt valueToInt(PrimitiveType id) {
356371
return value -> checkIntValue(id, value.getUint32());
357372
case Uint64:
358373
return value -> checkIntValue(id, value.getUint64());
374+
case Date:
375+
return value -> checkIntValue(id, value.getDate().toEpochDay());
359376
default:
360377
return castToIntNotSupported(id.name());
361378
}
@@ -382,6 +399,7 @@ private static ValueToLong valueToLong(PrimitiveType id) {
382399
case Uint64:
383400
return PrimitiveReader::getUint64;
384401
case Date:
402+
return value -> value.getDate().toEpochDay();
385403
case Datetime:
386404
case TzDate:
387405
case TzDatetime:
@@ -622,7 +640,7 @@ private static SqlType buildPrimitiveType(int sqlType, PrimitiveType id) {
622640

623641
static SqlType buildDataType(Type type) {
624642
// All types must be the same as for #valueToObject
625-
int sqlType = YdbTypesImpl.getInstance().toSqlType(type);
643+
int sqlType = YdbTypes.toSqlType(type);
626644

627645
switch (type.getKind()) {
628646
case PRIMITIVE:
@@ -692,6 +710,177 @@ private static ValueToObject valueToObject(PrimitiveType id) {
692710
}
693711
}
694712

713+
private static class ValueToClassBuilder {
714+
private final Type type;
715+
private final Map<Class<?>, Function<ValueReader, ?>> map = new HashMap<>();
716+
717+
ValueToClassBuilder(Type type) {
718+
this.type = type;
719+
}
720+
721+
public <T> ValueToClassBuilder register(Class<T> clazz, Function<ValueReader, T> func) {
722+
map.put(clazz, func);
723+
return this;
724+
}
725+
726+
@SuppressWarnings("Convert2Lambda")
727+
public ValueToClass build() {
728+
return new ValueToClass() {
729+
@Override
730+
@SuppressWarnings("unchecked")
731+
public <T> T fromValue(ValueReader reader, Class<T> clazz) throws SQLException {
732+
Function<ValueReader, ?> f = map.get(clazz);
733+
if (f != null) {
734+
return (T) f.apply(reader);
735+
}
736+
throw new SQLException(String.format(YdbConst.UNABLE_TO_CAST_TO_CLASS, type, clazz));
737+
}
738+
};
739+
}
740+
}
741+
742+
743+
private static ValueToClass valueToClass(PrimitiveType id) {
744+
ValueToClassBuilder builder = new ValueToClassBuilder(id);
745+
switch (id) {
746+
case Bytes:
747+
return builder
748+
.register(byte[].class, ValueReader::getBytes)
749+
.build();
750+
case Text:
751+
return builder
752+
.register(String.class, ValueReader::getText)
753+
.build();
754+
case Json:
755+
return builder
756+
.register(String.class, ValueReader::getJson)
757+
.build();
758+
case JsonDocument:
759+
return builder
760+
.register(String.class, ValueReader::getJsonDocument)
761+
.build();
762+
case Yson:
763+
return builder
764+
.register(byte[].class, ValueReader::getYson)
765+
.build();
766+
case Uuid:
767+
return builder
768+
.register(UUID.class, ValueReader::getUuid)
769+
.build();
770+
case Bool:
771+
return builder
772+
.register(boolean.class, ValueReader::getBool)
773+
.register(Boolean.class, ValueReader::getBool)
774+
.build();
775+
case Int8:
776+
return builder
777+
.register(byte.class, ValueReader::getInt8)
778+
.register(Byte.class, ValueReader::getInt8)
779+
.build();
780+
case Uint8:
781+
return builder
782+
.register(int.class, ValueReader::getUint8)
783+
.register(Integer.class, ValueReader::getUint8)
784+
.build();
785+
case Int16:
786+
return builder
787+
.register(short.class, ValueReader::getInt16)
788+
.register(Short.class, ValueReader::getInt16)
789+
.build();
790+
case Uint16:
791+
return builder
792+
.register(int.class, ValueReader::getUint16)
793+
.register(Integer.class, ValueReader::getUint16)
794+
.build();
795+
case Int32:
796+
return builder
797+
.register(int.class, ValueReader::getInt32)
798+
.register(Integer.class, ValueReader::getInt32)
799+
.build();
800+
case Uint32:
801+
return builder
802+
.register(long.class, ValueReader::getUint32)
803+
.register(Long.class, ValueReader::getUint32)
804+
.build();
805+
case Int64:
806+
return builder
807+
.register(long.class, ValueReader::getInt64)
808+
.register(Long.class, ValueReader::getInt64)
809+
.build();
810+
case Uint64:
811+
return builder
812+
.register(long.class, ValueReader::getUint64)
813+
.register(Long.class, ValueReader::getUint64)
814+
.build();
815+
case Float:
816+
return builder
817+
.register(float.class, ValueReader::getFloat)
818+
.register(Float.class, ValueReader::getFloat)
819+
.build();
820+
case Double:
821+
return builder
822+
.register(double.class, ValueReader::getDouble)
823+
.register(Double.class, ValueReader::getDouble)
824+
.build();
825+
case Date:
826+
return builder
827+
.register(long.class, v -> v.getDate().toEpochDay())
828+
.register(Long.class, v -> v.getDate().toEpochDay())
829+
.register(LocalDate.class, ValueReader::getDate)
830+
.register(LocalDateTime.class, v -> v.getDate().atStartOfDay())
831+
.register(java.sql.Date.class, v -> java.sql.Date.valueOf(v.getDate()))
832+
.register(java.sql.Timestamp.class, v -> java.sql.Timestamp.valueOf(v.getDate().atStartOfDay()))
833+
.register(Instant.class, v -> v.getDate().atStartOfDay(ZoneId.systemDefault()).toInstant())
834+
.register(java.util.Date.class, v -> java.util.Date.from(
835+
v.getDate().atStartOfDay(ZoneId.systemDefault()).toInstant()))
836+
.build();
837+
case Datetime:
838+
return builder
839+
.register(long.class, v -> v.getDatetime().toEpochSecond(ZoneOffset.UTC))
840+
.register(Long.class, v -> v.getDatetime().toEpochSecond(ZoneOffset.UTC))
841+
.register(LocalDate.class, v -> v.getDatetime().toLocalDate())
842+
.register(LocalDateTime.class, ValueReader::getDatetime)
843+
.register(java.sql.Date.class, v -> java.sql.Date.valueOf(v.getDatetime().toLocalDate()))
844+
.register(java.sql.Timestamp.class, v -> java.sql.Timestamp.valueOf(v.getDatetime()))
845+
.register(Instant.class, v -> v.getDatetime().atZone(ZoneId.systemDefault()).toInstant())
846+
.register(java.util.Date.class, v -> java.util.Date.from(
847+
v.getDatetime().atZone(ZoneId.systemDefault()).toInstant()))
848+
.build();
849+
case Timestamp:
850+
return builder
851+
.register(long.class, v -> v.getTimestamp().toEpochMilli())
852+
.register(Long.class, v -> v.getTimestamp().toEpochMilli())
853+
.register(LocalDate.class, v -> v.getTimestamp().atZone(ZoneId.systemDefault()).toLocalDate())
854+
.register(LocalDateTime.class, v -> v.getTimestamp()
855+
.atZone(ZoneId.systemDefault()).toLocalDateTime())
856+
.register(java.sql.Date.class, v -> java.sql.Date
857+
.valueOf(v.getTimestamp().atZone(ZoneId.systemDefault()).toLocalDate()))
858+
.register(java.sql.Timestamp.class, v -> java.sql.Timestamp
859+
.valueOf(v.getTimestamp().atZone(ZoneId.systemDefault()).toLocalDateTime()))
860+
.register(Instant.class, ValueReader::getTimestamp)
861+
.register(java.util.Date.class, v -> java.util.Date.from(v.getTimestamp()))
862+
.build();
863+
case Interval:
864+
return builder
865+
.register(Duration.class, ValueReader::getInterval)
866+
.build();
867+
case TzDate:
868+
return builder
869+
.register(ZonedDateTime.class, ValueReader::getTzDate)
870+
.build();
871+
case TzDatetime:
872+
return builder
873+
.register(ZonedDateTime.class, ValueReader::getTzDatetime)
874+
.build();
875+
case TzTimestamp:
876+
return builder
877+
.register(ZonedDateTime.class, ValueReader::getTzTimestamp)
878+
.build();
879+
default:
880+
return castToClassNotSupported(id.toString());
881+
}
882+
}
883+
695884
private static SQLException cannotConvert(PrimitiveType type, Class<?> javaType, Object value) {
696885
return new SQLException(String.format(YdbConst.UNABLE_TO_CONVERT, type, value, javaType));
697886
}
@@ -778,6 +967,16 @@ private static ValueToReader castToReaderNotSupported(String type) {
778967
};
779968
}
780969

970+
@SuppressWarnings("Convert2Lambda")
971+
private static ValueToClass castToClassNotSupported(String type) {
972+
return new ValueToClass() {
973+
@Override
974+
public <T> T fromValue(ValueReader reader, Class<T> clazz) throws SQLException {
975+
throw new SQLException(String.format(YdbConst.UNABLE_TO_CAST_TO_CLASS, type, clazz));
976+
}
977+
};
978+
}
979+
781980
public static class Getters {
782981
private final ValueToString toString;
783982
private final ValueToBoolean toBoolean;
@@ -789,6 +988,7 @@ public static class Getters {
789988
private final ValueToDouble toDouble;
790989
private final ValueToBytes toBytes;
791990
private final ValueToObject toObject;
991+
private final ValueToClass toClass;
792992
private final ValueToInstant toInstant;
793993
private final ValueToNString toNString;
794994
private final ValueToURL toURL;
@@ -806,6 +1006,7 @@ public static class Getters {
8061006
ValueToDouble toDouble,
8071007
ValueToBytes toBytes,
8081008
ValueToObject toObject,
1009+
ValueToClass toClass,
8091010
ValueToInstant toInstant,
8101011
ValueToNString toNString,
8111012
ValueToURL toURL,
@@ -821,6 +1022,7 @@ public static class Getters {
8211022
this.toDouble = toDouble;
8221023
this.toBytes = toBytes;
8231024
this.toObject = toObject;
1025+
this.toClass = toClass;
8241026
this.toInstant = toInstant;
8251027
this.toNString = toNString;
8261028
this.toURL = toURL;
@@ -868,6 +1070,10 @@ public Object readObject(ValueReader reader) throws SQLException {
8681070
return toObject.fromValue(reader);
8691071
}
8701072

1073+
public <T> T readClass(ValueReader reader, Class<T> clazz) throws SQLException {
1074+
return toClass.fromValue(reader, clazz);
1075+
}
1076+
8711077
public Instant readInstant(ValueReader reader) throws SQLException {
8721078
return toInstant.fromValue(reader);
8731079
}
@@ -929,6 +1135,10 @@ private interface ValueToObject {
9291135
Object fromValue(ValueReader reader) throws SQLException;
9301136
}
9311137

1138+
private interface ValueToClass {
1139+
<T> T fromValue(ValueReader reader, Class<T> clazz) throws SQLException;
1140+
}
1141+
9321142
private interface ValueToInstant {
9331143
Instant fromValue(ValueReader reader) throws SQLException;
9341144
}

jdbc/src/main/java/tech/ydb/jdbc/impl/YdbResultSetImpl.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1180,12 +1180,13 @@ public InputStream getAsciiStream(String columnLabel) throws SQLException {
11801180

11811181
@Override
11821182
public <T> T getObject(int columnIndex, Class<T> type) throws SQLException {
1183-
throw new SQLFeatureNotSupportedException(YdbConst.OBJECT_TYPED_UNSUPPORTED);
1183+
initValueReader(columnIndex);
1184+
return state.description.getters().readClass(state.value, type);
11841185
}
11851186

11861187
@Override
11871188
public <T> T getObject(String columnLabel, Class<T> type) throws SQLException {
1188-
throw new SQLFeatureNotSupportedException(YdbConst.OBJECT_TYPED_UNSUPPORTED);
1189+
return getObject(findColumn(columnLabel), type);
11891190
}
11901191

11911192
@Override

jdbc/src/test/java/tech/ydb/jdbc/impl/YdbPreparedStatementTest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.util.TimeZone;
2020

2121
import org.junit.Assert;
22+
import org.junit.jupiter.api.AfterAll;
2223
import org.junit.jupiter.api.BeforeAll;
2324
import org.junit.jupiter.api.BeforeEach;
2425
import org.junit.jupiter.api.Disabled;

jdbc/src/test/java/tech/ydb/jdbc/impl/YdbResultSetImplTest.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1960,10 +1960,10 @@ public void getNCharacterStream() throws SQLException {
19601960
@Test
19611961
public void unsupportedGetters() {
19621962
// getObject with type
1963-
ExceptionAssert.sqlFeatureNotSupported("Object with type conversion is not supported yet",
1964-
() -> resultSet.getObject(1, Integer.class));
1965-
ExceptionAssert.sqlFeatureNotSupported("Object with type conversion is not supported yet",
1966-
() -> resultSet.getObject("Column", Integer.class));
1963+
// ExceptionAssert.sqlFeatureNotSupported("Object with type conversion is not supported yet",
1964+
// () -> resultSet.getObject(1, Integer.class));
1965+
// ExceptionAssert.sqlFeatureNotSupported("Object with type conversion is not supported yet",
1966+
// () -> resultSet.getObject("Column", Integer.class));
19671967

19681968
// getObject with type map
19691969
ExceptionAssert.sqlFeatureNotSupported("Object with type conversion is not supported yet",
@@ -2151,7 +2151,7 @@ private static class ResultSetChecker<T> {
21512151
private final IndexFunctor<T> indexFunctor;
21522152
private final StringFunctor<T> nameFunctor;
21532153

2154-
public ResultSetChecker(ResultSet rs, IndexFunctor<T> indexFunctor, StringFunctor<T> nameFunctor) {
2154+
public ResultSetChecker(ResultSet rs, IndexFunctor<T> indexFunctor, StringFunctor<T> nameFunctor) {
21552155
this.rs = rs;
21562156
this.indexFunctor = indexFunctor;
21572157
this.nameFunctor = nameFunctor;

0 commit comments

Comments
 (0)