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 dae2bb9137ee..e69c8fddfb4c 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.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 void contributeType(CompositeUserType type) { ); } 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 c06ab3b1eeec..f443109561ff 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 @@ private static void convertedBasicValueToString( 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 47a97d600962..700f3a020283 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.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 void contributeTypes(TypeContributions typeContributions, ServiceRegistry ) ); + 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 6a3111edbe66..31f71edb6a10 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_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 String aggregateComponentCustomReadExpression( 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 000000000000..7ff0ab4d830d --- /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 86ad87a8a633..fe01d71e6703 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 boolean useObjectEqualsHashCode() { 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 000000000000..743639c88f35 --- /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 000000000000..3111e39af2c6 --- /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 000000000000..a7c05c4b281a --- /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; + } +}