From a5fa3e4acb68a1f9c4a3c7e51cfb8b5278bb5697 Mon Sep 17 00:00:00 2001 From: Jan Schatteman Date: Thu, 10 Oct 2024 21:25:01 +0200 Subject: [PATCH 1/2] 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 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; + } +} From 877eb95893b58dd57ae65907169fa3e8d9cd179e Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Wed, 13 Nov 2024 12:44:43 +0100 Subject: [PATCH 2/2] HHH-17246 Handle UUID specially within JSON --- .../org/hibernate/dialect/JsonHelper.java | 7 +++++ .../org/hibernate/dialect/OracleDialect.java | 3 -- .../OracleUserDefinedTypeExporter.java | 3 ++ .../aggregate/DB2AggregateSupport.java | 29 +++++++++++++++---- .../aggregate/MySQLAggregateSupport.java | 19 ++++++++---- .../aggregate/OracleAggregateSupport.java | 9 ++++++ .../descriptor/java/OracleUUIDJavaType.java | 27 ----------------- .../orm/test/id/uuid/SybaseASEUUIDTest.java | 22 ++++++++------ .../uuid/SybaseUuidAsVarbinaryJdbcType.java | 18 ------------ 9 files changed, 69 insertions(+), 68 deletions(-) delete mode 100644 hibernate-core/src/main/java/org/hibernate/type/descriptor/java/OracleUUIDJavaType.java delete 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/dialect/JsonHelper.java b/hibernate-core/src/main/java/org/hibernate/dialect/JsonHelper.java index f443109561ff..6fc4b21a4cd3 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/JsonHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/JsonHelper.java @@ -1232,6 +1232,13 @@ private static Object fromString( ), options ); + case SqlTypes.UUID: + return jdbcJavaType.wrap( + PrimitiveByteArrayJavaType.INSTANCE.fromString( + string.substring( start, end ).replace( "-", "" ) + ), + options + ); case SqlTypes.DATE: return jdbcJavaType.wrap( JdbcDateJavaType.INSTANCE.fromEncodedString( 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 700f3a020283..47a97d600962 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java @@ -82,7 +82,6 @@ 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; @@ -1042,8 +1041,6 @@ 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/OracleUserDefinedTypeExporter.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleUserDefinedTypeExporter.java index c9cbe4837203..6b6325454101 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleUserDefinedTypeExporter.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleUserDefinedTypeExporter.java @@ -28,6 +28,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; /** @@ -355,6 +356,8 @@ private String determineValueExpression(String expression, int elementSqlTypeCod case VARBINARY: case LONG32VARBINARY: return "hextoraw(" + expression + ")"; + case UUID: + return "hextoraw(replace(" + expression + ",'-',''))"; default: return expression; } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/aggregate/DB2AggregateSupport.java b/hibernate-core/src/main/java/org/hibernate/dialect/aggregate/DB2AggregateSupport.java index bfbb9c7c4820..23b42d669311 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/aggregate/DB2AggregateSupport.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/aggregate/DB2AggregateSupport.java @@ -50,12 +50,15 @@ 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.UUID; import static org.hibernate.type.SqlTypes.VARBINARY; public class DB2AggregateSupport extends AggregateSupportImpl { public static final AggregateSupport INSTANCE = new DB2AggregateSupport( false ); public static final AggregateSupport JSON_INSTANCE = new DB2AggregateSupport( true ); + private static final String JSON_QUERY_START = "json_query("; + private static final String JSON_QUERY_JSON_END = "')"; private final boolean jsonSupport; @@ -77,25 +80,32 @@ public String aggregateComponentCustomReadExpression( if ( !jsonSupport ) { break; } + final String parentPartExpression; + if ( aggregateParentReadExpression.startsWith( JSON_QUERY_START ) && aggregateParentReadExpression.endsWith( JSON_QUERY_JSON_END ) ) { + parentPartExpression = aggregateParentReadExpression.substring( JSON_QUERY_START.length(), aggregateParentReadExpression.length() - JSON_QUERY_JSON_END.length() ) + "."; + } + else { + parentPartExpression = aggregateParentReadExpression + ",'$."; + } switch ( column.getJdbcMapping().getJdbcType().getDefaultSqlTypeCode() ) { case BOOLEAN: if ( SqlTypes.isNumericType( column.getJdbcMapping().getJdbcType().getDdlTypeCode() ) ) { return template.replace( placeholder, - "decode(json_value(" + aggregateParentReadExpression + ",'$." + columnExpression + "'),'true',1,'false',0)" + "decode(json_value(" + parentPartExpression + columnExpression + "'),'true',1,'false',0)" ); } else { return template.replace( placeholder, - "decode(json_value(" + aggregateParentReadExpression + ",'$." + columnExpression + "'),'true',true,'false',false)" + "decode(json_value(" + parentPartExpression + columnExpression + "'),'true',true,'false',false)" ); } case TIMESTAMP_WITH_TIMEZONE: case TIMESTAMP_UTC: return template.replace( placeholder, - "cast(trim(trailing 'Z' from json_value(" + aggregateParentReadExpression + ",'$." + columnExpression + "' returning varchar(35))) as " + column.getColumnDefinition() + ")" + "cast(trim(trailing 'Z' from json_value(" + parentPartExpression + columnExpression + "' returning varchar(35))) as " + column.getColumnDefinition() + ")" ); case BINARY: case VARBINARY: @@ -104,18 +114,23 @@ public String aggregateComponentCustomReadExpression( // We encode binary data as hex, so we have to decode here return template.replace( placeholder, - "hextoraw(json_value(" + aggregateParentReadExpression + ",'$." + columnExpression + "'))" + "hextoraw(json_value(" + parentPartExpression + columnExpression + "'))" + ); + case UUID: + return template.replace( + placeholder, + "hextoraw(replace(json_value(" + parentPartExpression + columnExpression + "'),'-',''))" ); case JSON: case JSON_ARRAY: return template.replace( placeholder, - "json_query(" + aggregateParentReadExpression + ",'$." + columnExpression + "')" + "json_query(" + parentPartExpression + columnExpression + "')" ); default: return template.replace( placeholder, - "json_value(" + aggregateParentReadExpression + ",'$." + columnExpression + "' returning " + column.getColumnDefinition() + ")" + "json_value(" + parentPartExpression + columnExpression + "' returning " + column.getColumnDefinition() + ")" ); } case STRUCT: @@ -133,6 +148,8 @@ private static String jsonCustomWriteExpression(String customWriteExpression, Jd case BLOB: // We encode binary data as hex return "hex(" + customWriteExpression + ")"; + case UUID: + return "regexp_replace(lower(hex(" + customWriteExpression + ")),'^(.{8})(.{4})(.{4})(.{4})(.{12})$','$1-$2-$3-$4-$5')"; case ARRAY: case JSON_ARRAY: return "(" + customWriteExpression + ") format json"; diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/aggregate/MySQLAggregateSupport.java b/hibernate-core/src/main/java/org/hibernate/dialect/aggregate/MySQLAggregateSupport.java index 9e0fe03d2ac1..6ac777a3d493 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/aggregate/MySQLAggregateSupport.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/aggregate/MySQLAggregateSupport.java @@ -42,6 +42,7 @@ import static org.hibernate.type.SqlTypes.TIMESTAMP; import static org.hibernate.type.SqlTypes.TIMESTAMP_UTC; import static org.hibernate.type.SqlTypes.TINYINT; +import static org.hibernate.type.SqlTypes.UUID; import static org.hibernate.type.SqlTypes.VARBINARY; import static org.hibernate.type.SqlTypes.VARCHAR; @@ -89,6 +90,14 @@ public String aggregateComponentCustomReadExpression( placeholder, "unhex(json_unquote(" + queryExpression( aggregateParentReadExpression, columnExpression ) + "))" ); + case UUID: + if ( column.getJdbcMapping().getJdbcType().isBinary() ) { + return template.replace( + placeholder, + "unhex(replace(json_unquote(" + queryExpression( aggregateParentReadExpression, columnExpression ) + "),'-',''))" + ); + } + // Fall-through intended default: return template.replace( placeholder, @@ -148,16 +157,16 @@ private static String jsonCustomWriteExpression(String customWriteExpression, Jd return "date_format(" + customWriteExpression + ",'%Y-%m-%dT%T.%f')"; case TIMESTAMP_UTC: return "date_format(" + customWriteExpression + ",'%Y-%m-%dT%T.%fZ')"; + case UUID: + if ( jdbcMapping.getJdbcType().isBinary() ) { + return "regexp_replace(lower(hex(" + customWriteExpression + ")),'^(.{8})(.{4})(.{4})(.{4})(.{12})$','$1-$2-$3-$4-$5')"; + } + // Fall-through intended default: return customWriteExpression; } } - @Override - public int aggregateComponentSqlTypeCode(int aggregateColumnSqlTypeCode, int columnSqlTypeCode) { - return super.aggregateComponentSqlTypeCode( aggregateColumnSqlTypeCode, columnSqlTypeCode ); - } - @Override public String aggregateComponentAssignmentExpression( String aggregateParentAssignmentExpression, 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 31f71edb6a10..8d41467614e8 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 @@ -189,6 +189,11 @@ public String aggregateComponentCustomReadExpression( placeholder, "hextoraw(json_value(" + parentPartExpression + columnExpression + "'))" ); + case UUID: + return template.replace( + placeholder, + "hextoraw(replace(json_value(" + parentPartExpression + columnExpression + "'),'-',''))" + ); case CLOB: case NCLOB: case BLOB: @@ -277,12 +282,16 @@ private String jsonCustomWriteExpression( switch ( sqlTypeCode ) { case CLOB: return "to_clob(" + customWriteExpression + ")"; + case UUID: + return "regexp_replace(lower(rawtohex(" + customWriteExpression + ")),'^(.{8})(.{4})(.{4})(.{4})(.{12})$','\\1-\\2-\\3-\\4-\\5')"; case ARRAY: final BasicPluralType pluralType = (BasicPluralType) jdbcMapping; final OracleArrayJdbcType jdbcType = (OracleArrayJdbcType) pluralType.getJdbcType(); switch ( jdbcType.getElementJdbcType().getDefaultSqlTypeCode() ) { case CLOB: return "(select json_arrayagg(to_clob(t.column_value)) from table(" + customWriteExpression + ") t)"; + case UUID: + return "(select json_arrayagg(regexp_replace(lower(rawtohex(t.column_value)),'^(.{8})(.{4})(.{4})(.{4})(.{12})$','\\1-\\2-\\3-\\4-\\5')) from table(" + customWriteExpression + ") t)"; case BIT: return "decode(" + customWriteExpression + ",1,'true',0,'false',null)"; case BOOLEAN: 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 deleted file mode 100644 index 7ff0ab4d830d..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/OracleUUIDJavaType.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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/test/java/org/hibernate/orm/test/id/uuid/SybaseASEUUIDTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/id/uuid/SybaseASEUUIDTest.java index 3111e39af2c6..034c34a23433 100644 --- 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 @@ -4,15 +4,17 @@ */ package org.hibernate.orm.test.id.uuid; +import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.Id; -import org.hibernate.annotations.JdbcType; +import jakarta.persistence.Table; 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.hibernate.type.descriptor.java.UUIDJavaType; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -29,13 +31,15 @@ @SessionFactory public class SybaseASEUUIDTest { - private static final UUID uuid = UUID.fromString("53886a8a-7082-4879-b430-25cb94415b00"); + private static final UUID THE_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 ); + session.createNativeQuery( "insert into book (id, author) values (?,?)" ) + .setParameter( 1, UUIDJavaType.ToBytesTransformer.INSTANCE.transform( THE_UUID ) ) + .setParameter( 2, "John Doe" ) + .executeUpdate(); } ); } @@ -49,22 +53,22 @@ void tearDown(SessionFactoryScope scope) { @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 -> { + // Assert that our assumption is correct i.e. Sybase truncates trailing zero bytes + assertEquals( 15, session.createNativeQuery("select id from book", byte[].class).getSingleResult().length ); Book b = session.createQuery( "from Book", Book.class ).getSingleResult(); - assertEquals(uuid, b.id); + assertEquals( THE_UUID, b.id ); } ); } @Entity(name = "Book") + @Table(name = "book") static class Book { @Id // The purpose is to effectively provoke the trailing 0 bytes truncation - @JdbcType( SybaseUuidAsVarbinaryJdbcType.class ) + @Column(columnDefinition = "varbinary(16)") UUID id; String 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 deleted file mode 100644 index a7c05c4b281a..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/id/uuid/SybaseUuidAsVarbinaryJdbcType.java +++ /dev/null @@ -1,18 +0,0 @@ -/* - * 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; - } -}