Skip to content

Commit 380404d

Browse files
authored
Added support of UUID and custom decimal types (#83)
2 parents f8253da + 6979126 commit 380404d

21 files changed

+1222
-517
lines changed

jdbc/pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
<environmentVariables>
7878
<TESTCONTAINERS_REUSE_ENABLE>true</TESTCONTAINERS_REUSE_ENABLE>
7979
<YDB_DOCKER_IMAGE>ydbplatform/local-ydb:trunk</YDB_DOCKER_IMAGE>
80+
<YDB_DOCKER_FEATURE_FLAGS>enable_parameterized_decimal</YDB_DOCKER_FEATURE_FLAGS>
8081
</environmentVariables>
8182
<systemPropertyVariables>
8283
<java.util.logging.config.file>src/test/resources/logging.properties</java.util.logging.config.file>

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

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,7 @@ public final class YdbConst {
66
public static final int UNKNOWN_SQL_TYPE = Integer.MIN_VALUE;
77

88
public static final int SQL_KIND_PRIMITIVE = 10000;
9-
public static final int SQL_KIND_DECIMAL = 1 << 15; // 32768
10-
11-
// YDB does not support types with custom precision yet
12-
public static final int SQL_DECIMAL_DEFAULT_PRECISION = 22;
13-
public static final int SQL_DECIMAL_DEFAULT_SCALE = 9;
9+
public static final int SQL_KIND_DECIMAL = 1 << 14; // 16384
1410

1511
// Built-in limits
1612
public static final int MAX_PRIMARY_KEY_SIZE = 1024 * 1024; // 1 MiB per index

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

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -64,17 +64,17 @@ static Getters buildGetters(Type type) {
6464
castToBooleanNotSupported(clazz),
6565
castToByteNotSupported(clazz),
6666
castToShortNotSupported(clazz),
67-
value -> value.getDecimal().toBigInteger().intValue(),
68-
value -> value.getDecimal().toBigInteger().longValue(),
69-
value -> value.getDecimal().toBigDecimal().floatValue(),
70-
value -> value.getDecimal().toBigDecimal().doubleValue(),
67+
value -> safeDecimalInt(value.getDecimal()),
68+
value -> safeDecimalLong(value.getDecimal()),
69+
value -> safeDecimal(value.getDecimal()).floatValue(),
70+
value -> safeDecimal(value.getDecimal()).doubleValue(),
7171
castToBytesNotSupported(clazz),
72-
value -> value.getDecimal().toBigDecimal(),
72+
value -> safeDecimal(value.getDecimal()),
7373
castToClassNotSupported(clazz),
7474
castToInstantNotSupported(clazz),
7575
castToNStringNotSupported(clazz),
7676
castToUrlNotSupported(clazz),
77-
value -> value.getDecimal().toBigDecimal(),
77+
value -> safeDecimal(value.getDecimal()),
7878
castToReaderNotSupported(clazz)
7979
);
8080
case VOID:
@@ -235,6 +235,35 @@ private static ValueToBoolean valueToBoolean(PrimitiveType id) {
235235
}
236236
}
237237

238+
private static BigDecimal safeDecimal(DecimalValue value) throws SQLException {
239+
if (value.isInf() || value.isNegativeInf() || value.isNan()) {
240+
throw cannotConvert(value.getType(), BigDecimal.class, value.toString());
241+
}
242+
return value.toBigDecimal();
243+
}
244+
245+
private static int safeDecimalInt(DecimalValue value) throws SQLException {
246+
if (value.isInf() || value.isNegativeInf() || value.isNan()) {
247+
throw cannotConvert(value.getType(), int.class, value.toString());
248+
}
249+
try {
250+
return value.toBigDecimal().intValueExact();
251+
} catch (ArithmeticException ex) {
252+
throw cannotConvert(value.getType(), int.class, value.toString());
253+
}
254+
}
255+
256+
private static long safeDecimalLong(DecimalValue value) throws SQLException {
257+
if (value.isInf() || value.isNegativeInf() || value.isNan()) {
258+
throw cannotConvert(value.getType(), long.class, value.toString());
259+
}
260+
try {
261+
return value.toBigDecimal().longValueExact();
262+
} catch (ArithmeticException ex) {
263+
throw cannotConvert(value.getType(), long.class, value.toString());
264+
}
265+
}
266+
238267
private static byte checkByteValue(PrimitiveType id, int value) throws SQLException {
239268
int ch = value >= 0 ? value : ~value;
240269
if ((ch & 0x7F) != ch) {
@@ -574,8 +603,9 @@ private static SqlType buildPrimitiveType(int sqlType, PrimitiveType id) {
574603
case Text:
575604
case Json:
576605
case JsonDocument:
577-
case Uuid:
578606
return new SqlType(sqlType, String.class);
607+
case Uuid:
608+
return new SqlType(sqlType, UUID.class);
579609
case Bytes:
580610
case Yson:
581611
return new SqlType(sqlType, byte[].class);
@@ -857,7 +887,7 @@ private static ValueToClass valueToClass(PrimitiveType id) {
857887
}
858888
}
859889

860-
private static SQLException cannotConvert(PrimitiveType type, Class<?> javaType, Object value) {
890+
private static SQLException cannotConvert(Type type, Class<?> javaType, Object value) {
861891
return new SQLException(String.format(YdbConst.UNABLE_TO_CONVERT, type, value, javaType));
862892
}
863893

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

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import tech.ydb.table.description.TableIndex;
4040
import tech.ydb.table.result.ResultSetReader;
4141
import tech.ydb.table.settings.DescribeTableSettings;
42+
import tech.ydb.table.values.DecimalType;
4243
import tech.ydb.table.values.PrimitiveType;
4344
import tech.ydb.table.values.Type;
4445

@@ -799,7 +800,14 @@ public ResultSet getColumns(String catalog, String schemaPattern, String tableNa
799800
nullable = columnNoNulls;
800801
}
801802

802-
int decimalDigits = type.getKind() == Type.Kind.DECIMAL ? YdbConst.SQL_DECIMAL_DEFAULT_PRECISION : 0;
803+
int decimalDigits = 0;
804+
if (type.getKind() == Type.Kind.DECIMAL) {
805+
if (type instanceof DecimalType) {
806+
decimalDigits = ((DecimalType) type).getPrecision();
807+
} else {
808+
decimalDigits = DecimalType.getDefault().getPrecision();
809+
}
810+
}
803811

804812
rs.newRow()
805813
.withTextValue("TABLE_NAME", tableName)
@@ -874,11 +882,17 @@ public ResultSet getBestRowIdentifier(String catalog, String schema, String tabl
874882
for (String key : description.getPrimaryKeys()) {
875883
TableColumn column = columnMap.get(key);
876884
Type type = column.getType();
885+
int decimalDigits = 0;
877886
if (type.getKind() == Type.Kind.OPTIONAL) {
878887
type = type.unwrapOptional();
879888
}
880-
881-
int decimalDigits = type.getKind() == Type.Kind.DECIMAL ? YdbConst.SQL_DECIMAL_DEFAULT_PRECISION : 0;
889+
if (type.getKind() == Type.Kind.DECIMAL) {
890+
if (type instanceof DecimalType) {
891+
decimalDigits = ((DecimalType) type).getPrecision();
892+
} else {
893+
decimalDigits = DecimalType.getDefault().getPrecision();
894+
}
895+
}
882896

883897
rs.newRow()
884898
.withShortValue("SCOPE", (short) scope)
@@ -962,7 +976,15 @@ public ResultSet getTypeInfo() {
962976

963977
for (Type type: YdbTypes.getAllDatabaseTypes()) {
964978
String literal = getLiteral(type);
965-
int scale = type.getKind() == Type.Kind.DECIMAL ? YdbConst.SQL_DECIMAL_DEFAULT_SCALE : 0;
979+
int scale = 0;
980+
if (type.getKind() == Type.Kind.DECIMAL) {
981+
if (type instanceof DecimalType) {
982+
scale = ((DecimalType) type).getScale();
983+
} else {
984+
scale = DecimalType.getDefault().getScale();
985+
}
986+
}
987+
966988
rs.newRow()
967989
.withTextValue("TYPE_NAME", type.toString())
968990
.withIntValue("DATA_TYPE", YdbTypes.toSqlType(type))

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

Lines changed: 82 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,7 @@
1414
import java.util.HashMap;
1515
import java.util.List;
1616
import java.util.Map;
17-
18-
import io.grpc.netty.shaded.io.netty.util.collection.IntObjectHashMap;
19-
import io.grpc.netty.shaded.io.netty.util.collection.IntObjectMap;
17+
import java.util.UUID;
2018

2119
import tech.ydb.jdbc.YdbConst;
2220
import tech.ydb.table.values.DecimalType;
@@ -29,18 +27,43 @@
2927
public class YdbTypes {
3028
private static final YdbTypes INSTANCE = new YdbTypes();
3129

32-
private final IntObjectMap<Type> typeBySqlType;
30+
private final Map<Integer, Type> typeBySqlType;
3331
private final Map<Class<?>, Type> typeByClass;
3432

35-
private final Map<Type, Integer> sqlTypeByPrimitiveNumId;
36-
3733
private YdbTypes() {
38-
typeBySqlType = new IntObjectHashMap<>(18 + PrimitiveType.values().length);
34+
typeBySqlType = new HashMap<>();
3935

4036
// Store custom type ids to use it for PrepaparedStatement.setObject
41-
for (PrimitiveType type: PrimitiveType.values()) {
42-
typeBySqlType.put(YdbConst.SQL_KIND_PRIMITIVE + type.ordinal(), type);
43-
}
37+
typeBySqlType.put(YdbConst.SQL_KIND_PRIMITIVE + 0, PrimitiveType.Bool);
38+
39+
typeBySqlType.put(YdbConst.SQL_KIND_PRIMITIVE + 1, PrimitiveType.Int8);
40+
typeBySqlType.put(YdbConst.SQL_KIND_PRIMITIVE + 2, PrimitiveType.Uint8);
41+
typeBySqlType.put(YdbConst.SQL_KIND_PRIMITIVE + 3, PrimitiveType.Int16);
42+
typeBySqlType.put(YdbConst.SQL_KIND_PRIMITIVE + 4, PrimitiveType.Uint16);
43+
typeBySqlType.put(YdbConst.SQL_KIND_PRIMITIVE + 5, PrimitiveType.Int32);
44+
typeBySqlType.put(YdbConst.SQL_KIND_PRIMITIVE + 6, PrimitiveType.Uint32);
45+
typeBySqlType.put(YdbConst.SQL_KIND_PRIMITIVE + 7, PrimitiveType.Int64);
46+
typeBySqlType.put(YdbConst.SQL_KIND_PRIMITIVE + 8, PrimitiveType.Uint64);
47+
48+
typeBySqlType.put(YdbConst.SQL_KIND_PRIMITIVE + 9, PrimitiveType.Float);
49+
typeBySqlType.put(YdbConst.SQL_KIND_PRIMITIVE + 10, PrimitiveType.Double);
50+
51+
typeBySqlType.put(YdbConst.SQL_KIND_PRIMITIVE + 11, PrimitiveType.Bytes);
52+
typeBySqlType.put(YdbConst.SQL_KIND_PRIMITIVE + 12, PrimitiveType.Text);
53+
typeBySqlType.put(YdbConst.SQL_KIND_PRIMITIVE + 13, PrimitiveType.Yson);
54+
typeBySqlType.put(YdbConst.SQL_KIND_PRIMITIVE + 14, PrimitiveType.Json);
55+
56+
typeBySqlType.put(YdbConst.SQL_KIND_PRIMITIVE + 15, PrimitiveType.Uuid);
57+
58+
typeBySqlType.put(YdbConst.SQL_KIND_PRIMITIVE + 16, PrimitiveType.Date);
59+
typeBySqlType.put(YdbConst.SQL_KIND_PRIMITIVE + 17, PrimitiveType.Datetime);
60+
typeBySqlType.put(YdbConst.SQL_KIND_PRIMITIVE + 18, PrimitiveType.Timestamp);
61+
typeBySqlType.put(YdbConst.SQL_KIND_PRIMITIVE + 19, PrimitiveType.Interval);
62+
typeBySqlType.put(YdbConst.SQL_KIND_PRIMITIVE + 20, PrimitiveType.TzDate);
63+
typeBySqlType.put(YdbConst.SQL_KIND_PRIMITIVE + 21, PrimitiveType.TzDatetime);
64+
typeBySqlType.put(YdbConst.SQL_KIND_PRIMITIVE + 22, PrimitiveType.TzTimestamp);
65+
66+
typeBySqlType.put(YdbConst.SQL_KIND_PRIMITIVE + 23, PrimitiveType.JsonDocument);
4467

4568
typeBySqlType.put(Types.VARCHAR, PrimitiveType.Text);
4669
typeBySqlType.put(Types.BIGINT, PrimitiveType.Int64);
@@ -90,6 +113,7 @@ private YdbTypes() {
90113
typeByClass.put(String.class, PrimitiveType.Text);
91114
typeByClass.put(byte[].class, PrimitiveType.Bytes);
92115

116+
typeByClass.put(UUID.class, PrimitiveType.Uuid);
93117
typeByClass.put(java.sql.Date.class, PrimitiveType.Date);
94118
typeByClass.put(LocalDate.class, PrimitiveType.Date);
95119
typeByClass.put(LocalDateTime.class, PrimitiveType.Datetime);
@@ -104,71 +128,21 @@ private YdbTypes() {
104128
typeByClass.put(DecimalValue.class, DecimalType.getDefault());
105129
typeByClass.put(BigDecimal.class, DecimalType.getDefault());
106130
typeByClass.put(Duration.class, PrimitiveType.Interval);
107-
108-
sqlTypeByPrimitiveNumId = new HashMap<>(PrimitiveType.values().length);
109-
for (PrimitiveType id : PrimitiveType.values()) {
110-
final int sqlType;
111-
switch (id) {
112-
case Text:
113-
case Json:
114-
case JsonDocument:
115-
case Uuid:
116-
sqlType = Types.VARCHAR;
117-
break;
118-
case Bytes:
119-
case Yson:
120-
sqlType = Types.BINARY;
121-
break;
122-
case Bool:
123-
sqlType = Types.BOOLEAN;
124-
break;
125-
case Int8:
126-
case Int16:
127-
sqlType = Types.SMALLINT;
128-
break;
129-
case Uint8:
130-
case Int32:
131-
case Uint16:
132-
sqlType = Types.INTEGER;
133-
break;
134-
case Uint32:
135-
case Int64:
136-
case Uint64:
137-
case Interval:
138-
sqlType = Types.BIGINT;
139-
break;
140-
case Float:
141-
sqlType = Types.FLOAT;
142-
break;
143-
case Double:
144-
sqlType = Types.DOUBLE;
145-
break;
146-
case Date:
147-
sqlType = Types.DATE;
148-
break;
149-
case Datetime:
150-
sqlType = Types.TIMESTAMP;
151-
break;
152-
case Timestamp:
153-
sqlType = Types.TIMESTAMP;
154-
break;
155-
case TzDate:
156-
case TzDatetime:
157-
case TzTimestamp:
158-
sqlType = Types.TIMESTAMP_WITH_TIMEZONE;
159-
break;
160-
default:
161-
sqlType = Types.JAVA_OBJECT;
162-
}
163-
sqlTypeByPrimitiveNumId.put(id, sqlType);
164-
}
165131
}
166132

167133
public static Type findType(Object obj, int sqlType) {
168134
return INSTANCE.findTypeImpl(obj, sqlType);
169135
}
170136

171137
private Type findTypeImpl(Object obj, int sqlType) {
138+
if ((sqlType & YdbConst.SQL_KIND_DECIMAL) != 0) {
139+
int precision = ((sqlType - YdbConst.SQL_KIND_DECIMAL) >> 6);
140+
int scale = ((sqlType - YdbConst.SQL_KIND_DECIMAL) & 0b111111);
141+
if (precision > 0 && precision < 36 && scale >= 0 && scale <= precision) {
142+
return DecimalType.of(precision, scale);
143+
}
144+
}
145+
172146
if (typeBySqlType.containsKey(sqlType)) {
173147
return typeBySqlType.get(sqlType);
174148
}
@@ -196,10 +170,46 @@ public static int toSqlType(Type type) {
196170
private int toSqlTypeImpl(Type type) {
197171
switch (type.getKind()) {
198172
case PRIMITIVE:
199-
if (!sqlTypeByPrimitiveNumId.containsKey(type)) {
200-
throw new RuntimeException("Internal error. Unsupported YDB type: " + type);
173+
switch ((PrimitiveType) type) {
174+
case Text:
175+
case Json:
176+
case JsonDocument:
177+
case Uuid:
178+
return Types.VARCHAR;
179+
case Bytes:
180+
case Yson:
181+
return Types.BINARY;
182+
case Bool:
183+
return Types.BOOLEAN;
184+
case Int8:
185+
case Int16:
186+
return Types.SMALLINT;
187+
case Uint8:
188+
case Int32:
189+
case Uint16:
190+
return Types.INTEGER;
191+
case Uint32:
192+
case Int64:
193+
case Uint64:
194+
case Interval:
195+
return Types.BIGINT;
196+
case Float:
197+
return Types.FLOAT;
198+
case Double:
199+
return Types.DOUBLE;
200+
case Date:
201+
return Types.DATE;
202+
case Datetime:
203+
return Types.TIMESTAMP;
204+
case Timestamp:
205+
return Types.TIMESTAMP;
206+
case TzDate:
207+
case TzDatetime:
208+
case TzTimestamp:
209+
return Types.TIMESTAMP_WITH_TIMEZONE;
210+
default:
211+
return Types.JAVA_OBJECT;
201212
}
202-
return sqlTypeByPrimitiveNumId.get(type);
203213
case OPTIONAL:
204214
return toSqlTypeImpl(type.unwrapOptional());
205215
case DECIMAL:
@@ -245,7 +255,7 @@ private int getSqlPrecisionImpl(Type type) {
245255
case OPTIONAL:
246256
return getSqlPrecisionImpl(type.unwrapOptional());
247257
case DECIMAL:
248-
return 8 + 8;
258+
return ((DecimalType) type).getPrecision();
249259
case PRIMITIVE:
250260
return getSqlPrecisionImpl((PrimitiveType) type);
251261
default:
@@ -287,8 +297,6 @@ private List<Type> getAllDatabaseTypesImpl() {
287297
DecimalType.getDefault());
288298
}
289299

290-
//
291-
292300
private int getSqlPrecisionImpl(PrimitiveType type) {
293301
switch (type) {
294302
case Bool:

0 commit comments

Comments
 (0)