Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support of UUID and custom decimal types #83

Merged
merged 5 commits into from
Nov 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions jdbc/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
<environmentVariables>
<TESTCONTAINERS_REUSE_ENABLE>true</TESTCONTAINERS_REUSE_ENABLE>
<YDB_DOCKER_IMAGE>ydbplatform/local-ydb:trunk</YDB_DOCKER_IMAGE>
<YDB_DOCKER_FEATURE_FLAGS>enable_parameterized_decimal</YDB_DOCKER_FEATURE_FLAGS>
</environmentVariables>
<systemPropertyVariables>
<java.util.logging.config.file>src/test/resources/logging.properties</java.util.logging.config.file>
Expand Down
6 changes: 1 addition & 5 deletions jdbc/src/main/java/tech/ydb/jdbc/YdbConst.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,7 @@ public final class YdbConst {
public static final int UNKNOWN_SQL_TYPE = Integer.MIN_VALUE;

public static final int SQL_KIND_PRIMITIVE = 10000;
public static final int SQL_KIND_DECIMAL = 1 << 15; // 32768

// YDB does not support types with custom precision yet
public static final int SQL_DECIMAL_DEFAULT_PRECISION = 22;
public static final int SQL_DECIMAL_DEFAULT_SCALE = 9;
public static final int SQL_KIND_DECIMAL = 1 << 14; // 16384

// Built-in limits
public static final int MAX_PRIMARY_KEY_SIZE = 1024 * 1024; // 1 MiB per index
Expand Down
46 changes: 38 additions & 8 deletions jdbc/src/main/java/tech/ydb/jdbc/common/MappingGetters.java
Original file line number Diff line number Diff line change
Expand Up @@ -64,17 +64,17 @@ static Getters buildGetters(Type type) {
castToBooleanNotSupported(clazz),
castToByteNotSupported(clazz),
castToShortNotSupported(clazz),
value -> value.getDecimal().toBigInteger().intValue(),
value -> value.getDecimal().toBigInteger().longValue(),
value -> value.getDecimal().toBigDecimal().floatValue(),
value -> value.getDecimal().toBigDecimal().doubleValue(),
value -> safeDecimalInt(value.getDecimal()),
value -> safeDecimalLong(value.getDecimal()),
value -> safeDecimal(value.getDecimal()).floatValue(),
value -> safeDecimal(value.getDecimal()).doubleValue(),
castToBytesNotSupported(clazz),
value -> value.getDecimal().toBigDecimal(),
value -> safeDecimal(value.getDecimal()),
castToClassNotSupported(clazz),
castToInstantNotSupported(clazz),
castToNStringNotSupported(clazz),
castToUrlNotSupported(clazz),
value -> value.getDecimal().toBigDecimal(),
value -> safeDecimal(value.getDecimal()),
castToReaderNotSupported(clazz)
);
case VOID:
Expand Down Expand Up @@ -235,6 +235,35 @@ private static ValueToBoolean valueToBoolean(PrimitiveType id) {
}
}

private static BigDecimal safeDecimal(DecimalValue value) throws SQLException {
if (value.isInf() || value.isNegativeInf() || value.isNan()) {
throw cannotConvert(value.getType(), BigDecimal.class, value.toString());
}
return value.toBigDecimal();
}

private static int safeDecimalInt(DecimalValue value) throws SQLException {
if (value.isInf() || value.isNegativeInf() || value.isNan()) {
throw cannotConvert(value.getType(), int.class, value.toString());
}
try {
return value.toBigDecimal().intValueExact();
} catch (ArithmeticException ex) {
throw cannotConvert(value.getType(), int.class, value.toString());
}
}

private static long safeDecimalLong(DecimalValue value) throws SQLException {
if (value.isInf() || value.isNegativeInf() || value.isNan()) {
throw cannotConvert(value.getType(), long.class, value.toString());
}
try {
return value.toBigDecimal().longValueExact();
} catch (ArithmeticException ex) {
throw cannotConvert(value.getType(), long.class, value.toString());
}
}

