From 0ca27a4e32badd41532f72c63ff7d44e26e6342c Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Wed, 13 Nov 2024 12:44:43 +0100 Subject: [PATCH] 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 f443109561..6fc4b21a4c 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 @@ public class JsonHelper { ), 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 700f3a0202..47a97d6009 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.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; @@ -1042,8 +1041,6 @@ 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/OracleUserDefinedTypeExporter.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleUserDefinedTypeExporter.java index c9cbe48372..6b63254541 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; 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 @@ public class OracleUserDefinedTypeExporter extends StandardUserDefinedTypeExport 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 bfbb9c7c48..23b42d6693 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.TIME; 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 class DB2AggregateSupport extends AggregateSupportImpl { 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 class DB2AggregateSupport extends AggregateSupportImpl { // 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 @@ public class DB2AggregateSupport extends AggregateSupportImpl { 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 9e0fe03d2a..6ac777a3d4 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.SMALLINT; 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 class MySQLAggregateSupport extends AggregateSupportImpl { 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 @@ public class MySQLAggregateSupport extends AggregateSupportImpl { 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 31f71edb6a..8d41467614 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 class OracleAggregateSupport extends AggregateSupportImpl { 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 @@ public class OracleAggregateSupport extends AggregateSupportImpl { 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 7ff0ab4d83..0000000000 --- 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 3111e39af2..034c34a234 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 @@ import static org.junit.jupiter.api.Assertions.assertEquals; @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 @@ public class SybaseASEUUIDTest { @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 a7c05c4b28..0000000000 --- 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; - } -}