Skip to content

Commit 8cdafbd

Browse files
authored
GH-180 Introduced YdbType complement type-safe variant (#185)
2 parents 902a538 + 43b00e9 commit 8cdafbd

File tree

14 files changed

+601
-156
lines changed

14 files changed

+601
-156
lines changed

hibernate-dialect/pom.xml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@
4141
<junit5.version>5.9.3</junit5.version>
4242
<log4j2.version>2.17.2</log4j2.version>
4343

44-
<ydb.sdk.version>2.3.4</ydb.sdk.version>
45-
<ydb.jdbc.version>2.3.3</ydb.jdbc.version>
44+
<ydb.sdk.version>2.3.13</ydb.sdk.version>
45+
<ydb.jdbc.version>2.3.10</ydb.jdbc.version>
4646
</properties>
4747

4848
<licenses>
@@ -146,7 +146,6 @@
146146
<environmentVariables>
147147
<TESTCONTAINERS_REUSE_ENABLE>true</TESTCONTAINERS_REUSE_ENABLE>
148148
<YDB_DOCKER_FEATURE_FLAGS>enable_parameterized_decimal</YDB_DOCKER_FEATURE_FLAGS>
149-
<YDB_DOCKER_IMAGE>cr.yandex/yc/yandex-docker-local-ydb:trunk</YDB_DOCKER_IMAGE>
150149
</environmentVariables>
151150
</configuration>
152151
</plugin>

spring-data-jdbc-ydb/pom.xml

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,12 @@
5050
<maven.compiler.source>17</maven.compiler.source>
5151

5252
<junit5.version>5.10.2</junit5.version>
53-
<lombok.version>1.18.30</lombok.version>
5453
<spring.version>3.4.0</spring.version>
5554
<liquibase.version>4.24.0</liquibase.version>
5655

57-
<ydb.sdk.version>2.2.9</ydb.sdk.version>
58-
<ydb.jdbc.version>2.2.3</ydb.jdbc.version>
59-
<ydb.liquibase.version>0.9.7</ydb.liquibase.version>
56+
<ydb.sdk.version>2.3.13</ydb.sdk.version>
57+
<ydb.jdbc.version>2.3.10</ydb.jdbc.version>
58+
<ydb.liquibase.version>1.1.1</ydb.liquibase.version>
6059
</properties>
6160

6261
<dependencyManagement>
@@ -84,12 +83,6 @@
8483
<artifactId>spring-data-jdbc</artifactId>
8584
<scope>provided</scope>
8685
</dependency>
87-
<dependency>
88-
<groupId>tech.ydb.jdbc</groupId>
89-
<artifactId>ydb-jdbc-driver</artifactId>
90-
<version>${ydb.jdbc.version}</version>
91-
<scope>provided</scope>
92-
</dependency>
9386

9487
<dependency>
9588
<groupId>tech.ydb.test</groupId>
@@ -102,9 +95,9 @@
10295
<scope>test</scope>
10396
</dependency>
10497
<dependency>
105-
<groupId>org.projectlombok</groupId>
106-
<artifactId>lombok</artifactId>
107-
<version>${lombok.version}</version>
98+
<groupId>tech.ydb.jdbc</groupId>
99+
<artifactId>ydb-jdbc-driver</artifactId>
100+
<version>${ydb.jdbc.version}</version>
108101
<scope>test</scope>
109102
</dependency>
110103
<dependency>
@@ -126,7 +119,6 @@
126119
<dependency>
127120
<groupId>org.liquibase</groupId>
128121
<artifactId>liquibase-core</artifactId>
129-
<version>${liquibase.version}</version>
130122
<scope>test</scope>
131123
</dependency>
132124
</dependencies>
Lines changed: 68 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,75 @@
11
package tech.ydb.data.core.convert;
22

3-
import java.sql.SQLType;
4-
import tech.ydb.jdbc.YdbConst;
5-
import tech.ydb.table.values.PrimitiveType;
6-
7-
/**
8-
* @author Madiyar Nurgazin
9-
*/
10-
public record YQLType(PrimitiveType type) implements SQLType {
11-
@Override
12-
public String getName() {
13-
return type.name();
14-
}
153

16-
@Override
17-
public String getVendor() {
18-
return "YDB";
4+
public enum YQLType {
5+
/** Boolean value. */
6+
Bool(YdbConst.SQL_KIND_PRIMITIVE + 0),
7+
/** A signed integer. Acceptable values: from -2^7 to 2^7–1. Not supported for table columns */
8+
Int8(YdbConst.SQL_KIND_PRIMITIVE + 1),
9+
/** An unsigned integer. Acceptable values: from 0 to 2^8–1. */
10+
Uint8(YdbConst.SQL_KIND_PRIMITIVE + 2),
11+
/** A signed integer. Acceptable values: from –2^15 to 2^15–1. Not supported for table columns */
12+
Int16(YdbConst.SQL_KIND_PRIMITIVE + 3),
13+
/** An unsigned integer. Acceptable values: from 0 to 2^16–1. Not supported for table columns */
14+
Uint16(YdbConst.SQL_KIND_PRIMITIVE + 4),
15+
/** A signed integer. Acceptable values: from –2^31 to 2^31–1. */
16+
Int32(YdbConst.SQL_KIND_PRIMITIVE + 5),
17+
/** An unsigned integer. Acceptable values: from 0 to 2^32–1. */
18+
Uint32(YdbConst.SQL_KIND_PRIMITIVE + 6),
19+
/** A signed integer. Acceptable values: from –2^63 to 2^63–1. */
20+
Int64(YdbConst.SQL_KIND_PRIMITIVE + 7),
21+
/** An unsigned integer. Acceptable values: from 0 to 2^64–1. */
22+
Uint64(YdbConst.SQL_KIND_PRIMITIVE + 8),
23+
/** A real number with variable precision, 4 bytes in size. Can't be used in the primary key */
24+
Float(YdbConst.SQL_KIND_PRIMITIVE + 9),
25+
/** A real number with variable precision, 8 bytes in size. Can't be used in the primary key */
26+
Double(YdbConst.SQL_KIND_PRIMITIVE + 10),
27+
/** A binary data, synonym for YDB type String */
28+
Bytes(YdbConst.SQL_KIND_PRIMITIVE + 11),
29+
/** Text encoded in UTF-8, synonym for YDB type Utf8 */
30+
Text(YdbConst.SQL_KIND_PRIMITIVE + 12),
31+
/** YSON in a textual or binary representation. Doesn't support matching, can't be used in the primary key */
32+
Yson(YdbConst.SQL_KIND_PRIMITIVE + 13),
33+
/** JSON represented as text. Doesn't support matching, can't be used in the primary key */
34+
Json(YdbConst.SQL_KIND_PRIMITIVE + 14),
35+
/** Universally unique identifier UUID. Not supported for table columns */
36+
Uuid(YdbConst.SQL_KIND_PRIMITIVE + 15),
37+
/** Date, precision to the day */
38+
Date(YdbConst.SQL_KIND_PRIMITIVE + 16),
39+
/** Date/time, precision to the second */
40+
Datetime(YdbConst.SQL_KIND_PRIMITIVE + 17),
41+
/** Date/time, precision to the microsecond */
42+
Timestamp(YdbConst.SQL_KIND_PRIMITIVE + 18),
43+
/** Time interval (signed), precision to microseconds */
44+
Interval(YdbConst.SQL_KIND_PRIMITIVE + 19),
45+
/** Date with time zone label, precision to the day */
46+
TzDate(YdbConst.SQL_KIND_PRIMITIVE + 20),
47+
/** Date/time with time zone label, precision to the second */
48+
TzDatetime(YdbConst.SQL_KIND_PRIMITIVE + 21),
49+
/** Date/time with time zone label, precision to the microsecond */
50+
TzTimestamp(YdbConst.SQL_KIND_PRIMITIVE + 22),
51+
/** JSON in an indexed binary representation. Doesn't support matching, can't be used in the primary key */
52+
JsonDocument(YdbConst.SQL_KIND_PRIMITIVE + 23),
53+
54+
// DyNumber(YdbConst.SQL_KIND_PRIMITIVE + 24), -- not supported by JDBC Driver
55+
56+
Date32(YdbConst.SQL_KIND_PRIMITIVE + 25),
57+
58+
Datetime64(YdbConst.SQL_KIND_PRIMITIVE + 26),
59+
60+
Timestamp64(YdbConst.SQL_KIND_PRIMITIVE + 27),
61+
62+
Interval64(YdbConst.SQL_KIND_PRIMITIVE + 28),
63+
64+
Decimal(YdbConst.SQL_DEFAULT_DECIMAL); // special case
65+
66+
private final int sqlType;
67+
68+
private YQLType(int sqlType) {
69+
this.sqlType = sqlType;
1970
}
2071

21-
@Override
22-
public Integer getVendorTypeNumber() {
23-
return YdbConst.SQL_KIND_PRIMITIVE + type.ordinal();
72+
public int getSqlType() {
73+
return this.sqlType;
2474
}
2575
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package tech.ydb.data.core.convert;
2+
3+
/**
4+
* That class contain custom YDB type codes
5+
* @see <a href="https://github.com/ydb-platform/ydb-jdbc-driver/blob/3d74021/jdbc/src/main/java/tech/ydb/jdbc/YdbConst.java#L8-L9">JDBC Driver constants</a>
6+
* @see <a href="https://github.com/ydb-platform/ydb-jdbc-driver/blob/3d74021/jdbc/src/main/java/tech/ydb/jdbc/impl/YdbTypes.java#L37-L66">Primitive types</a>
7+
* @see <a href="https://github.com/ydb-platform/ydb-jdbc-driver/blob/3d74021/jdbc/src/main/java/tech/ydb/jdbc/impl/YdbTypes.java#L138-L144">Decimal type</a>
8+
*
9+
* @author Aleksandr Gorshenin
10+
*/
11+
final class YdbConst {
12+
public static final int SQL_KIND_PRIMITIVE = 10000;
13+
public static final int SQL_DEFAULT_DECIMAL = ydbDecimal(22, 9);
14+
private static final int SQL_KIND_DECIMAL = 1 << 14; // 16384
15+
16+
public static int ydbDecimal(int precision, int scale) {
17+
return SQL_KIND_DECIMAL + (precision << 6) + (scale & 0x111111);
18+
}
19+
20+
private YdbConst() { };
21+
}
Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,57 @@
11
package tech.ydb.data.core.convert;
22

33
import java.sql.SQLType;
4+
import java.util.concurrent.ConcurrentHashMap;
5+
import java.util.concurrent.ConcurrentMap;
6+
47
import org.springframework.data.convert.CustomConversions;
58
import org.springframework.data.jdbc.core.convert.JdbcTypeFactory;
69
import org.springframework.data.jdbc.core.convert.MappingJdbcConverter;
710
import org.springframework.data.jdbc.core.convert.RelationResolver;
811
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
912
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
10-
import tech.ydb.table.values.PrimitiveType;
13+
14+
import tech.ydb.data.core.convert.annotation.YdbType;
15+
1116

1217
/**
1318
* @author Madiyar Nurgazin
19+
* @author Mikhail Polivakha
1420
*/
21+
@SuppressWarnings("removal")
1522
public class YdbMappingJdbcConverter extends MappingJdbcConverter {
23+
private final static Class<YdbType> ANNOTATION = YdbType.class;
24+
private final static Class<tech.ydb.data.core.convert.YdbType> OLD_TYPE = tech.ydb.data.core.convert.YdbType.class;
25+
26+
private final ConcurrentMap<RelationalPersistentProperty, SQLType> typesCache = new ConcurrentHashMap<>();
27+
1628
public YdbMappingJdbcConverter(RelationalMappingContext context, RelationResolver relationResolver,
1729
CustomConversions conversions, JdbcTypeFactory typeFactory) {
1830
super(context, relationResolver, conversions, typeFactory);
1931
}
2032

2133
@Override
2234
public SQLType getTargetSqlType(RelationalPersistentProperty property) {
23-
return property.isAnnotationPresent(YdbType.class) ?
24-
new YQLType(PrimitiveType.valueOf(property.getRequiredAnnotation(YdbType.class).value())) :
25-
super.getTargetSqlType(property);
35+
return typesCache.computeIfAbsent(property, this::resolveSqlType);
36+
}
37+
38+
private SQLType resolveSqlType(RelationalPersistentProperty property) {
39+
if (property.isAnnotationPresent(ANNOTATION)) {
40+
tech.ydb.data.core.convert.annotation.YdbType type = property.getRequiredAnnotation(ANNOTATION);
41+
YQLType yql = type.value();
42+
if (yql == YQLType.Decimal) {
43+
int precision = type.decimalPrecision();
44+
int scale = type.decimalScale();
45+
return new YdbSqlType(precision, scale);
46+
}
47+
return new YdbSqlType(yql);
48+
}
49+
50+
if (property.isAnnotationPresent(OLD_TYPE)) {
51+
String typeName = property.getRequiredAnnotation(OLD_TYPE).value();
52+
return new YdbSqlType(YQLType.valueOf(typeName));
53+
}
54+
55+
return super.getTargetSqlType(property);
2656
}
2757
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package tech.ydb.data.core.convert;
2+
3+
import java.io.Serializable;
4+
import java.sql.SQLType;
5+
6+
/**
7+
*
8+
* @author Aleksandr Gorshenin
9+
*/
10+
class YdbSqlType implements SQLType, Serializable {
11+
private static final long serialVersionUID = -5722445668088782880L;
12+
13+
private final String name;
14+
private final int vendorCode;
15+
16+
public YdbSqlType(YQLType type) {
17+
this.name = type.name();
18+
this.vendorCode = type.getSqlType();
19+
}
20+
21+
public YdbSqlType(int decimalPrecision, int decimalScale) {
22+
this.name = "Decimal(" + decimalPrecision + "," + decimalScale + ")";
23+
this.vendorCode = YdbConst.ydbDecimal(decimalPrecision, decimalScale);
24+
}
25+
26+
@Override
27+
public String getName() {
28+
return name;
29+
}
30+
31+
@Override
32+
public String getVendor() {
33+
return "YDB";
34+
}
35+
36+
@Override
37+
public Integer getVendorTypeNumber() {
38+
return vendorCode;
39+
}
40+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,26 @@
11
package tech.ydb.data.core.convert;
22

3+
import java.lang.annotation.Documented;
34
import java.lang.annotation.ElementType;
45
import java.lang.annotation.Retention;
56
import java.lang.annotation.RetentionPolicy;
67
import java.lang.annotation.Target;
78

89
/**
10+
* The annotation for qualification of the target YDB data type.
11+
*
912
* @author Madiyar Nurgazin
13+
* @author Mikhail Polivakha
14+
* @deprecated Please, use {@link tech.ydb.data.core.convert.annotation.YdbType} instead because of type safety considerations.
1015
*/
1116
@Retention(RetentionPolicy.RUNTIME)
1217
@Target(ElementType.FIELD)
18+
@Deprecated(forRemoval = true)
19+
@Documented
1320
public @interface YdbType {
21+
/**
22+
* The target YDB data type.
23+
* @return name of YDB data type
24+
*/
1425
String value();
1526
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package tech.ydb.data.core.convert.annotation;
2+
3+
import java.lang.annotation.Documented;
4+
import java.lang.annotation.ElementType;
5+
import java.lang.annotation.Retention;
6+
import java.lang.annotation.RetentionPolicy;
7+
import java.lang.annotation.Target;
8+
9+
import tech.ydb.data.core.convert.YQLType;
10+
11+
12+
/**
13+
* The annotation for qualification of the target YDB data type.
14+
*
15+
* @author Mikhail Polivakha
16+
* @author Aleksandr Gorshenin
17+
*/
18+
@Retention(RetentionPolicy.RUNTIME)
19+
@Target(ElementType.FIELD)
20+
@Documented
21+
public @interface YdbType {
22+
/**
23+
* The target YDB data type.
24+
* @return The target YDB data type.
25+
*/
26+
YQLType value();
27+
28+
/**
29+
* Decimal precision. Applies only to {@link YQLType#Decimal }
30+
* @return Custom decimal type precision.
31+
*/
32+
int decimalPrecision() default 22;
33+
34+
/**
35+
* Decimal scale. Applies only to {@link YQLType#Decimal }
36+
* @return Custom decimal type scale.
37+
*/
38+
int decimalScale() default 9;
39+
}

spring-data-jdbc-ydb/src/test/java/tech/ydb/data/all_types_table/AllTypesTableTest.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@
77
import java.time.temporal.ChronoUnit;
88
import java.util.List;
99
import java.util.Optional;
10+
1011
import org.junit.jupiter.api.Assertions;
1112
import org.junit.jupiter.api.Test;
1213
import org.springframework.beans.factory.annotation.Autowired;
1314
import org.springframework.data.jdbc.core.JdbcAggregateOperations;
1415
import org.springframework.data.relational.core.conversion.DbActionExecutionException;
16+
1517
import tech.ydb.data.YdbBaseTest;
1618
import tech.ydb.data.all_types_table.entity.AllTypesEntity;
1719
import tech.ydb.data.all_types_table.repository.AllTypesEntityRepository;
@@ -41,6 +43,7 @@ public void allTypesTableCrudTest() {
4143
);
4244
repository.save(expected);
4345

46+
Assertions.assertEquals(expected.getDecimalColumn(), entity1.get().getDecimalColumn());
4447
Assertions.assertEquals(expected, entity1.get());
4548
Assertions.assertEquals(expected.getTextColumn(),
4649
repository.findAllByTextColumn("Madiyar Nurgazin").get(0).getTextColumn());
@@ -69,10 +72,11 @@ public void allTypesTableCrudTest() {
6972

7073
entities = repository.findAllByDateColumnAfterNow();
7174
Assertions.assertEquals(1, entities.size());
72-
Assertions.assertEquals(4, entities.get(0).getId());
75+
Assertions.assertEquals(Integer.valueOf(4), entities.get(0).getId());
7376

7477
entity3.setJsonColumn("Not json");
75-
Assertions.assertThrows(DbActionExecutionException.class, () -> repository.save(entity3));
78+
var ex = Assertions.assertThrows(DbActionExecutionException.class, () -> repository.save(entity3));
79+
Assertions.assertTrue(ex.getMessage().startsWith("Failed to execute DbAction.UpdateRoot"));
7680

7781
entity3.setJsonColumn("{\"values\": [1, 2, 3]}");
7882
AllTypesEntity updated = repository.save(entity3);

0 commit comments

Comments
 (0)