private static byte checkByteValue(PrimitiveType id, int value) throws SQLException {
int ch = value >= 0 ? value : ~value;
if ((ch & 0x7F) != ch) {
Expand Down Expand Up @@ -574,8 +603,9 @@ private static SqlType buildPrimitiveType(int sqlType, PrimitiveType id) {
case Text:
case Json:
case JsonDocument:
case Uuid:
return new SqlType(sqlType, String.class);
case Uuid:
return new SqlType(sqlType, UUID.class);
case Bytes:
case Yson:
return new SqlType(sqlType, byte[].class);
Expand Down Expand Up @@ -857,7 +887,7 @@ private static ValueToClass valueToClass(PrimitiveType id) {
}
}

private static SQLException cannotConvert(PrimitiveType type, Class<?> javaType, Object value) {
private static SQLException cannotConvert(Type type, Class<?> javaType, Object value) {
return new SQLException(String.format(YdbConst.UNABLE_TO_CONVERT, type, value, javaType));
}

Expand Down
30 changes: 26 additions & 4 deletions jdbc/src/main/java/tech/ydb/jdbc/impl/YdbDatabaseMetaDataImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import tech.ydb.table.description.TableIndex;
import tech.ydb.table.result.ResultSetReader;
import tech.ydb.table.settings.DescribeTableSettings;
import tech.ydb.table.values.DecimalType;
import tech.ydb.table.values.PrimitiveType;
import tech.ydb.table.values.Type;

Expand Down Expand Up @@ -799,7 +800,14 @@ public ResultSet getColumns(String catalog, String schemaPattern, String tableNa
nullable = columnNoNulls;
}

int decimalDigits = type.getKind() == Type.Kind.DECIMAL ? YdbConst.SQL_DECIMAL_DEFAULT_PRECISION : 0;
int decimalDigits = 0;
if (type.getKind() == Type.Kind.DECIMAL) {
if (type instanceof DecimalType) {
decimalDigits = ((DecimalType) type).getPrecision();
} else {
decimalDigits = DecimalType.getDefault().getPrecision();
}
}

rs.newRow()
.withTextValue("TABLE_NAME", tableName)
Expand Down Expand Up @@ -874,11 +882,17 @@ public ResultSet getBestRowIdentifier(String catalog, String schema, String tabl
for (String key : description.getPrimaryKeys()) {
TableColumn column = columnMap.get(key);
Type type = column.getType();
int decimalDigits = 0;
if (type.getKind() == Type.Kind.OPTIONAL) {
type = type.unwrapOptional();
}

int decimalDigits = type.getKind() == Type.Kind.DECIMAL ? YdbConst.SQL_DECIMAL_DEFAULT_PRECISION : 0;
if (type.getKind() == Type.Kind.DECIMAL) {
if (type instanceof DecimalType) {
decimalDigits = ((DecimalType) type).getPrecision();
} else {
decimalDigits = DecimalType.getDefault().getPrecision();
}
}

rs.newRow()
.withShortValue("SCOPE", (short) scope)
Expand Down Expand Up @@ -962,7 +976,15 @@ public ResultSet getTypeInfo() {

for (Type type: YdbTypes.getAllDatabaseTypes()) {
String literal = getLiteral(type);
int scale = type.getKind() == Type.Kind.DECIMAL ? YdbConst.SQL_DECIMAL_DEFAULT_SCALE : 0;
int scale = 0;
if (type.getKind() == Type.Kind.DECIMAL) {
if (type instanceof DecimalType) {
scale = ((DecimalType) type).getScale();
} else {
scale = DecimalType.getDefault().getScale();
}
}

rs.newRow()
.withTextValue("TYPE_NAME", type.toString())
.withIntValue("DATA_TYPE", YdbTypes.toSqlType(type))
Expand Down
156 changes: 82 additions & 74 deletions jdbc/src/main/java/tech/ydb/jdbc/impl/YdbTypes.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import io.grpc.netty.shaded.io.netty.util.collection.IntObjectHashMap;
import io.grpc.netty.shaded.io.netty.util.collection.IntObjectMap;
import java.util.UUID;

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

private final IntObjectMap<Type> typeBySqlType;
private final Map<Integer, Type> typeBySqlType;
private final Map<Class<?>, Type> typeByClass;

private final Map<Type, Integer> sqlTypeByPrimitiveNumId;

private YdbTypes() {
typeBySqlType = new IntObjectHashMap<>(18 + PrimitiveType.values().length);
typeBySqlType = new HashMap<>();

// Store custom type ids to use it for PrepaparedStatement.setObject
for (PrimitiveType type: PrimitiveType.values()) {
typeBySqlType.put(YdbConst.SQL_KIND_PRIMITIVE + type.ordinal(), type);
}
typeBySqlType.put(YdbConst.SQL_KIND_PRIMITIVE + 0, PrimitiveType.Bool);

typeBySqlType.put(YdbConst.SQL_KIND_PRIMITIVE + 1, PrimitiveType.Int8);
typeBySqlType.put(YdbConst.SQL_KIND_PRIMITIVE + 2, PrimitiveType.Uint8);
typeBySqlType.put(YdbConst.SQL_KIND_PRIMITIVE + 3, PrimitiveType.Int16);
typeBySqlType.put(YdbConst.SQL_KIND_PRIMITIVE + 4, PrimitiveType.Uint16);
typeBySqlType.put(YdbConst.SQL_KIND_PRIMITIVE + 5, PrimitiveType.Int32);
typeBySqlType.put(YdbConst.SQL_KIND_PRIMITIVE + 6, PrimitiveType.Uint32);
typeBySqlType.put(YdbConst.SQL_KIND_PRIMITIVE + 7, PrimitiveType.Int64);
typeBySqlType.put(YdbConst.SQL_KIND_PRIMITIVE + 8, PrimitiveType.Uint64);

typeBySqlType.put(YdbConst.SQL_KIND_PRIMITIVE + 9, PrimitiveType.Float);
typeBySqlType.put(YdbConst.SQL_KIND_PRIMITIVE + 10, PrimitiveType.Double);

typeBySqlType.put(YdbConst.SQL_KIND_PRIMITIVE + 11, PrimitiveType.Bytes);
typeBySqlType.put(YdbConst.SQL_KIND_PRIMITIVE + 12, PrimitiveType.Text);
typeBySqlType.put(YdbConst.SQL_KIND_PRIMITIVE + 13, PrimitiveType.Yson);
typeBySqlType.put(YdbConst.SQL_KIND_PRIMITIVE + 14, PrimitiveType.Json);

typeBySqlType.put(YdbConst.SQL_KIND_PRIMITIVE + 15, PrimitiveType.Uuid);

typeBySqlType.put(YdbConst.SQL_KIND_PRIMITIVE + 16, PrimitiveType.Date);
typeBySqlType.put(YdbConst.SQL_KIND_PRIMITIVE + 17, PrimitiveType.Datetime);
typeBySqlType.put(YdbConst.SQL_KIND_PRIMITIVE + 18, PrimitiveType.Timestamp);
typeBySqlType.put(YdbConst.SQL_KIND_PRIMITIVE + 19, PrimitiveType.Interval);
typeBySqlType.put(YdbConst.SQL_KIND_PRIMITIVE + 20, PrimitiveType.TzDate);
typeBySqlType.put(YdbConst.SQL_KIND_PRIMITIVE + 21, PrimitiveType.TzDatetime);
typeBySqlType.put(YdbConst.SQL_KIND_PRIMITIVE + 22, PrimitiveType.TzTimestamp);

typeBySqlType.put(YdbConst.SQL_KIND_PRIMITIVE + 23, PrimitiveType.JsonDocument);

typeBySqlType.put(Types.VARCHAR, PrimitiveType.Text);
typeBySqlType.put(Types.BIGINT, PrimitiveType.Int64);
Expand Down Expand Up @@ -90,6 +113,7 @@ private YdbTypes() {
typeByClass.put(String.class, PrimitiveType.Text);
typeByClass.put(byte[].class, PrimitiveType.Bytes);

typeByClass.put(UUID.class, PrimitiveType.Uuid);
typeByClass.put(java.sql.Date.class, PrimitiveType.Date);
typeByClass.put(LocalDate.class, PrimitiveType.Date);
typeByClass.put(LocalDateTime.class, PrimitiveType.Datetime);
Expand All @@ -104,71 +128,21 @@ private YdbTypes() {
typeByClass.put(DecimalValue.class, DecimalType.getDefault());
typeByClass.put(BigDecimal.class, DecimalType.getDefault());
typeByClass.put(Duration.class, PrimitiveType.Interval);

sqlTypeByPrimitiveNumId = new HashMap<>(PrimitiveType.values().length);
for (PrimitiveType id : PrimitiveType.values()) {
final int sqlType;
switch (id) {
case Text:
case Json:
case JsonDocument:
case Uuid:
sqlType = Types.VARCHAR;
break;
case Bytes:
case Yson:
sqlType = Types.BINARY;
break;
case Bool:
sqlType = Types.BOOLEAN;
break;
case Int8:
case Int16:
sqlType = Types.SMALLINT;
break;
case Uint8:
case Int32:
case Uint16:
sqlType = Types.INTEGER;
break;
case Uint32:
case Int64:
case Uint64:
case Interval:
sqlType = Types.BIGINT;
break;
case Float:
sqlType = Types.FLOAT;
break;
case Double:
sqlType = Types.DOUBLE;
break;
case Date:
sqlType = Types.DATE;
break;
case Datetime:
sqlType = Types.TIMESTAMP;
break;
case Timestamp:
sqlType = Types.TIMESTAMP;
break;
case TzDate:
case TzDatetime:
case TzTimestamp:
sqlType = Types.TIMESTAMP_WITH_TIMEZONE;
break;
default:
sqlType = Types.JAVA_OBJECT;
}
sqlTypeByPrimitiveNumId.put(id, sqlType);
}
}

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

private Type findTypeImpl(Object obj, int sqlType) {
if ((sqlType & YdbConst.SQL_KIND_DECIMAL) != 0) {
int precision = ((sqlType - YdbConst.SQL_KIND_DECIMAL) >> 6);
int scale = ((sqlType - YdbConst.SQL_KIND_DECIMAL) & 0b111111);
if (precision > 0 && precision < 36 && scale >= 0 && scale <= precision) {
return DecimalType.of(precision, scale);
}
}

if (typeBySqlType.containsKey(sqlType)) {
return typeBySqlType.get(sqlType);
}
Expand Down Expand Up @@ -196,10 +170,46 @@ public static int toSqlType(Type type) {
private int toSqlTypeImpl(Type type) {
switch (type.getKind()) {
case PRIMITIVE:
if (!sqlTypeByPrimitiveNumId.containsKey(type)) {
throw new RuntimeException("Internal error. Unsupported YDB type: " + type);
switch ((PrimitiveType) type) {
case Text:
case Json:
case JsonDocument:
case Uuid:
return Types.VARCHAR;
case Bytes:
case Yson:
return Types.BINARY;
case Bool:
return Types.BOOLEAN;
case Int8:
case Int16:
return Types.SMALLINT;
case Uint8:
case Int32:
case Uint16:
return Types.INTEGER;
case Uint32:
case Int64:
case Uint64:
case Interval:
return Types.BIGINT;
case Float:
return Types.FLOAT;
case Double:
return Types.DOUBLE;
case Date:
return Types.DATE;
case Datetime:
return Types.TIMESTAMP;
case Timestamp:
return Types.TIMESTAMP;
case TzDate:
case TzDatetime:
case TzTimestamp:
return Types.TIMESTAMP_WITH_TIMEZONE;
default:
return Types.JAVA_OBJECT;
}
return sqlTypeByPrimitiveNumId.get(type);
case OPTIONAL:
return toSqlTypeImpl(type.unwrapOptional());
case DECIMAL:
Expand Down Expand Up @@ -245,7 +255,7 @@ private int getSqlPrecisionImpl(Type type) {
case OPTIONAL:
return getSqlPrecisionImpl(type.unwrapOptional());
case DECIMAL:
return 8 + 8;
return ((DecimalType) type).getPrecision();
case PRIMITIVE:
return getSqlPrecisionImpl((PrimitiveType) type);
default:
Expand Down Expand Up @@ -287,8 +297,6 @@ private List<Type> getAllDatabaseTypesImpl() {
DecimalType.getDefault());
}

//

private int getSqlPrecisionImpl(PrimitiveType type) {
switch (type) {
case Bool:
Expand Down
Loading