Skip to content

Commit e940f1d

Browse files
feat: support Decimal($p, $s) (#162)
1 parent 456abee commit e940f1d

File tree

9 files changed

+432
-20
lines changed

9 files changed

+432
-20
lines changed

hibernate-dialect/pom.xml

+4-2
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.2.6</ydb.sdk.version>
45-
<ydb.jdbc.version>2.2.4</ydb.jdbc.version>
44+
<ydb.sdk.version>2.3.4</ydb.sdk.version>
45+
<ydb.jdbc.version>2.3.3</ydb.jdbc.version>
4646
</properties>
4747

4848
<licenses>
@@ -145,6 +145,8 @@
145145
<configuration>
146146
<environmentVariables>
147147
<TESTCONTAINERS_REUSE_ENABLE>true</TESTCONTAINERS_REUSE_ENABLE>
148+
<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>
148150
</environmentVariables>
149151
</configuration>
150152
</plugin>

hibernate-dialect/src/main/java/tech/ydb/hibernate/dialect/YdbDialect.java

+46-8
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import java.time.LocalDateTime;
44
import java.util.List;
5+
import java.util.concurrent.ConcurrentHashMap;
56
import org.hibernate.boot.model.FunctionContributions;
67
import org.hibernate.boot.model.TypeContributions;
78
import org.hibernate.dialect.Dialect;
@@ -51,33 +52,40 @@
5152
import static org.hibernate.type.SqlTypes.VARBINARY;
5253
import static org.hibernate.type.SqlTypes.VARCHAR;
5354
import org.hibernate.type.StandardBasicTypes;
55+
import org.hibernate.type.descriptor.jdbc.JdbcType;
56+
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
5457
import org.hibernate.type.descriptor.sql.internal.DdlTypeImpl;
5558
import org.hibernate.type.descriptor.sql.spi.DdlTypeRegistry;
59+
import tech.ydb.hibernate.dialect.code.YdbJdbcCode;
60+
import static tech.ydb.hibernate.dialect.code.YdbJdbcCode.DECIMAL_SHIFT;
5661
import tech.ydb.hibernate.dialect.exporter.EmptyExporter;
5762
import tech.ydb.hibernate.dialect.exporter.YdbIndexExporter;
5863
import tech.ydb.hibernate.dialect.hint.IndexQueryHintHandler;
5964
import tech.ydb.hibernate.dialect.hint.QueryHintHandler;
6065
import tech.ydb.hibernate.dialect.hint.ScanQueryHintHandler;
6166
import tech.ydb.hibernate.dialect.translator.YdbSqlAstTranslatorFactory;
67+
import tech.ydb.hibernate.dialect.types.BigDecimalJavaType;
68+
import tech.ydb.hibernate.dialect.types.DecimalJdbcType;
6269
import tech.ydb.hibernate.dialect.types.InstantJavaType;
6370
import tech.ydb.hibernate.dialect.types.InstantJdbcType;
6471
import tech.ydb.hibernate.dialect.types.LocalDateJavaType;
6572
import tech.ydb.hibernate.dialect.types.LocalDateJdbcType;
6673
import tech.ydb.hibernate.dialect.types.LocalDateTimeJavaType;
6774
import tech.ydb.hibernate.dialect.types.LocalDateTimeJdbcType;
6875
import static tech.ydb.hibernate.dialect.types.LocalDateTimeJdbcType.JDBC_TYPE_DATETIME_CODE;
76+
import tech.ydb.hibernate.dialect.types.Uint8JdbcType;
6977

7078
/**
7179
* @author Kirill Kurdyukov
7280
*/
7381
public class YdbDialect extends Dialect {
74-
7582
private static final Exporter<ForeignKey> FOREIGN_KEY_EMPTY_EXPORTER = new EmptyExporter<>();
7683
private static final Exporter<Constraint> UNIQUE_KEY_EMPTY_EXPORTER = new EmptyExporter<>();
7784
private static final List<QueryHintHandler> QUERY_HINT_HANDLERS = List.of(
7885
IndexQueryHintHandler.INSTANCE,
7986
ScanQueryHintHandler.INSTANCE
8087
);
88+
private static final ConcurrentHashMap<Integer, DecimalJdbcType> DECIMAL_JDBC_TYPE_CACHE = new ConcurrentHashMap<>();
8189

8290
public YdbDialect(DialectResolutionInfo dialectResolutionInfo) {
8391
super(dialectResolutionInfo);
@@ -93,7 +101,7 @@ protected String columnType(int sqlTypeCode) {
93101
case BIGINT -> "Int64";
94102
case REAL, FLOAT -> "Float";
95103
case DOUBLE -> "Double";
96-
case NUMERIC, DECIMAL -> "Decimal (22,9)"; // Fixed
104+
case NUMERIC, DECIMAL -> "Decimal($p, $s)";
97105
case DATE -> "Date";
98106
case JDBC_TYPE_DATETIME_CODE -> "Datetime";
99107
case TIME_WITH_TIMEZONE -> "TzDateTime";
@@ -117,6 +125,14 @@ public void contributeTypes(TypeContributions typeContributions, ServiceRegistry
117125
typeContributions.contributeJdbcType(LocalDateJdbcType.INSTANCE);
118126
typeContributions.contributeJavaType(InstantJavaType.INSTANCE);
119127
typeContributions.contributeJdbcType(InstantJdbcType.INSTANCE);
128+
typeContributions.contributeJdbcType(new DecimalJdbcType(YdbJdbcCode.DECIMAL_22_9));
129+
typeContributions.contributeJdbcType(new DecimalJdbcType(YdbJdbcCode.DECIMAL_31_9));
130+
typeContributions.contributeJdbcType(new DecimalJdbcType(YdbJdbcCode.DECIMAL_35_0));
131+
typeContributions.contributeJdbcType(new DecimalJdbcType(YdbJdbcCode.DECIMAL_35_9));
132+
133+
// custom jdbc codec
134+
typeContributions.contributeJdbcType(Uint8JdbcType.INSTANCE);
135+
typeContributions.contributeJavaType(BigDecimalJavaType.INSTANCE_22_9);
120136
}
121137

122138
@Override
@@ -125,7 +141,33 @@ protected void registerColumnTypes(TypeContributions typeContributions, ServiceR
125141

126142
final DdlTypeRegistry ddlTypeRegistry = typeContributions.getTypeConfiguration().getDdlTypeRegistry();
127143

128-
ddlTypeRegistry.addDescriptor(new DdlTypeImpl(JDBC_TYPE_DATETIME_CODE, "Datetime", "Datetime", this));
144+
ddlTypeRegistry.addDescriptor(new DdlTypeImpl(YdbJdbcCode.DATETIME, "Datetime", "Datetime", this));
145+
ddlTypeRegistry.addDescriptor(new DdlTypeImpl(YdbJdbcCode.UINT8, "Uint8", "Uint8", this));
146+
ddlTypeRegistry.addDescriptor(new DdlTypeImpl(YdbJdbcCode.DECIMAL_22_9, "Decimal(22, 9)", "Decimal(22, 9)", this));
147+
ddlTypeRegistry.addDescriptor(new DdlTypeImpl(YdbJdbcCode.DECIMAL_31_9, "Decimal(31, 9)", "Decimal(31, 9)", this));
148+
ddlTypeRegistry.addDescriptor(new DdlTypeImpl(YdbJdbcCode.DECIMAL_35_0, "Decimal(35, 0)", "Decimal(35, 0)", this));
149+
ddlTypeRegistry.addDescriptor(new DdlTypeImpl(YdbJdbcCode.DECIMAL_35_9, "Decimal(35, 9)", "Decimal(35, 9)", this));
150+
}
151+
152+
@Override
153+
public JdbcType resolveSqlTypeDescriptor(
154+
String columnTypeName,
155+
int jdbcTypeCode,
156+
int precision,
157+
int scale,
158+
JdbcTypeRegistry jdbcTypeRegistry) {
159+
if ((jdbcTypeCode == NUMERIC || jdbcTypeCode == DECIMAL) && (precision != 0 || scale != 0)) {
160+
int sqlCode = DECIMAL_SHIFT + (precision << 6) + scale;
161+
162+
return DECIMAL_JDBC_TYPE_CACHE.computeIfAbsent(sqlCode, DecimalJdbcType::new);
163+
}
164+
165+
return super.resolveSqlTypeDescriptor(columnTypeName, jdbcTypeCode, precision, scale, jdbcTypeRegistry);
166+
}
167+
168+
@Override
169+
public int getDefaultDecimalPrecision() {
170+
return 22;
129171
}
130172

131173
@Override
@@ -139,11 +181,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
139181

140182
functionContributions.getFunctionRegistry().register(
141183
"current_time",
142-
new CurrentFunction(
143-
"current_time",
144-
currentTime(),
145-
localDateTimeType
146-
)
184+
new CurrentFunction("current_time", currentTime(), localDateTimeType)
147185
);
148186
}
149187

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
package tech.ydb.hibernate.dialect.code;
2+
3+
/**
4+
* @author Kirill Kurdyukov
5+
*/
6+
public final class YdbJdbcCode {
7+
8+
/**
9+
* Boolean value.
10+
*/
11+
public static final int BOOL = 10000;
12+
13+
/**
14+
* A signed integer. Acceptable values: from -2^7 to 2^7–1. Not supported for table columns
15+
*/
16+
public static final int INT8 = 10001;
17+
18+
/**
19+
* An unsigned integer. Acceptable values: from 0 to 2^8–1.
20+
*/
21+
public static final int UINT8 = 10002;
22+
23+
/**
24+
* A signed integer. Acceptable values: from –2^15 to 2^15–1. Not supported for table columns
25+
*/
26+
public static final int INT16 = 10003;
27+
28+
/**
29+
* An unsigned integer. Acceptable values: from 0 to 2^16–1. Not supported for table columns
30+
*/
31+
public static final int UINT16 = 10004;
32+
33+
/**
34+
* A signed integer. Acceptable values: from –2^31 to 2^31–1.
35+
*/
36+
public static final int INT32 = 10005;
37+
38+
/**
39+
* An unsigned integer. Acceptable values: from 0 to 2^32–1.
40+
*/
41+
public static final int UINT32 = 10006;
42+
43+
/**
44+
* A signed integer. Acceptable values: from –2^63 to 2^63–1.
45+
*/
46+
public static final int INT64 = 10007;
47+
48+
/**
49+
* An unsigned integer. Acceptable values: from 0 to 2^64–1.
50+
*/
51+
public static final int UINT64 = 10008;
52+
53+
/**
54+
* A real number with variable precision, 4 bytes in size. Can't be used in the primary key
55+
*/
56+
public static final int FLOAT = 10009;
57+
58+
/**
59+
* A real number with variable precision, 8 bytes in size. Can't be used in the primary key
60+
*/
61+
public static final int DOUBLE = 10010;
62+
63+
/**
64+
* A binary data, synonym for YDB type String
65+
*/
66+
public static final int BYTES = 10011;
67+
68+
/**
69+
* Text encoded in UTF-8, synonym for YDB type Utf8
70+
*/
71+
public static final int TEXT = 10012;
72+
73+
/**
74+
* YSON in a textual or binary representation. Doesn't support matching, can't be used in the primary key
75+
*/
76+
public static final int YSON = 10013;
77+
78+
/**
79+
* JSON represented as text. Doesn't support matching, can't be used in the primary key
80+
*/
81+
public static final int JSON = 10014;
82+
83+
/**
84+
* Universally unique identifier UUID. Not supported for table columns
85+
*/
86+
public static final int UUID = 10015;
87+
88+
/**
89+
* Date, precision to the day
90+
*/
91+
public static final int DATE = 10016;
92+
93+
/**
94+
* Date/time, precision to the second
95+
*/
96+
public static final int DATETIME = 10017;
97+
98+
/**
99+
* Date/time, precision to the microsecond
100+
*/
101+
public static final int TIMESTAMP = 10018;
102+
103+
/**
104+
* Time interval (signed), precision to microseconds
105+
*/
106+
public static final int INTERVAL = 10019;
107+
108+
/**
109+
* Date with time zone label, precision to the day
110+
*/
111+
public static final int TZ_DATE = 10020;
112+
113+
/**
114+
* Date/time with time zone label, precision to the second
115+
*/
116+
public static final int TZ_DATETIME = 10021;
117+
118+
/**
119+
* Date/time with time zone label, precision to the microsecond
120+
*/
121+
public static final int TZ_TIMESTAMP = 10022;
122+
123+
/**
124+
* JSON in an indexed binary representation. Doesn't support matching, can't be used in the primary key
125+
*/
126+
public static final int JSON_DOCUMENT = 10023;
127+
128+
public static final int DECIMAL_SHIFT = (1 << 14);
129+
130+
/**
131+
* <a href="https://github.com/ydb-platform/ydb-jdbc-driver/blob/v2.3.3/jdbc/src/main/java/tech/ydb/jdbc/impl/YdbTypes.java#L37-L66">link</a>
132+
*/
133+
public static final int DECIMAL_22_9 = DECIMAL_SHIFT + (22 << 6) + 9;
134+
135+
public static final int DECIMAL_31_9 = DECIMAL_SHIFT + (31 << 6) + 9;
136+
137+
public static final int DECIMAL_35_0 = DECIMAL_SHIFT + (35 << 6);
138+
139+
public static final int DECIMAL_35_9 = DECIMAL_SHIFT + (35 << 6) + 9;
140+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package tech.ydb.hibernate.dialect.types;
2+
3+
import org.hibernate.dialect.Dialect;
4+
import org.hibernate.type.descriptor.jdbc.JdbcType;
5+
6+
/**
7+
* @author Kirill Kurdyukov
8+
*/
9+
public final class BigDecimalJavaType extends org.hibernate.type.descriptor.java.BigDecimalJavaType {
10+
11+
public static final BigDecimalJavaType INSTANCE_22_9 = new BigDecimalJavaType();
12+
13+
@Override
14+
public int getDefaultSqlScale(Dialect dialect, JdbcType jdbcType) {
15+
return 9;
16+
}
17+
18+
@Override
19+
public int getDefaultSqlPrecision(Dialect dialect, JdbcType jdbcType) {
20+
return 22;
21+
}
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package tech.ydb.hibernate.dialect.types;
2+
3+
import java.math.BigDecimal;
4+
import java.sql.CallableStatement;
5+
import java.sql.PreparedStatement;
6+
import java.sql.SQLException;
7+
import org.hibernate.type.descriptor.ValueBinder;
8+
import org.hibernate.type.descriptor.WrapperOptions;
9+
import org.hibernate.type.descriptor.java.JavaType;
10+
11+
/**
12+
* @author Kirill Kurdyukov
13+
*/
14+
public class DecimalJdbcType extends org.hibernate.type.descriptor.jdbc.DecimalJdbcType {
15+
private final int sqlCode;
16+
17+
public DecimalJdbcType(int sqlCode) {
18+
this.sqlCode = sqlCode;
19+
}
20+
21+
@Override
22+
public int getJdbcTypeCode() {
23+
return sqlCode;
24+
}
25+
26+
@Override
27+
public <X> ValueBinder<X> getBinder(final JavaType<X> javaType) {
28+
return new ValueBinder<>() {
29+
@Override
30+
public void bind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException {
31+
st.setObject(index, javaType.unwrap(value, BigDecimal.class, options), sqlCode);
32+
}
33+
34+
@Override
35+
public void bind(CallableStatement st, X value, String name, WrapperOptions options) throws SQLException {
36+
st.setObject(name, javaType.unwrap(value, BigDecimal.class, options), sqlCode);
37+
}
38+
};
39+
}
40+
}

0 commit comments

Comments
 (0)