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
);
case SqlTypes.UUID:
return jdbcJavaType.wrap(
PrimitiveByteArrayJavaType.INSTANCE.fromString(
string.substring( start, end ).replace( "-", "" )
),
options
);
case SqlTypes.DATE:
return jdbcJavaType.wrap(
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.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);

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

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_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";

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_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,

View File

@ -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:

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

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