HHH-17246 Handle UUID specially within JSON

This commit is contained in:
Christian Beikov 2024-11-13 12:44:43 +01:00
parent 532d5460d4
commit 0ca27a4e32
9 changed files with 69 additions and 68 deletions

View File

@ -1232,6 +1232,13 @@ public class JsonHelper {
), ),
options options
); );
case SqlTypes.UUID:
return jdbcJavaType.wrap(
PrimitiveByteArrayJavaType.INSTANCE.fromString(
string.substring( start, end ).replace( "-", "" )
),
options
);
case SqlTypes.DATE: case SqlTypes.DATE:
return jdbcJavaType.wrap( return jdbcJavaType.wrap(
JdbcDateJavaType.INSTANCE.fromEncodedString( JdbcDateJavaType.INSTANCE.fromEncodedString(

View File

@ -82,7 +82,6 @@ import org.hibernate.tool.schema.spi.Exporter;
import org.hibernate.type.JavaObjectType; import org.hibernate.type.JavaObjectType;
import org.hibernate.type.NullType; import org.hibernate.type.NullType;
import org.hibernate.type.StandardBasicTypes; import org.hibernate.type.StandardBasicTypes;
import org.hibernate.type.descriptor.java.OracleUUIDJavaType;
import org.hibernate.type.descriptor.java.PrimitiveByteArrayJavaType; import org.hibernate.type.descriptor.java.PrimitiveByteArrayJavaType;
import org.hibernate.type.descriptor.jdbc.BlobJdbcType; import org.hibernate.type.descriptor.jdbc.BlobJdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.JdbcType;
@ -1042,8 +1041,6 @@ public class OracleDialect extends Dialect {
) )
); );
typeContributions.contributeJavaType( OracleUUIDJavaType.INSTANCE );
if(getVersion().isSameOrAfter(23)) { if(getVersion().isSameOrAfter(23)) {
final JdbcTypeRegistry jdbcTypeRegistry = typeContributions.getTypeConfiguration().getJdbcTypeRegistry(); final JdbcTypeRegistry jdbcTypeRegistry = typeContributions.getTypeConfiguration().getJdbcTypeRegistry();
jdbcTypeRegistry.addDescriptor(OracleEnumJdbcType.INSTANCE); jdbcTypeRegistry.addDescriptor(OracleEnumJdbcType.INSTANCE);

View File

@ -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_UTC;
import static org.hibernate.type.SqlTypes.TIMESTAMP_WITH_TIMEZONE; import static org.hibernate.type.SqlTypes.TIMESTAMP_WITH_TIMEZONE;
import static org.hibernate.type.SqlTypes.TINYINT; 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.VARBINARY;
/** /**
@ -355,6 +356,8 @@ public class OracleUserDefinedTypeExporter extends StandardUserDefinedTypeExport
case VARBINARY: case VARBINARY:
case LONG32VARBINARY: case LONG32VARBINARY:
return "hextoraw(" + expression + ")"; return "hextoraw(" + expression + ")";
case UUID:
return "hextoraw(replace(" + expression + ",'-',''))";
default: default:
return expression; return expression;
} }

View File

@ -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;
import static org.hibernate.type.SqlTypes.TIMESTAMP_UTC; import static org.hibernate.type.SqlTypes.TIMESTAMP_UTC;
import static org.hibernate.type.SqlTypes.TIMESTAMP_WITH_TIMEZONE; import static org.hibernate.type.SqlTypes.TIMESTAMP_WITH_TIMEZONE;
import static org.hibernate.type.SqlTypes.UUID;
import static org.hibernate.type.SqlTypes.VARBINARY; import static org.hibernate.type.SqlTypes.VARBINARY;
public class DB2AggregateSupport extends AggregateSupportImpl { public class DB2AggregateSupport extends AggregateSupportImpl {
public static final AggregateSupport INSTANCE = new DB2AggregateSupport( false ); public static final AggregateSupport INSTANCE = new DB2AggregateSupport( false );
public static final AggregateSupport JSON_INSTANCE = new DB2AggregateSupport( true ); 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; private final boolean jsonSupport;
@ -77,25 +80,32 @@ public class DB2AggregateSupport extends AggregateSupportImpl {
if ( !jsonSupport ) { if ( !jsonSupport ) {
break; 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() ) { switch ( column.getJdbcMapping().getJdbcType().getDefaultSqlTypeCode() ) {
case BOOLEAN: case BOOLEAN:
if ( SqlTypes.isNumericType( column.getJdbcMapping().getJdbcType().getDdlTypeCode() ) ) { if ( SqlTypes.isNumericType( column.getJdbcMapping().getJdbcType().getDdlTypeCode() ) ) {
return template.replace( return template.replace(
placeholder, placeholder,
"decode(json_value(" + aggregateParentReadExpression + ",'$." + columnExpression + "'),'true',1,'false',0)" "decode(json_value(" + parentPartExpression + columnExpression + "'),'true',1,'false',0)"
); );
} }
else { else {
return template.replace( return template.replace(
placeholder, 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_WITH_TIMEZONE:
case TIMESTAMP_UTC: case TIMESTAMP_UTC:
return template.replace( return template.replace(
placeholder, 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 BINARY:
case VARBINARY: case VARBINARY:
@ -104,18 +114,23 @@ public class DB2AggregateSupport extends AggregateSupportImpl {
// We encode binary data as hex, so we have to decode here // We encode binary data as hex, so we have to decode here
return template.replace( return template.replace(
placeholder, 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:
case JSON_ARRAY: case JSON_ARRAY:
return template.replace( return template.replace(
placeholder, placeholder,
"json_query(" + aggregateParentReadExpression + ",'$." + columnExpression + "')" "json_query(" + parentPartExpression + columnExpression + "')"
); );
default: default:
return template.replace( return template.replace(
placeholder, placeholder,
"json_value(" + aggregateParentReadExpression + ",'$." + columnExpression + "' returning " + column.getColumnDefinition() + ")" "json_value(" + parentPartExpression + columnExpression + "' returning " + column.getColumnDefinition() + ")"
); );
} }
case STRUCT: case STRUCT:
@ -133,6 +148,8 @@ public class DB2AggregateSupport extends AggregateSupportImpl {
case BLOB: case BLOB:
// We encode binary data as hex // We encode binary data as hex
return "hex(" + customWriteExpression + ")"; return "hex(" + customWriteExpression + ")";
case UUID:
return "regexp_replace(lower(hex(" + customWriteExpression + ")),'^(.{8})(.{4})(.{4})(.{4})(.{12})$','$1-$2-$3-$4-$5')";
case ARRAY: case ARRAY:
case JSON_ARRAY: case JSON_ARRAY:
return "(" + customWriteExpression + ") format json"; return "(" + customWriteExpression + ") format json";

View File

@ -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;
import static org.hibernate.type.SqlTypes.TIMESTAMP_UTC; import static org.hibernate.type.SqlTypes.TIMESTAMP_UTC;
import static org.hibernate.type.SqlTypes.TINYINT; 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.VARBINARY;
import static org.hibernate.type.SqlTypes.VARCHAR; import static org.hibernate.type.SqlTypes.VARCHAR;
@ -89,6 +90,14 @@ public class MySQLAggregateSupport extends AggregateSupportImpl {
placeholder, placeholder,
"unhex(json_unquote(" + queryExpression( aggregateParentReadExpression, columnExpression ) + "))" "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: default:
return template.replace( return template.replace(
placeholder, placeholder,
@ -148,16 +157,16 @@ public class MySQLAggregateSupport extends AggregateSupportImpl {
return "date_format(" + customWriteExpression + ",'%Y-%m-%dT%T.%f')"; return "date_format(" + customWriteExpression + ",'%Y-%m-%dT%T.%f')";
case TIMESTAMP_UTC: case TIMESTAMP_UTC:
return "date_format(" + customWriteExpression + ",'%Y-%m-%dT%T.%fZ')"; 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: default:
return customWriteExpression; return customWriteExpression;
} }
} }
@Override
public int aggregateComponentSqlTypeCode(int aggregateColumnSqlTypeCode, int columnSqlTypeCode) {
return super.aggregateComponentSqlTypeCode( aggregateColumnSqlTypeCode, columnSqlTypeCode );
}
@Override @Override
public String aggregateComponentAssignmentExpression( public String aggregateComponentAssignmentExpression(
String aggregateParentAssignmentExpression, String aggregateParentAssignmentExpression,

View File

@ -189,6 +189,11 @@ public class OracleAggregateSupport extends AggregateSupportImpl {
placeholder, placeholder,
"hextoraw(json_value(" + parentPartExpression + columnExpression + "'))" "hextoraw(json_value(" + parentPartExpression + columnExpression + "'))"
); );
case UUID:
return template.replace(
placeholder,
"hextoraw(replace(json_value(" + parentPartExpression + columnExpression + "'),'-',''))"
);
case CLOB: case CLOB:
case NCLOB: case NCLOB:
case BLOB: case BLOB:
@ -277,12 +282,16 @@ public class OracleAggregateSupport extends AggregateSupportImpl {
switch ( sqlTypeCode ) { switch ( sqlTypeCode ) {
case CLOB: case CLOB:
return "to_clob(" + customWriteExpression + ")"; return "to_clob(" + customWriteExpression + ")";
case UUID:
return "regexp_replace(lower(rawtohex(" + customWriteExpression + ")),'^(.{8})(.{4})(.{4})(.{4})(.{12})$','\\1-\\2-\\3-\\4-\\5')";
case ARRAY: case ARRAY:
final BasicPluralType<?, ?> pluralType = (BasicPluralType<?, ?>) jdbcMapping; final BasicPluralType<?, ?> pluralType = (BasicPluralType<?, ?>) jdbcMapping;
final OracleArrayJdbcType jdbcType = (OracleArrayJdbcType) pluralType.getJdbcType(); final OracleArrayJdbcType jdbcType = (OracleArrayJdbcType) pluralType.getJdbcType();
switch ( jdbcType.getElementJdbcType().getDefaultSqlTypeCode() ) { switch ( jdbcType.getElementJdbcType().getDefaultSqlTypeCode() ) {
case CLOB: case CLOB:
return "(select json_arrayagg(to_clob(t.column_value)) from table(" + customWriteExpression + ") t)"; 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: case BIT:
return "decode(" + customWriteExpression + ",1,'true',0,'false',null)"; return "decode(" + customWriteExpression + ",1,'true',0,'false',null)";
case BOOLEAN: case BOOLEAN:

View File

@ -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() );
}
}

View File

@ -4,15 +4,17 @@
*/ */
package org.hibernate.orm.test.id.uuid; package org.hibernate.orm.test.id.uuid;
import jakarta.persistence.Column;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
import jakarta.persistence.Id; import jakarta.persistence.Id;
import org.hibernate.annotations.JdbcType; import jakarta.persistence.Table;
import org.hibernate.dialect.SybaseASEDialect; import org.hibernate.dialect.SybaseASEDialect;
import org.hibernate.testing.orm.junit.DomainModel; import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.JiraKey; import org.hibernate.testing.orm.junit.JiraKey;
import org.hibernate.testing.orm.junit.RequiresDialect; import org.hibernate.testing.orm.junit.RequiresDialect;
import org.hibernate.testing.orm.junit.SessionFactory; import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope; 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.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -29,13 +31,15 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
@SessionFactory @SessionFactory
public class SybaseASEUUIDTest { 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 @BeforeEach
void setUp(SessionFactoryScope scope) { void setUp(SessionFactoryScope scope) {
scope.inTransaction( session -> { scope.inTransaction( session -> {
final Book book = new Book(uuid, "John Doe"); session.createNativeQuery( "insert into book (id, author) values (?,?)" )
session.persist( book ); .setParameter( 1, UUIDJavaType.ToBytesTransformer.INSTANCE.transform( THE_UUID ) )
.setParameter( 2, "John Doe" )
.executeUpdate();
} ); } );
} }
@ -49,22 +53,22 @@ public class SybaseASEUUIDTest {
@Test @Test
@JiraKey( value = "HHH-17246" ) @JiraKey( value = "HHH-17246" )
public void testTrailingZeroByteTruncation(SessionFactoryScope scope) { public void testTrailingZeroByteTruncation(SessionFactoryScope scope) {
scope.inSession(
session -> assertEquals( 15, session.createNativeQuery("select id from Book", byte[].class).getSingleResult().length )
);
scope.inTransaction( scope.inTransaction(
session -> { 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(); Book b = session.createQuery( "from Book", Book.class ).getSingleResult();
assertEquals(uuid, b.id); assertEquals( THE_UUID, b.id );
} }
); );
} }
@Entity(name = "Book") @Entity(name = "Book")
@Table(name = "book")
static class Book { static class Book {
@Id @Id
// The purpose is to effectively provoke the trailing 0 bytes truncation // The purpose is to effectively provoke the trailing 0 bytes truncation
@JdbcType( SybaseUuidAsVarbinaryJdbcType.class ) @Column(columnDefinition = "varbinary(16)")
UUID id; UUID id;
String author; String author;

View File

@ -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;
}
}