HHH-12132: HANA boolean type mapping doesn't work for existing schema definitions

- introduce parameter hibernate.dialect.hana.use_legacy_boolean_type to enable
  switching between new and legacy behavior.
This commit is contained in:
Jonathan Bregler 2017-11-28 16:46:36 +01:00 committed by Chris Cranford
parent a0f430a94d
commit 7b59cb2f8d
2 changed files with 263 additions and 2 deletions

View File

@ -54,6 +54,7 @@ import org.hibernate.dialect.pagination.LimitHandler;
import org.hibernate.dialect.pagination.LimitHelper; import org.hibernate.dialect.pagination.LimitHelper;
import org.hibernate.engine.config.spi.ConfigurationService; import org.hibernate.engine.config.spi.ConfigurationService;
import org.hibernate.engine.config.spi.ConfigurationService.Converter; import org.hibernate.engine.config.spi.ConfigurationService.Converter;
import org.hibernate.engine.config.spi.StandardConverters;
import org.hibernate.engine.jdbc.BinaryStream; import org.hibernate.engine.jdbc.BinaryStream;
import org.hibernate.engine.jdbc.BlobImplementer; import org.hibernate.engine.jdbc.BlobImplementer;
import org.hibernate.engine.jdbc.CharacterStream; import org.hibernate.engine.jdbc.CharacterStream;
@ -87,7 +88,9 @@ import org.hibernate.type.descriptor.java.DataHelper;
import org.hibernate.type.descriptor.java.JavaTypeDescriptor; import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
import org.hibernate.type.descriptor.sql.BasicBinder; import org.hibernate.type.descriptor.sql.BasicBinder;
import org.hibernate.type.descriptor.sql.BasicExtractor; import org.hibernate.type.descriptor.sql.BasicExtractor;
import org.hibernate.type.descriptor.sql.BitTypeDescriptor;
import org.hibernate.type.descriptor.sql.BlobTypeDescriptor; import org.hibernate.type.descriptor.sql.BlobTypeDescriptor;
import org.hibernate.type.descriptor.sql.BooleanTypeDescriptor;
import org.hibernate.type.descriptor.sql.ClobTypeDescriptor; import org.hibernate.type.descriptor.sql.ClobTypeDescriptor;
import org.hibernate.type.descriptor.sql.NClobTypeDescriptor; import org.hibernate.type.descriptor.sql.NClobTypeDescriptor;
import org.hibernate.type.descriptor.sql.SmallIntTypeDescriptor; import org.hibernate.type.descriptor.sql.SmallIntTypeDescriptor;
@ -662,8 +665,10 @@ public abstract class AbstractHANADialect extends Dialect {
} }
private static final String MAX_LOB_PREFETCH_SIZE_PARAMETER_NAME = new String( "hibernate.dialect.hana.max_lob_prefetch_size" ); private static final String MAX_LOB_PREFETCH_SIZE_PARAMETER_NAME = new String( "hibernate.dialect.hana.max_lob_prefetch_size" );
private static final String USE_LEGACY_BOOLEAN_TYPE_PARAMETER_NAME = new String( "hibernate.dialect.hana.use_legacy_boolean_type" );
private static final int MAX_LOB_PREFETCH_SIZE_DEFAULT_VALUE = 1024; private static final int MAX_LOB_PREFETCH_SIZE_DEFAULT_VALUE = 1024;
private static final Boolean USE_LEGACY_BOOLEAN_TYPE_DEFAULT_VALUE = Boolean.FALSE;
private HANANClobTypeDescriptor nClobTypeDescriptor = new HANANClobTypeDescriptor( MAX_LOB_PREFETCH_SIZE_DEFAULT_VALUE ); private HANANClobTypeDescriptor nClobTypeDescriptor = new HANANClobTypeDescriptor( MAX_LOB_PREFETCH_SIZE_DEFAULT_VALUE );
@ -671,6 +676,8 @@ public abstract class AbstractHANADialect extends Dialect {
private HANAClobTypeDescriptor clobTypeDescriptor = new HANAClobTypeDescriptor( MAX_LOB_PREFETCH_SIZE_DEFAULT_VALUE ); private HANAClobTypeDescriptor clobTypeDescriptor = new HANAClobTypeDescriptor( MAX_LOB_PREFETCH_SIZE_DEFAULT_VALUE );
private boolean useLegacyBooleanType = USE_LEGACY_BOOLEAN_TYPE_DEFAULT_VALUE.booleanValue();
/* /*
* Tables named "TYPE" need to be quoted * Tables named "TYPE" need to be quoted
*/ */
@ -1069,8 +1076,6 @@ public abstract class AbstractHANADialect extends Dialect {
@Override @Override
protected SqlTypeDescriptor getSqlTypeDescriptorOverride(final int sqlCode) { protected SqlTypeDescriptor getSqlTypeDescriptorOverride(final int sqlCode) {
switch ( sqlCode ) { switch ( sqlCode ) {
// case Types.BOOLEAN:
// return BitTypeDescriptor.INSTANCE;
case Types.CLOB: case Types.CLOB:
return this.clobTypeDescriptor; return this.clobTypeDescriptor;
case Types.NCLOB: case Types.NCLOB:
@ -1080,6 +1085,8 @@ public abstract class AbstractHANADialect extends Dialect {
case Types.TINYINT: case Types.TINYINT:
// tinyint is unsigned on HANA // tinyint is unsigned on HANA
return SmallIntTypeDescriptor.INSTANCE; return SmallIntTypeDescriptor.INSTANCE;
case Types.BOOLEAN:
return this.useLegacyBooleanType ? BitTypeDescriptor.INSTANCE : BooleanTypeDescriptor.INSTANCE;
default: default:
return super.getSqlTypeDescriptorOverride( sqlCode ); return super.getSqlTypeDescriptorOverride( sqlCode );
} }
@ -1499,6 +1506,9 @@ public abstract class AbstractHANADialect extends Dialect {
if ( this.clobTypeDescriptor.getMaxLobPrefetchSize() != maxLobPrefetchSize ) { if ( this.clobTypeDescriptor.getMaxLobPrefetchSize() != maxLobPrefetchSize ) {
this.clobTypeDescriptor = new HANAClobTypeDescriptor( maxLobPrefetchSize ); this.clobTypeDescriptor = new HANAClobTypeDescriptor( maxLobPrefetchSize );
} }
this.useLegacyBooleanType = configurationService.getSetting( USE_LEGACY_BOOLEAN_TYPE_PARAMETER_NAME, StandardConverters.BOOLEAN,
USE_LEGACY_BOOLEAN_TYPE_DEFAULT_VALUE ).booleanValue();
} }
public SqlTypeDescriptor getBlobTypeDescriptor() { public SqlTypeDescriptor getBlobTypeDescriptor() {
@ -1507,6 +1517,9 @@ public abstract class AbstractHANADialect extends Dialect {
@Override @Override
public String toBooleanValueString(boolean bool) { public String toBooleanValueString(boolean bool) {
if ( this.useLegacyBooleanType ) {
return bool ? "1" : "0";
}
return bool ? "true" : "false"; return bool ? "true" : "false";
} }

View File

@ -0,0 +1,248 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.test.dialect.functional;
import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.sql.PreparedStatement;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.PersistenceException;
import org.hibernate.Session;
import org.hibernate.dialect.AbstractHANADialect;
import org.hibernate.query.Query;
import org.hibernate.testing.RequiresDialect;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.junit.Test;
/**
* Tests the correctness of the parameter hibernate.dialect.hana.use_legacy_boolean_type which controls the mapping of
* boolean types to be either TINYINT (parameter is set to true) or BOOLEAN (default behavior or parameter is set to
* false)
*
* @author Jonathan Bregler
*/
@RequiresDialect(value = { AbstractHANADialect.class })
public class HANABooleanTest extends BaseCoreFunctionalTestCase {
private static final String ENTITY_NAME = "BooleanEntity";
private static final String LEGACY_ENTITY_NAME = "LegacyBooleanEntity";
@Override
protected void prepareTest() throws Exception {
doInHibernate( this::sessionFactory, session -> {
session.doWork( connection -> {
try ( PreparedStatement ps = connection
.prepareStatement( "CREATE COLUMN TABLE " + ENTITY_NAME + " (key INTEGER, bool BOOLEAN, PRIMARY KEY (key))" ) ) {
ps.execute();
}
try ( PreparedStatement ps = connection
.prepareStatement( "CREATE COLUMN TABLE " + LEGACY_ENTITY_NAME + " (key INTEGER, bool TINYINT, PRIMARY KEY (key))" ) ) {
ps.execute();
}
} );
} );
}
@Override
protected void cleanupTest() throws Exception {
doInHibernate( this::sessionFactory, session -> {
session.doWork( connection -> {
try ( PreparedStatement ps = connection.prepareStatement( "DROP TABLE " + ENTITY_NAME ) ) {
ps.execute();
}
catch (Exception e) {
// Ignore
}
try ( PreparedStatement ps = connection.prepareStatement( "DROP TABLE " + LEGACY_ENTITY_NAME ) ) {
ps.execute();
}
catch (Exception e) {
// Ignore
}
} );
} );
}
@Test
@TestForIssue(jiraKey = "HHH-12132")
public void testBooleanType() throws Exception {
rebuildSessionFactory( configuration -> {
configuration.setProperty( "hibernate.dialect.hana.use_legacy_boolean_type", Boolean.FALSE.toString() );
} );
Session s = openSession();
s.beginTransaction();
BooleanEntity entity = new BooleanEntity();
entity.key = Integer.valueOf( 1 );
entity.bool = Boolean.TRUE;
s.persist( entity );
s.flush();
s.getTransaction().commit();
s.clear();
Query<BooleanEntity> legacyQuery = s.createQuery( "select b from " + ENTITY_NAME + " b where bool = true", BooleanEntity.class );
BooleanEntity retrievedEntity = legacyQuery.getSingleResult();
assertEquals( Integer.valueOf( 1 ), retrievedEntity.key );
assertTrue( retrievedEntity.bool );
}
@Test
@TestForIssue(jiraKey = "HHH-12132")
public void testBooleanTypeDefaultBehavior() throws Exception {
rebuildSessionFactory();
Session s = openSession();
s.beginTransaction();
BooleanEntity entity = new BooleanEntity();
entity.key = Integer.valueOf( 1 );
entity.bool = Boolean.TRUE;
s.persist( entity );
s.flush();
s.getTransaction().commit();
s.clear();
Query<BooleanEntity> legacyQuery = s.createQuery( "select b from " + ENTITY_NAME + " b where bool = true", BooleanEntity.class );
BooleanEntity retrievedEntity = legacyQuery.getSingleResult();
assertEquals( Integer.valueOf( 1 ), retrievedEntity.key );
assertTrue( retrievedEntity.bool );
}
@Test(expected = PersistenceException.class)
@TestForIssue(jiraKey = "HHH-12132")
public void testLegacyBooleanType() throws Exception {
rebuildSessionFactory( configuration -> {
configuration.setProperty( "hibernate.dialect.hana.use_legacy_boolean_type", Boolean.FALSE.toString() );
} );
Session s = openSession();
s.beginTransaction();
LegacyBooleanEntity legacyEntity = new LegacyBooleanEntity();
legacyEntity.key = Integer.valueOf( 2 );
legacyEntity.bool = Boolean.FALSE;
s.persist( legacyEntity );
s.flush();
s.getTransaction().commit();
s.clear();
Query<LegacyBooleanEntity> query = s.createQuery( "select b from " + LEGACY_ENTITY_NAME + " b where bool = true", LegacyBooleanEntity.class );
query.getSingleResult();
}
@Test
@TestForIssue(jiraKey = "HHH-12132")
public void testLegacyBooleanTypeLegacyBehavior() throws Exception {
rebuildSessionFactory( configuration -> {
configuration.setProperty( "hibernate.dialect.hana.use_legacy_boolean_type", Boolean.TRUE.toString() );
} );
Session s = openSession();
s.beginTransaction();
LegacyBooleanEntity legacyEntity = new LegacyBooleanEntity();
legacyEntity.key = Integer.valueOf( 1 );
legacyEntity.bool = Boolean.TRUE;
s.persist( legacyEntity );
s.flush();
s.getTransaction().commit();
s.clear();
Query<LegacyBooleanEntity> legacyQuery = s.createQuery( "select b from " + LEGACY_ENTITY_NAME + " b where bool = true", LegacyBooleanEntity.class );
LegacyBooleanEntity retrievedEntity = legacyQuery.getSingleResult();
assertEquals( Integer.valueOf( 1 ), retrievedEntity.key );
assertTrue( retrievedEntity.bool );
}
@Test(expected = PersistenceException.class)
@TestForIssue(jiraKey = "HHH-12132")
public void testBooleanTypeLegacyBehavior() throws Exception {
rebuildSessionFactory( configuration -> {
configuration.setProperty( "hibernate.dialect.hana.use_legacy_boolean_type", Boolean.TRUE.toString() );
} );
Session s = openSession();
s.beginTransaction();
BooleanEntity entity = new BooleanEntity();
entity.key = Integer.valueOf( 2 );
entity.bool = Boolean.FALSE;
s.persist( entity );
s.flush();
s.getTransaction().commit();
s.clear();
Query<BooleanEntity> query = s.createQuery( "select b from " + ENTITY_NAME + " b where bool = true", BooleanEntity.class );
query.getSingleResult();
}
@Override
protected boolean createSchema() {
return false;
}
@Override
protected java.lang.Class<?>[] getAnnotatedClasses() {
return new java.lang.Class[]{
BooleanEntity.class, LegacyBooleanEntity.class
};
}
@Entity(name = LEGACY_ENTITY_NAME)
public static class LegacyBooleanEntity {
@Id
public Integer key;
public Boolean bool;
}
@Entity(name = ENTITY_NAME)
public static class BooleanEntity {
@Id
public Integer key;
public Boolean bool;
}
}