Skip to content

Commit

Permalink
HHH-17246 - Guard against Sybase being configured for truncating trai…
Browse files Browse the repository at this point in the history
…ling zeros

Signed-off-by: Jan Schatteman <[email protected]>
  • Loading branch information
jrenaat committed Oct 10, 2024
1 parent e485738 commit eef475e
Show file tree
Hide file tree
Showing 5 changed files with 173 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -787,7 +787,7 @@ public void contributeType(CompositeUserType<?> type) {
);
}
else {
addFallbackIfNecessary( jdbcTypeRegistry, SqlTypes.UUID, SqlTypes.BINARY );
addFallbackIfNecessary( jdbcTypeRegistry, SqlTypes.UUID, SqlTypes.VARBINARY_UUID );
}

final int preferredSqlTypeCodeForArray = getPreferredSqlTypeCodeForArray( serviceRegistry );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,12 @@
import org.hibernate.sql.ast.spi.StandardSqlAstTranslatorFactory;
import org.hibernate.sql.ast.tree.Statement;
import org.hibernate.sql.exec.spi.JdbcOperation;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.TimestampJdbcType;
import org.hibernate.type.descriptor.jdbc.TinyIntJdbcType;
import org.hibernate.type.descriptor.jdbc.UUIDasVarBinaryJdbcType;
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
import org.hibernate.type.descriptor.sql.internal.CapacityDependentDdlType;
import org.hibernate.type.descriptor.sql.spi.DdlTypeRegistry;
Expand Down Expand Up @@ -246,6 +248,7 @@ public void contributeTypes(TypeContributions typeContributions, ServiceRegistry
final JdbcTypeRegistry jdbcTypeRegistry = typeContributions.getTypeConfiguration()
.getJdbcTypeRegistry();
jdbcTypeRegistry.addDescriptor( Types.BOOLEAN, TinyIntJdbcType.INSTANCE );
jdbcTypeRegistry.addDescriptor( SqlTypes.VARBINARY_UUID, UUIDasVarBinaryJdbcType.INSTANCE );
// At least the jTDS driver does not support this type code
if ( getDriverKind() == SybaseDriverKind.JTDS ) {
jdbcTypeRegistry.addDescriptor( Types.TIMESTAMP_WITH_TIMEZONE, TimestampJdbcType.INSTANCE );
Expand Down
7 changes: 7 additions & 0 deletions hibernate-core/src/main/java/org/hibernate/type/SqlTypes.java
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,13 @@ public class SqlTypes {
*/
public static final int UUID = 3000;

/**
* A type code representing the generic SQL type {@code UUID} when it's being stored as a VARBINARY type.
*
* @see org.hibernate.type.descriptor.jdbc.UUIDasVarBinaryJdbcType
*/
public static final int VARBINARY_UUID = 3020;

/**
* A type code representing the generic SQL type {@code JSON}.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* 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.VARBINARY_UUID;

/**
* @author Jan Schatteman
*/
public class UUIDasVarBinaryJdbcType extends BinaryJdbcType {

public static final UUIDasVarBinaryJdbcType INSTANCE = new UUIDasVarBinaryJdbcType();

@Override
public int getDdlTypeCode() {
return Types.VARBINARY;
}

@Override
public int getDefaultSqlTypeCode() {
return VARBINARY_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 {
return javaType.wrap( Arrays.copyOf(rs.getBytes( paramIndex), 16), options );
}

@Override
protected X doExtract( CallableStatement statement, int index, WrapperOptions options ) throws SQLException {
return javaType.wrap( Arrays.copyOf(statement.getBytes(index), 16), options );
}

@Override
protected X doExtract( CallableStatement statement, String name, WrapperOptions options )
throws SQLException {
return javaType.wrap( Arrays.copyOf(statement.getBytes(name), 16), options );
}
};
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*
* 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.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.testing.util.uuid.SafeRandomUUIDGenerator;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.nio.ByteBuffer;
import java.sql.ResultSet;
import java.sql.Statement;
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 UUID uuid;

@BeforeEach
void setUp(SessionFactoryScope scope) {
this.uuid = scope.fromTransaction( session -> {
UUID uuid = UUID.randomUUID();
// Create a UUID with trailing zeros
while ( SafeRandomUUIDGenerator.isSafeUUID(uuid) ) {
uuid = UUID.randomUUID();
}
final Book book = new Book(uuid, "John Doe");
session.persist( book );
return uuid;
} );
}

@AfterEach
void tearDown(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
session.createMutationQuery( "delete from Book" ).executeUpdate();
}
);
}

@Test
@JiraKey( value = "" )
public void testTrailingZeroByteTruncation(SessionFactoryScope scope) {
scope.inSession(
session -> {
session.doWork(
connection -> {
Statement st = connection.createStatement();

Check warning

Code scanning / CodeQL

Potential database resource leak Warning test

This Statement is not always closed on method exit.
ResultSet rs = st.executeQuery( "select id from Book" );

Check warning

Code scanning / CodeQL

Potential database resource leak Warning test

This ResultSet is not always closed on method exit.
rs.next();
byte[] barr = rs.getBytes( 1 );
assertEquals( 15, barr.length );
}
);
}
);
scope.inTransaction(
session -> {
Book b = session.createQuery( "from Book", Book.class ).getSingleResult();
UUID uuid = b.id;
ByteBuffer bb = ByteBuffer.allocate( 8 );
bb.putLong( uuid.getLeastSignificantBits() );
byte[] arr = bb.array();
assertEquals(8, arr.length);
assertEquals(this.uuid, uuid);
}
);
}

@Entity(name = "Book")
static class Book {
@Id
UUID id;

String author;

public Book() {
}

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

}

0 comments on commit eef475e

Please sign in to comment.