Skip to content

Commit 6a93a96

Browse files
committed
[hibernate#1338] Validate identity types for CockroachDB
1 parent f2239cb commit 6a93a96

File tree

3 files changed

+58
-19
lines changed

3 files changed

+58
-19
lines changed

hibernate-reactive-core/src/main/java/org/hibernate/reactive/logging/impl/Log.java

+3
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,9 @@ public interface Log extends BasicLogger {
223223
@Message(id = 72, value= "Cannot update an uninitialized proxy. Make sure to fetch the value before trying to update it: %1$s")
224224
HibernateException uninitializedProxyUpdate(Object entity);
225225

226+
@Message(id = 73, value = "%1$s is an invalid identity type when using CockroachDB (entity %2$s) - CockroachDB might generates identifiers that are too big and won't always fit in a %1$s. java.lang.Long is valid replacement")
227+
HibernateException invalidIdentifierTypeForCockroachDB(@FormatWith(ClassFormatter.class) Class<?> idType, String entityName);
228+
226229
// Same method that exists in CoreMessageLogger
227230
@LogMessage(level = WARN)
228231
@Message(id = 104, value = "firstResult/maxResults specified with collection fetch; applying in memory!" )

hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveAbstractEntityPersister.java

+26-7
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.hibernate.StaleObjectStateException;
2727
import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeDescriptor;
2828
import org.hibernate.collection.spi.PersistentCollection;
29+
import org.hibernate.dialect.CockroachDB192Dialect;
2930
import org.hibernate.dialect.Dialect;
3031
import org.hibernate.engine.OptimisticLockStyle;
3132
import org.hibernate.engine.internal.Versioning;
@@ -44,6 +45,7 @@
4445
import org.hibernate.jdbc.Expectation;
4546
import org.hibernate.loader.entity.UniqueEntityLoader;
4647
import org.hibernate.persister.entity.AbstractEntityPersister;
48+
import org.hibernate.persister.entity.EntityPersister;
4749
import org.hibernate.persister.entity.Lockable;
4850
import org.hibernate.persister.entity.MultiLoadOptions;
4951
import org.hibernate.persister.entity.OuterJoinLoadable;
@@ -72,6 +74,8 @@
7274
import org.hibernate.tuple.NonIdentifierAttribute;
7375
import org.hibernate.tuple.ValueGenerator;
7476
import org.hibernate.type.EntityType;
77+
import org.hibernate.type.IntegerType;
78+
import org.hibernate.type.ShortType;
7579
import org.hibernate.type.Type;
7680
import org.hibernate.type.VersionType;
7781

@@ -399,13 +403,28 @@ default CompletionStage<Serializable> insertReactive(
399403
// getGeneratedKeys(), or an extra round select statement. But we
400404
// don't need these extra options.
401405
.insertAndSelectIdentifier( sql, params, idClass, delegate().getIdentifierColumnNames()[0] )
402-
.thenApply( generatedId -> {
403-
log.debugf( "Natively generated identity: %s", generatedId );
404-
if ( generatedId == null ) {
405-
throw log.noNativelyGeneratedValueReturned();
406-
}
407-
return castToIdentifierType( generatedId, this );
408-
} );
406+
.thenApply( this::convertGeneratedId );
407+
}
408+
409+
private Serializable convertGeneratedId(Object generatedId) {
410+
log.debugf( "Natively generated identity: %s", generatedId );
411+
validateGeneratedIdentityId( generatedId, this );
412+
return castToIdentifierType( generatedId, this );
413+
}
414+
415+
private static void validateGeneratedIdentityId(Object generatedId, EntityPersister persister) {
416+
if ( generatedId == null ) {
417+
throw log.noNativelyGeneratedValueReturned();
418+
}
419+
420+
// CockroachDB might generate an identifier that fits an integer (and maybe a short) from time to time.
421+
// Users should not rely on it, or they might have random, hard to debug failures.
422+
Type identifierType = persister.getIdentifierType();
423+
if ( ( identifierType == IntegerType.INSTANCE || identifierType == ShortType.INSTANCE )
424+
&& persister.getFactory().getJdbcServices().getDialect() instanceof CockroachDB192Dialect
425+
) {
426+
throw log.invalidIdentifierTypeForCockroachDB( identifierType.getReturnedClass(), persister.getEntityName() );
427+
}
409428
}
410429

411430
default CompletionStage<Void> deleteReactive(

hibernate-reactive-core/src/test/java/org/hibernate/reactive/IdentityGeneratorTypeForCockroachDBTest.java

+29-12
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,11 @@
2929
import static org.hibernate.reactive.testing.ReactiveAssertions.assertThrown;
3030

3131
/**
32-
* Test supported types for ids generated by CockroachDB
32+
* Test supported types for ids generated by CockroachDB.
33+
* <p>
34+
* It won't work with {@link Short} and {@link Integer} because the id generated by CockroachDB don't map
35+
* those types - they are less than 0 or bigger.
36+
* </p>
3337
*
3438
* @see IdentityGeneratorTest
3539
* @see IdentityGeneratorTypeTest
@@ -71,7 +75,7 @@ public void longIdentityType(TestContext context) {
7175
LongTypeEntity entity = new LongTypeEntity();
7276

7377
test( context, getMutinySessionFactory()
74-
.withTransaction( (s, tx) -> s.persist( entity ) )
78+
.withTransaction( s -> s.persist( entity ) )
7579
.invoke( () -> {
7680
context.assertNotNull( entity );
7781
context.assertTrue( entity.id > 0 );
@@ -82,26 +86,39 @@ public void longIdentityType(TestContext context) {
8286
@Test
8387
public void integerIdentityType(TestContext context) {
8488
test( context, assertThrown( PersistenceException.class, getMutinySessionFactory()
85-
.withTransaction( (s, tx) -> s.persist( new IntegerTypeEntity() ) ) )
86-
.invoke( exception -> {
87-
assertThat( exception.getMessage() ).contains( "too big" );
88-
assertThat( exception.getMessage() ).contains( "Integer" );
89-
} )
89+
.withTransaction( s -> s.persist( new IntegerTypeEntity() ) ) )
90+
.invoke( exception -> validateErrorMessage( Integer.class, IntegerTypeEntity.class, exception ) )
9091
);
9192

9293
}
9394

9495
@Test
9596
public void shortIdentityType(TestContext context) {
9697
test( context, assertThrown( PersistenceException.class, getMutinySessionFactory()
97-
.withTransaction( (s, tx) -> s.persist( new ShortTypeEntity() ) ) )
98-
.invoke( exception -> {
99-
assertThat( exception.getMessage() ).contains( "too big" );
100-
assertThat( exception.getMessage() ).contains( "Short" );
101-
} )
98+
.withTransaction( s -> s.persist( new ShortTypeEntity() ) ) )
99+
.invoke( exception -> validateErrorMessage( Short.class, ShortTypeEntity.class, exception ) )
102100
);
103101
}
104102

103+
104+
private void validateErrorMessage(Class<?> idType, Class<?> entityTYpe, PersistenceException exception) {
105+
assertThat( exception.getMessage() )
106+
.as( "Unexpected error code - this should be a CockroachDB specific issue" )
107+
.contains( "HR000073" );
108+
assertThat( exception.getMessage() )
109+
.as( "Error message should contain the entity name" )
110+
.contains( entityTYpe.getName() );
111+
assertThat( exception.getMessage() )
112+
.as( "Error message should contain the invalid type" )
113+
.contains( idType.getName() );
114+
assertThat( exception.getMessage() )
115+
.as( "Error message should contain the valid type" )
116+
.contains( Long.class.getName() );
117+
assertThat( exception.getMessage() )
118+
.as( "Error message should mention that it happens only for CockroachDB" )
119+
.contains( "CockroachDB" );
120+
}
121+
105122
interface TypeIdentity<T extends Number> {
106123
T getId();
107124
}

0 commit comments

Comments
 (0)