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:
parent
a0f430a94d
commit
7b59cb2f8d
|
@ -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";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue