From e940f1d682e249a43e3d8d636194653bc33959e7 Mon Sep 17 00:00:00 2001 From: Kirill Kurdyukov Date: Fri, 8 Nov 2024 12:16:48 +0300 Subject: [PATCH] feat: support Decimal($p, $s) (#162) --- hibernate-dialect/pom.xml | 6 +- .../ydb/hibernate/dialect/YdbDialect.java | 54 ++++++- .../hibernate/dialect/code/YdbJdbcCode.java | 140 ++++++++++++++++++ .../dialect/types/BigDecimalJavaType.java | 22 +++ .../dialect/types/DecimalJdbcType.java | 40 +++++ .../dialect/types/Uint8JdbcType.java | 69 +++++++++ .../java/tech/ydb/hibernate/TestUtils.java | 14 +- .../hibernate/ydb_jdbc_code/TestEntity.java | 42 ++++++ .../ydb_jdbc_code/YdbJdbcCodeTests.java | 65 ++++++++ 9 files changed, 432 insertions(+), 20 deletions(-) create mode 100644 hibernate-dialect/src/main/java/tech/ydb/hibernate/dialect/code/YdbJdbcCode.java create mode 100644 hibernate-dialect/src/main/java/tech/ydb/hibernate/dialect/types/BigDecimalJavaType.java create mode 100644 hibernate-dialect/src/main/java/tech/ydb/hibernate/dialect/types/DecimalJdbcType.java create mode 100644 hibernate-dialect/src/main/java/tech/ydb/hibernate/dialect/types/Uint8JdbcType.java create mode 100644 hibernate-dialect/src/test/java/tech/ydb/hibernate/ydb_jdbc_code/TestEntity.java create mode 100644 hibernate-dialect/src/test/java/tech/ydb/hibernate/ydb_jdbc_code/YdbJdbcCodeTests.java diff --git a/hibernate-dialect/pom.xml b/hibernate-dialect/pom.xml index c87edbd..3c8e9d7 100644 --- a/hibernate-dialect/pom.xml +++ b/hibernate-dialect/pom.xml @@ -41,8 +41,8 @@ 5.9.3 2.17.2 - 2.2.6 - 2.2.4 + 2.3.4 + 2.3.3 @@ -145,6 +145,8 @@ true + enable_parameterized_decimal + cr.yandex/yc/yandex-docker-local-ydb:trunk diff --git a/hibernate-dialect/src/main/java/tech/ydb/hibernate/dialect/YdbDialect.java b/hibernate-dialect/src/main/java/tech/ydb/hibernate/dialect/YdbDialect.java index 9dd8917..cd123a2 100644 --- a/hibernate-dialect/src/main/java/tech/ydb/hibernate/dialect/YdbDialect.java +++ b/hibernate-dialect/src/main/java/tech/ydb/hibernate/dialect/YdbDialect.java @@ -2,6 +2,7 @@ import java.time.LocalDateTime; import java.util.List; +import java.util.concurrent.ConcurrentHashMap; import org.hibernate.boot.model.FunctionContributions; import org.hibernate.boot.model.TypeContributions; import org.hibernate.dialect.Dialect; @@ -51,14 +52,20 @@ import static org.hibernate.type.SqlTypes.VARBINARY; import static org.hibernate.type.SqlTypes.VARCHAR; import org.hibernate.type.StandardBasicTypes; +import org.hibernate.type.descriptor.jdbc.JdbcType; +import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry; import org.hibernate.type.descriptor.sql.internal.DdlTypeImpl; import org.hibernate.type.descriptor.sql.spi.DdlTypeRegistry; +import tech.ydb.hibernate.dialect.code.YdbJdbcCode; +import static tech.ydb.hibernate.dialect.code.YdbJdbcCode.DECIMAL_SHIFT; import tech.ydb.hibernate.dialect.exporter.EmptyExporter; import tech.ydb.hibernate.dialect.exporter.YdbIndexExporter; import tech.ydb.hibernate.dialect.hint.IndexQueryHintHandler; import tech.ydb.hibernate.dialect.hint.QueryHintHandler; import tech.ydb.hibernate.dialect.hint.ScanQueryHintHandler; import tech.ydb.hibernate.dialect.translator.YdbSqlAstTranslatorFactory; +import tech.ydb.hibernate.dialect.types.BigDecimalJavaType; +import tech.ydb.hibernate.dialect.types.DecimalJdbcType; import tech.ydb.hibernate.dialect.types.InstantJavaType; import tech.ydb.hibernate.dialect.types.InstantJdbcType; import tech.ydb.hibernate.dialect.types.LocalDateJavaType; @@ -66,18 +73,19 @@ import tech.ydb.hibernate.dialect.types.LocalDateTimeJavaType; import tech.ydb.hibernate.dialect.types.LocalDateTimeJdbcType; import static tech.ydb.hibernate.dialect.types.LocalDateTimeJdbcType.JDBC_TYPE_DATETIME_CODE; +import tech.ydb.hibernate.dialect.types.Uint8JdbcType; /** * @author Kirill Kurdyukov */ public class YdbDialect extends Dialect { - private static final Exporter FOREIGN_KEY_EMPTY_EXPORTER = new EmptyExporter<>(); private static final Exporter UNIQUE_KEY_EMPTY_EXPORTER = new EmptyExporter<>(); private static final List QUERY_HINT_HANDLERS = List.of( IndexQueryHintHandler.INSTANCE, ScanQueryHintHandler.INSTANCE ); + private static final ConcurrentHashMap DECIMAL_JDBC_TYPE_CACHE = new ConcurrentHashMap<>(); public YdbDialect(DialectResolutionInfo dialectResolutionInfo) { super(dialectResolutionInfo); @@ -93,7 +101,7 @@ protected String columnType(int sqlTypeCode) { case BIGINT -> "Int64"; case REAL, FLOAT -> "Float"; case DOUBLE -> "Double"; - case NUMERIC, DECIMAL -> "Decimal (22,9)"; // Fixed + case NUMERIC, DECIMAL -> "Decimal($p, $s)"; case DATE -> "Date"; case JDBC_TYPE_DATETIME_CODE -> "Datetime"; case TIME_WITH_TIMEZONE -> "TzDateTime"; @@ -117,6 +125,14 @@ public void contributeTypes(TypeContributions typeContributions, ServiceRegistry typeContributions.contributeJdbcType(LocalDateJdbcType.INSTANCE); typeContributions.contributeJavaType(InstantJavaType.INSTANCE); typeContributions.contributeJdbcType(InstantJdbcType.INSTANCE); + typeContributions.contributeJdbcType(new DecimalJdbcType(YdbJdbcCode.DECIMAL_22_9)); + typeContributions.contributeJdbcType(new DecimalJdbcType(YdbJdbcCode.DECIMAL_31_9)); + typeContributions.contributeJdbcType(new DecimalJdbcType(YdbJdbcCode.DECIMAL_35_0)); + typeContributions.contributeJdbcType(new DecimalJdbcType(YdbJdbcCode.DECIMAL_35_9)); + + // custom jdbc codec + typeContributions.contributeJdbcType(Uint8JdbcType.INSTANCE); + typeContributions.contributeJavaType(BigDecimalJavaType.INSTANCE_22_9); } @Override @@ -125,7 +141,33 @@ protected void registerColumnTypes(TypeContributions typeContributions, ServiceR final DdlTypeRegistry ddlTypeRegistry = typeContributions.getTypeConfiguration().getDdlTypeRegistry(); - ddlTypeRegistry.addDescriptor(new DdlTypeImpl(JDBC_TYPE_DATETIME_CODE, "Datetime", "Datetime", this)); + ddlTypeRegistry.addDescriptor(new DdlTypeImpl(YdbJdbcCode.DATETIME, "Datetime", "Datetime", this)); + ddlTypeRegistry.addDescriptor(new DdlTypeImpl(YdbJdbcCode.UINT8, "Uint8", "Uint8", this)); + ddlTypeRegistry.addDescriptor(new DdlTypeImpl(YdbJdbcCode.DECIMAL_22_9, "Decimal(22, 9)", "Decimal(22, 9)", this)); + ddlTypeRegistry.addDescriptor(new DdlTypeImpl(YdbJdbcCode.DECIMAL_31_9, "Decimal(31, 9)", "Decimal(31, 9)", this)); + ddlTypeRegistry.addDescriptor(new DdlTypeImpl(YdbJdbcCode.DECIMAL_35_0, "Decimal(35, 0)", "Decimal(35, 0)", this)); + ddlTypeRegistry.addDescriptor(new DdlTypeImpl(YdbJdbcCode.DECIMAL_35_9, "Decimal(35, 9)", "Decimal(35, 9)", this)); + } + + @Override + public JdbcType resolveSqlTypeDescriptor( + String columnTypeName, + int jdbcTypeCode, + int precision, + int scale, + JdbcTypeRegistry jdbcTypeRegistry) { + if ((jdbcTypeCode == NUMERIC || jdbcTypeCode == DECIMAL) && (precision != 0 || scale != 0)) { + int sqlCode = DECIMAL_SHIFT + (precision << 6) + scale; + + return DECIMAL_JDBC_TYPE_CACHE.computeIfAbsent(sqlCode, DecimalJdbcType::new); + } + + return super.resolveSqlTypeDescriptor(columnTypeName, jdbcTypeCode, precision, scale, jdbcTypeRegistry); + } + + @Override + public int getDefaultDecimalPrecision() { + return 22; } @Override @@ -139,11 +181,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio functionContributions.getFunctionRegistry().register( "current_time", - new CurrentFunction( - "current_time", - currentTime(), - localDateTimeType - ) + new CurrentFunction("current_time", currentTime(), localDateTimeType) ); } diff --git a/hibernate-dialect/src/main/java/tech/ydb/hibernate/dialect/code/YdbJdbcCode.java b/hibernate-dialect/src/main/java/tech/ydb/hibernate/dialect/code/YdbJdbcCode.java new file mode 100644 index 0000000..9d52f06 --- /dev/null +++ b/hibernate-dialect/src/main/java/tech/ydb/hibernate/dialect/code/YdbJdbcCode.java @@ -0,0 +1,140 @@ +package tech.ydb.hibernate.dialect.code; + +/** + * @author Kirill Kurdyukov + */ +public final class YdbJdbcCode { + + /** + * Boolean value. + */ + public static final int BOOL = 10000; + + /** + * A signed integer. Acceptable values: from -2^7 to 2^7–1. Not supported for table columns + */ + public static final int INT8 = 10001; + + /** + * An unsigned integer. Acceptable values: from 0 to 2^8–1. + */ + public static final int UINT8 = 10002; + + /** + * A signed integer. Acceptable values: from –2^15 to 2^15–1. Not supported for table columns + */ + public static final int INT16 = 10003; + + /** + * An unsigned integer. Acceptable values: from 0 to 2^16–1. Not supported for table columns + */ + public static final int UINT16 = 10004; + + /** + * A signed integer. Acceptable values: from –2^31 to 2^31–1. + */ + public static final int INT32 = 10005; + + /** + * An unsigned integer. Acceptable values: from 0 to 2^32–1. + */ + public static final int UINT32 = 10006; + + /** + * A signed integer. Acceptable values: from –2^63 to 2^63–1. + */ + public static final int INT64 = 10007; + + /** + * An unsigned integer. Acceptable values: from 0 to 2^64–1. + */ + public static final int UINT64 = 10008; + + /** + * A real number with variable precision, 4 bytes in size. Can't be used in the primary key + */ + public static final int FLOAT = 10009; + + /** + * A real number with variable precision, 8 bytes in size. Can't be used in the primary key + */ + public static final int DOUBLE = 10010; + + /** + * A binary data, synonym for YDB type String + */ + public static final int BYTES = 10011; + + /** + * Text encoded in UTF-8, synonym for YDB type Utf8 + */ + public static final int TEXT = 10012; + + /** + * YSON in a textual or binary representation. Doesn't support matching, can't be used in the primary key + */ + public static final int YSON = 10013; + + /** + * JSON represented as text. Doesn't support matching, can't be used in the primary key + */ + public static final int JSON = 10014; + + /** + * Universally unique identifier UUID. Not supported for table columns + */ + public static final int UUID = 10015; + + /** + * Date, precision to the day + */ + public static final int DATE = 10016; + + /** + * Date/time, precision to the second + */ + public static final int DATETIME = 10017; + + /** + * Date/time, precision to the microsecond + */ + public static final int TIMESTAMP = 10018; + + /** + * Time interval (signed), precision to microseconds + */ + public static final int INTERVAL = 10019; + + /** + * Date with time zone label, precision to the day + */ + public static final int TZ_DATE = 10020; + + /** + * Date/time with time zone label, precision to the second + */ + public static final int TZ_DATETIME = 10021; + + /** + * Date/time with time zone label, precision to the microsecond + */ + public static final int TZ_TIMESTAMP = 10022; + + /** + * JSON in an indexed binary representation. Doesn't support matching, can't be used in the primary key + */ + public static final int JSON_DOCUMENT = 10023; + + public static final int DECIMAL_SHIFT = (1 << 14); + + /** + * link + */ + public static final int DECIMAL_22_9 = DECIMAL_SHIFT + (22 << 6) + 9; + + public static final int DECIMAL_31_9 = DECIMAL_SHIFT + (31 << 6) + 9; + + public static final int DECIMAL_35_0 = DECIMAL_SHIFT + (35 << 6); + + public static final int DECIMAL_35_9 = DECIMAL_SHIFT + (35 << 6) + 9; +} diff --git a/hibernate-dialect/src/main/java/tech/ydb/hibernate/dialect/types/BigDecimalJavaType.java b/hibernate-dialect/src/main/java/tech/ydb/hibernate/dialect/types/BigDecimalJavaType.java new file mode 100644 index 0000000..ffedd03 --- /dev/null +++ b/hibernate-dialect/src/main/java/tech/ydb/hibernate/dialect/types/BigDecimalJavaType.java @@ -0,0 +1,22 @@ +package tech.ydb.hibernate.dialect.types; + +import org.hibernate.dialect.Dialect; +import org.hibernate.type.descriptor.jdbc.JdbcType; + +/** + * @author Kirill Kurdyukov + */ +public final class BigDecimalJavaType extends org.hibernate.type.descriptor.java.BigDecimalJavaType { + + public static final BigDecimalJavaType INSTANCE_22_9 = new BigDecimalJavaType(); + + @Override + public int getDefaultSqlScale(Dialect dialect, JdbcType jdbcType) { + return 9; + } + + @Override + public int getDefaultSqlPrecision(Dialect dialect, JdbcType jdbcType) { + return 22; + } +} diff --git a/hibernate-dialect/src/main/java/tech/ydb/hibernate/dialect/types/DecimalJdbcType.java b/hibernate-dialect/src/main/java/tech/ydb/hibernate/dialect/types/DecimalJdbcType.java new file mode 100644 index 0000000..a0da692 --- /dev/null +++ b/hibernate-dialect/src/main/java/tech/ydb/hibernate/dialect/types/DecimalJdbcType.java @@ -0,0 +1,40 @@ +package tech.ydb.hibernate.dialect.types; + +import java.math.BigDecimal; +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import org.hibernate.type.descriptor.ValueBinder; +import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.JavaType; + +/** + * @author Kirill Kurdyukov + */ +public class DecimalJdbcType extends org.hibernate.type.descriptor.jdbc.DecimalJdbcType { + private final int sqlCode; + + public DecimalJdbcType(int sqlCode) { + this.sqlCode = sqlCode; + } + + @Override + public int getJdbcTypeCode() { + return sqlCode; + } + + @Override + public ValueBinder getBinder(final JavaType javaType) { + return new ValueBinder<>() { + @Override + public void bind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException { + st.setObject(index, javaType.unwrap(value, BigDecimal.class, options), sqlCode); + } + + @Override + public void bind(CallableStatement st, X value, String name, WrapperOptions options) throws SQLException { + st.setObject(name, javaType.unwrap(value, BigDecimal.class, options), sqlCode); + } + }; + } +} diff --git a/hibernate-dialect/src/main/java/tech/ydb/hibernate/dialect/types/Uint8JdbcType.java b/hibernate-dialect/src/main/java/tech/ydb/hibernate/dialect/types/Uint8JdbcType.java new file mode 100644 index 0000000..a2ee5e0 --- /dev/null +++ b/hibernate-dialect/src/main/java/tech/ydb/hibernate/dialect/types/Uint8JdbcType.java @@ -0,0 +1,69 @@ +package tech.ydb.hibernate.dialect.types; + + +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import org.hibernate.type.descriptor.ValueBinder; +import org.hibernate.type.descriptor.ValueExtractor; +import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.JavaType; +import org.hibernate.type.descriptor.jdbc.JdbcType; +import org.hibernate.type.spi.TypeConfiguration; +import tech.ydb.hibernate.dialect.code.YdbJdbcCode; + +/** + * @author Kirill Kurdyukov + */ +public class Uint8JdbcType implements JdbcType { + public static final Uint8JdbcType INSTANCE = new Uint8JdbcType(); + + @Override + public int getJdbcTypeCode() { + return YdbJdbcCode.UINT8; + } + + @Override + public JavaType getJdbcRecommendedJavaTypeMapping( + Integer precision, + Integer scale, + TypeConfiguration typeConfiguration) { + return typeConfiguration.getJavaTypeRegistry().getDescriptor(Integer.class); + } + + @Override + public ValueBinder getBinder(JavaType javaType) { + return new ValueBinder<>() { + @Override + public void bind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException { + st.setObject(index, javaType.unwrap(value, Integer.class, options), getJdbcTypeCode()); + } + + @Override + public void bind(CallableStatement st, X value, String name, WrapperOptions options) throws SQLException { + st.setObject(name, javaType.unwrap(value, Integer.class, options), getJdbcTypeCode()); + } + }; + } + + @Override + public ValueExtractor getExtractor(JavaType javaType) { + return new ValueExtractor<>() { + @Override + public X extract(ResultSet rs, int paramIndex, WrapperOptions options) throws SQLException { + return javaType.wrap(rs.getObject(paramIndex), options); + } + + @Override + public X extract(CallableStatement statement, int paramIndex, WrapperOptions options) throws SQLException { + return javaType.wrap(statement.getObject(paramIndex), options); + } + + @Override + public X extract(CallableStatement statement, String paramName, WrapperOptions options) throws SQLException { + return javaType.wrap(statement.getObject(paramName), options); + } + }; + } +} diff --git a/hibernate-dialect/src/test/java/tech/ydb/hibernate/TestUtils.java b/hibernate-dialect/src/test/java/tech/ydb/hibernate/TestUtils.java index a120bea..5e1860c 100644 --- a/hibernate-dialect/src/test/java/tech/ydb/hibernate/TestUtils.java +++ b/hibernate-dialect/src/test/java/tech/ydb/hibernate/TestUtils.java @@ -28,16 +28,10 @@ public static Configuration basedConfiguration() { } public static String jdbcUrl(YdbHelperExtension ydb) { - StringBuilder jdbc = new StringBuilder("jdbc:ydb:") - .append(ydb.useTls() ? "grpcs://" : "grpc://") - .append(ydb.endpoint()) - .append(ydb.database()); - - if (ydb.authToken() != null) { - jdbc.append("?").append("token=").append(ydb.authToken()); - } - - return jdbc.toString(); + return "jdbc:ydb:" + + (ydb.useTls() ? "grpcs://" : "grpc://") + + ydb.endpoint() + + ydb.database(); } public static void inTransaction(Consumer work) { diff --git a/hibernate-dialect/src/test/java/tech/ydb/hibernate/ydb_jdbc_code/TestEntity.java b/hibernate-dialect/src/test/java/tech/ydb/hibernate/ydb_jdbc_code/TestEntity.java new file mode 100644 index 0000000..c5c3cc0 --- /dev/null +++ b/hibernate-dialect/src/test/java/tech/ydb/hibernate/ydb_jdbc_code/TestEntity.java @@ -0,0 +1,42 @@ +package tech.ydb.hibernate.ydb_jdbc_code; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import java.math.BigDecimal; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.JdbcTypeCode; +import tech.ydb.hibernate.dialect.code.YdbJdbcCode; + +/** + * @author Kirill Kurdyukov + */ +@Entity +@Data +@AllArgsConstructor +@NoArgsConstructor +@Table(name = "hibernate_test_bigdecimal") +public class TestEntity { + + @Id + @JdbcTypeCode(YdbJdbcCode.UINT8) + private int id; + + @Column + private BigDecimal default_bigDecimal; + + @Column + @JdbcTypeCode(YdbJdbcCode.DECIMAL_31_9) + private BigDecimal bigDecimal31_9; + + @Column + @JdbcTypeCode(YdbJdbcCode.DECIMAL_35_0) + private BigDecimal bigDecimal35_0; + + @Column + @JdbcTypeCode(YdbJdbcCode.DECIMAL_35_9) + private BigDecimal bigDecimal35_9; +} diff --git a/hibernate-dialect/src/test/java/tech/ydb/hibernate/ydb_jdbc_code/YdbJdbcCodeTests.java b/hibernate-dialect/src/test/java/tech/ydb/hibernate/ydb_jdbc_code/YdbJdbcCodeTests.java new file mode 100644 index 0000000..0c4b924 --- /dev/null +++ b/hibernate-dialect/src/test/java/tech/ydb/hibernate/ydb_jdbc_code/YdbJdbcCodeTests.java @@ -0,0 +1,65 @@ +package tech.ydb.hibernate.ydb_jdbc_code; + +import java.math.BigDecimal; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.exception.GenericJDBCException; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import static tech.ydb.hibernate.TestUtils.SESSION_FACTORY; +import static tech.ydb.hibernate.TestUtils.basedConfiguration; +import static tech.ydb.hibernate.TestUtils.inTransaction; +import static tech.ydb.hibernate.TestUtils.jdbcUrl; +import tech.ydb.test.junit5.YdbHelperExtension; + +/** + * @author Kirill Kurdyukov + */ +public class YdbJdbcCodeTests { + + @RegisterExtension + private static final YdbHelperExtension ydb = new YdbHelperExtension(); + + @Test + public void integrationTests() { + SESSION_FACTORY = basedConfiguration() + .addAnnotatedClass(TestEntity.class) + .setProperty(AvailableSettings.URL, jdbcUrl(ydb) + "?disablePrepareDataQuery=true") + .buildSessionFactory(); + + /* + create table hibernate_test ( + default_bigDecimal Decimal(22, 9), + bigDecimal31_9 Decimal(31, 9), + bigDecimal35_0 Decimal(35, 0), + bigDecimal35_9 Decimal(35, 9), + id Uint8 not null, + primary key (id) + ) + */ + var testEntity = new TestEntity( + 1, + new BigDecimal("123.000000000"), + new BigDecimal("123.000000000"), + new BigDecimal("12345678123"), + new BigDecimal("12345678123.000000000") + ); + + inTransaction(session -> session.persist(testEntity)); + inTransaction(session -> assertEquals(testEntity, session.find(TestEntity.class, 1))); + + testEntity.setBigDecimal35_0(new BigDecimal(123)); + + inTransaction(session -> session.merge(testEntity)); + inTransaction(session -> assertEquals(testEntity, session.find(TestEntity.class, 1))); + + testEntity.setBigDecimal31_9(new BigDecimal("123123456781231234567812312345678123123456781231234567812312345678123123456781231234567812312345678123")); + + assertThrows(GenericJDBCException.class, () -> inTransaction(session -> session.merge(testEntity)), "Unable to bind parameter #1 - 123123456781231234567812312345678123123456781231234567812312345678123123456781231234567812312345678123 [Cannot cast to decimal type Decimal(31, 9): [class java.math.BigDecimal: 123123456781231234567812312345678123123456781231234567812312345678123123456781231234567812312345678123] is Infinite] [n/a]"); + + testEntity.setBigDecimal31_9(new BigDecimal("123451234512345123451234512345123")); + + assertThrows(GenericJDBCException.class, () -> inTransaction(session -> session.merge(testEntity)), "Unable to bind parameter #1 - 123451234512345123451234512345123 [Cannot cast to decimal type Decimal(31, 9): [class java.math.BigDecimal: 123123456781231234567812312345678123123456781231234567812312345678123123456781231234567812312345678123] is Infinite] [n/a]"); + } +}