From 532d5460d417eeeea1b4140b3bf3915785bf0623 Mon Sep 17 00:00:00 2001 From: Jan Schatteman Date: Thu, 10 Oct 2024 21:25:01 +0200 Subject: [PATCH] HHH-17246 - Guard against Sybase being configured for truncating trailing zeros Signed-off-by: Jan Schatteman --- .../process/spi/MetadataBuildingProcess.java | 3 +- .../org/hibernate/dialect/JsonHelper.java | 2 +- .../org/hibernate/dialect/OracleDialect.java | 3 + .../aggregate/OracleAggregateSupport.java | 2 + .../descriptor/java/OracleUUIDJavaType.java | 27 +++++++ .../type/descriptor/java/UUIDJavaType.java | 2 + .../descriptor/jdbc/UuidAsBinaryJdbcType.java | 60 ++++++++++++++ .../orm/test/id/uuid/SybaseASEUUIDTest.java | 81 +++++++++++++++++++ .../uuid/SybaseUuidAsVarbinaryJdbcType.java | 18 +++++ 9 files changed, 196 insertions(+), 2 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/type/descriptor/java/OracleUUIDJavaType.java create mode 100644 hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/UuidAsBinaryJdbcType.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/id/uuid/SybaseASEUUIDTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/id/uuid/SybaseUuidAsVarbinaryJdbcType.java diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/process/spi/MetadataBuildingProcess.java b/hibernate-core/src/main/java/org/hibernate/boot/model/process/spi/MetadataBuildingProcess.java index dae2bb9137..e69c8fddfb 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/process/spi/MetadataBuildingProcess.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/process/spi/MetadataBuildingProcess.java @@ -90,6 +90,7 @@ import org.hibernate.type.descriptor.jdbc.JsonAsStringJdbcType; import org.hibernate.type.descriptor.jdbc.XmlArrayJdbcTypeConstructor; import org.hibernate.type.descriptor.jdbc.XmlAsStringArrayJdbcTypeConstructor; import org.hibernate.type.descriptor.jdbc.XmlAsStringJdbcType; +import org.hibernate.type.descriptor.jdbc.UuidAsBinaryJdbcType; import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry; import org.hibernate.type.descriptor.sql.DdlType; import org.hibernate.type.descriptor.sql.internal.DdlTypeImpl; @@ -752,7 +753,7 @@ public class MetadataBuildingProcess { ); } else { - addFallbackIfNecessary( jdbcTypeRegistry, SqlTypes.UUID, SqlTypes.BINARY ); + jdbcTypeRegistry.addDescriptorIfAbsent( UuidAsBinaryJdbcType.INSTANCE ); } jdbcTypeRegistry.addDescriptorIfAbsent( JsonAsStringJdbcType.VARCHAR_INSTANCE ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/JsonHelper.java b/hibernate-core/src/main/java/org/hibernate/dialect/JsonHelper.java index c06ab3b1ee..f443109561 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/JsonHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/JsonHelper.java @@ -298,7 +298,7 @@ public class JsonHelper { case SqlTypes.DECIMAL: case SqlTypes.NUMERIC: case SqlTypes.DURATION: - case SqlTypes.UUID: + case SqlTypes.UUID: // These types need to be serialized as JSON string, but don't have a need for escaping appender.append( '"' ); javaType.appendEncodedString( appender, value ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java index 47a97d6009..700f3a0202 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java @@ -82,6 +82,7 @@ import org.hibernate.tool.schema.spi.Exporter; import org.hibernate.type.JavaObjectType; import org.hibernate.type.NullType; import org.hibernate.type.StandardBasicTypes; +import org.hibernate.type.descriptor.java.OracleUUIDJavaType; import org.hibernate.type.descriptor.java.PrimitiveByteArrayJavaType; import org.hibernate.type.descriptor.jdbc.BlobJdbcType; import org.hibernate.type.descriptor.jdbc.JdbcType; @@ -1041,6 +1042,8 @@ public class OracleDialect extends Dialect { ) ); + typeContributions.contributeJavaType( OracleUUIDJavaType.INSTANCE ); + if(getVersion().isSameOrAfter(23)) { final JdbcTypeRegistry jdbcTypeRegistry = typeContributions.getTypeConfiguration().getJdbcTypeRegistry(); jdbcTypeRegistry.addDescriptor(OracleEnumJdbcType.INSTANCE); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/aggregate/OracleAggregateSupport.java b/hibernate-core/src/main/java/org/hibernate/dialect/aggregate/OracleAggregateSupport.java index 6a3111edbe..31f71edb6a 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/aggregate/OracleAggregateSupport.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/aggregate/OracleAggregateSupport.java @@ -64,6 +64,7 @@ import static org.hibernate.type.SqlTypes.TIMESTAMP; import static org.hibernate.type.SqlTypes.TIMESTAMP_UTC; import static org.hibernate.type.SqlTypes.TIMESTAMP_WITH_TIMEZONE; import static org.hibernate.type.SqlTypes.TINYINT; +import static org.hibernate.type.SqlTypes.UUID; import static org.hibernate.type.SqlTypes.VARBINARY; public class OracleAggregateSupport extends AggregateSupportImpl { @@ -209,6 +210,7 @@ public class OracleAggregateSupport extends AggregateSupportImpl { case BINARY: case VARBINARY: case LONG32VARBINARY: + case UUID: return template.replace( placeholder, jdbcType.getSqlTypeName() + "_from_json(json_query(" + parentPartExpression + columnExpression + "' returning " + jsonTypeName + "))" diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/OracleUUIDJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/OracleUUIDJavaType.java new file mode 100644 index 0000000000..7ff0ab4d83 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/OracleUUIDJavaType.java @@ -0,0 +1,27 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.type.descriptor.java; + +import java.util.UUID; + +/** + * @author Jan Schatteman + */ +public class OracleUUIDJavaType extends UUIDJavaType { + + /* This class is related to the changes that were made for HHH-17246 */ + + public static final OracleUUIDJavaType INSTANCE = new OracleUUIDJavaType(); + + @Override + public String toString(UUID value) { + return NoDashesStringTransformer.INSTANCE.transform( value ); + } + + @Override + public UUID fromString(CharSequence string) { + return NoDashesStringTransformer.INSTANCE.parse( string.toString() ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/UUIDJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/UUIDJavaType.java index 86ad87a8a6..fe01d71e67 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/UUIDJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/UUIDJavaType.java @@ -37,10 +37,12 @@ public class UUIDJavaType extends AbstractClassJavaType { return true; } + @Override public String toString(UUID value) { return ToStringTransformer.INSTANCE.transform( value ); } + @Override public UUID fromString(CharSequence string) { return ToStringTransformer.INSTANCE.parse( string.toString() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/UuidAsBinaryJdbcType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/UuidAsBinaryJdbcType.java new file mode 100644 index 0000000000..743639c88f --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/UuidAsBinaryJdbcType.java @@ -0,0 +1,60 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.type.descriptor.jdbc; + +import org.hibernate.type.descriptor.ValueExtractor; +import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.JavaType; + +import java.sql.CallableStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Types; +import java.util.Arrays; + +import static org.hibernate.type.SqlTypes.UUID; + +/** + * @author Jan Schatteman + */ +public class UuidAsBinaryJdbcType extends BinaryJdbcType { + + public static final UuidAsBinaryJdbcType INSTANCE = new UuidAsBinaryJdbcType(); + + @Override + public int getDdlTypeCode() { + return Types.BINARY; + } + + @Override + public int getDefaultSqlTypeCode() { + return UUID; + } + + @Override + public ValueExtractor getExtractor( JavaType javaType ) { + return new BasicExtractor<>( javaType, this ) { + @Override + protected X doExtract( ResultSet rs, int paramIndex, WrapperOptions options ) throws SQLException { + final byte[] bytes = rs.getBytes( paramIndex ); + return javaType.wrap( bytes == null || bytes.length == 16 ? bytes : Arrays.copyOf( bytes, 16 ), options ); + } + + @Override + protected X doExtract( CallableStatement statement, int index, WrapperOptions options ) throws SQLException { + final byte[] bytes = statement.getBytes( index ); + return javaType.wrap( bytes == null || bytes.length == 16 ? bytes : Arrays.copyOf( bytes, 16 ), options ); + } + + @Override + protected X doExtract( CallableStatement statement, String name, WrapperOptions options ) + throws SQLException { + final byte[] bytes = statement.getBytes( name ); + return javaType.wrap( bytes == null || bytes.length == 16 ? bytes : Arrays.copyOf( bytes, 16 ), options ); + } + }; + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/id/uuid/SybaseASEUUIDTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/id/uuid/SybaseASEUUIDTest.java new file mode 100644 index 0000000000..3111e39af2 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/id/uuid/SybaseASEUUIDTest.java @@ -0,0 +1,81 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.id.uuid; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import org.hibernate.annotations.JdbcType; +import org.hibernate.dialect.SybaseASEDialect; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.RequiresDialect; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Jan Schatteman + */ +@RequiresDialect(value = SybaseASEDialect.class) +@DomainModel(annotatedClasses = { SybaseASEUUIDTest.Book.class }) +@SessionFactory +public class SybaseASEUUIDTest { + + private static final UUID uuid = UUID.fromString("53886a8a-7082-4879-b430-25cb94415b00"); + + @BeforeEach + void setUp(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final Book book = new Book(uuid, "John Doe"); + session.persist( book ); + } ); + } + + @AfterEach + void tearDown(SessionFactoryScope scope) { + scope.inTransaction( + session -> session.createMutationQuery( "delete from Book" ).executeUpdate() + ); + } + + @Test + @JiraKey( value = "HHH-17246" ) + public void testTrailingZeroByteTruncation(SessionFactoryScope scope) { + scope.inSession( + session -> assertEquals( 15, session.createNativeQuery("select id from Book", byte[].class).getSingleResult().length ) + ); + scope.inTransaction( + session -> { + Book b = session.createQuery( "from Book", Book.class ).getSingleResult(); + assertEquals(uuid, b.id); + } + ); + } + + @Entity(name = "Book") + static class Book { + @Id + // The purpose is to effectively provoke the trailing 0 bytes truncation + @JdbcType( SybaseUuidAsVarbinaryJdbcType.class ) + UUID id; + + String author; + + public Book() { + } + + public Book(UUID id, String author) { + this.id = id; + this.author = author; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/id/uuid/SybaseUuidAsVarbinaryJdbcType.java b/hibernate-core/src/test/java/org/hibernate/orm/test/id/uuid/SybaseUuidAsVarbinaryJdbcType.java new file mode 100644 index 0000000000..a7c05c4b28 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/id/uuid/SybaseUuidAsVarbinaryJdbcType.java @@ -0,0 +1,18 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.id.uuid; + +import org.hibernate.type.descriptor.jdbc.UuidAsBinaryJdbcType; +import java.sql.Types; + +/** + * @author Jan Schatteman + */ +public class SybaseUuidAsVarbinaryJdbcType extends UuidAsBinaryJdbcType { + @Override + public int getDdlTypeCode() { + return Types.VARBINARY; + } +}