Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
Expand Down Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand Down Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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:
Expand All @@ -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:
Expand All @@ -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";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -188,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:
Expand All @@ -209,6 +215,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 + "))"
Expand Down Expand Up @@ -275,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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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() );
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <X> ValueExtractor<X> getExtractor( JavaType<X> 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 );
}
};
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* 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.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
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;

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 THE_UUID = UUID.fromString("53886a8a-7082-4879-b430-25cb94415b00");

@BeforeEach
void setUp(SessionFactoryScope scope) {
scope.inTransaction( session -> {
session.createNativeQuery( "insert into book (id, author) values (?,?)" )
.setParameter( 1, UUIDJavaType.ToBytesTransformer.INSTANCE.transform( THE_UUID ) )
.setParameter( 2, "John Doe" )
.executeUpdate();
} );
}

@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.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( 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
@Column(columnDefinition = "varbinary(16)")
UUID id;

String author;

public Book() {
}

public Book(UUID id, String author) {
this.id = id;
this.author = author;
}
}

}
Loading