HHH-11477 - HQL query against field marked with @Lob fails on PostgreSQL

This commit is contained in:
Andrea Boriero 2017-02-10 14:12:05 +00:00 committed by Vlad Mihalcea
parent 9a9ef4d027
commit f0016db201
8 changed files with 338 additions and 24 deletions

View File

@ -188,7 +188,7 @@ public class PostgreSQL81Dialect extends Dialect {
break;
}
case Types.CLOB: {
descriptor = ClobTypeDescriptor.CLOB_BINDING;
descriptor = ClobTypeDescriptor.POSTGRESQL_CLOB_BINDING;
break;
}
default: {

View File

@ -107,6 +107,9 @@ public class ClobTypeDescriptor extends AbstractTypeDescriptor<Clob> {
: value;
return (X) clob;
}
else if ( String.class.isAssignableFrom( type ) ) {
return (X) DataHelper.extractString( value.getCharacterStream());
}
}
catch ( SQLException e ) {
throw new HibernateException( "Unable to access clob stream", e );
@ -129,6 +132,9 @@ public class ClobTypeDescriptor extends AbstractTypeDescriptor<Clob> {
Reader reader = (Reader) value;
return options.getLobCreator().createClob( DataHelper.extractString( reader ) );
}
else if ( String.class.isAssignableFrom( value.getClass() ) ) {
return options.getLobCreator().createClob( (String) value );
}
throw unknownWrap( value.getClass() );
}

View File

@ -114,6 +114,48 @@ public abstract class ClobTypeDescriptor implements SqlTypeDescriptor {
}
};
public static final ClobTypeDescriptor POSTGRESQL_CLOB_BINDING = new ClobTypeDescriptor() {
@Override
public <X> ValueExtractor<X> getExtractor(JavaTypeDescriptor<X> javaTypeDescriptor) {
return new BasicExtractor<X>( javaTypeDescriptor, this ) {
@Override
protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException {
return javaTypeDescriptor.wrap( rs.getString( name ), options );
}
@Override
protected X doExtract(CallableStatement statement, int index, WrapperOptions options)
throws SQLException {
return javaTypeDescriptor.wrap( statement.getString( index ), options );
}
@Override
protected X doExtract(CallableStatement statement, String name, WrapperOptions options)
throws SQLException {
return javaTypeDescriptor.wrap( statement.getString( name ), options );
}
};
}
@Override
public <X> BasicBinder<X> getClobBinder(final JavaTypeDescriptor<X> javaTypeDescriptor) {
return new BasicBinder<X>( javaTypeDescriptor, this ) {
@Override
protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options)
throws SQLException {
st.setString( index, javaTypeDescriptor.unwrap( value, String.class, options ) );
}
@Override
protected void doBind(CallableStatement st, X value, String name, WrapperOptions options)
throws SQLException {
st.setString( name, javaTypeDescriptor.unwrap( value, String.class, options ) );
}
};
}
};
public static final ClobTypeDescriptor STREAM_BINDING = new ClobTypeDescriptor() {
@Override
public <X> BasicBinder<X> getClobBinder(final JavaTypeDescriptor<X> javaTypeDescriptor) {

View File

@ -0,0 +1,222 @@
/*
* 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.lob;
import java.sql.Clob;
import java.sql.SQLException;
import java.util.List;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Lob;
import javax.persistence.Table;
import org.hibernate.engine.jdbc.ClobProxy;
import org.hibernate.query.Query;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.hibernate.testing.transaction.TransactionUtil;
import org.junit.Test;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
/**
* @author Andrea Boriero
*/
@TestForIssue(jiraKey = "HHH-11477")
public class LobStringTest extends BaseCoreFunctionalTestCase {
private static final int LONG_STRING_SIZE = 10000;
private final String value1 = buildRecursively( LONG_STRING_SIZE, 'x' );
private final String value2 = buildRecursively( LONG_STRING_SIZE, 'y' );
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class[] {TestEntity.class};
}
@Override
protected void prepareTest() throws Exception {
TestEntity entity = new TestEntity();
TransactionUtil.doInHibernate( this::sessionFactory, session -> {
entity.setFirstLobField( value1 );
entity.setSecondLobField( value2 );
entity.setClobField( session.getLobHelper().createClob( value2 ) );
session.save( entity );
} );
TransactionUtil.doInHibernate( this::sessionFactory, session -> {
final TestEntity testEntity = session.find( TestEntity.class, entity.getId() );
assertThat( testEntity.getFirstLobField(), is( value1 ) );
} );
}
@Test
@TestForIssue(jiraKey = "HHH-11477")
public void testHqlQuery() {
TransactionUtil.doInHibernate( this::sessionFactory, session -> {
final Query query = session.createQuery( "from TestEntity" );
final List<TestEntity> results = query.list();
assertThat( results.size(), is( 1 ) );
final TestEntity testEntity = results.get( 0 );
assertThat( testEntity.getFirstLobField(), is( value1 ) );
assertThat( testEntity.getSecondLobField(), is( value2 ) );
final Clob clobField = testEntity.getClobField();
try {
assertThat( clobField.getSubString( 1, (int) clobField.length() ), is( value2 ) );
}
catch (SQLException e) {
fail( e.getMessage() );
}
} );
}
@Test
@TestForIssue(jiraKey = "HHH-11477")
public void testUsingStringLobAnnotatedPropertyInHqlQuery() {
TransactionUtil.doInHibernate( this::sessionFactory, session -> {
final Query query = session.createQuery( "from TestEntity where LOWER(firstLobField) LIKE :value" );
query.setParameter( "value", value1 );
final List<TestEntity> results = query.list();
assertThat( results.size(), is( 1 ) );
final TestEntity testEntity = results.get( 0 );
assertThat( testEntity.getFirstLobField(), is( value1 ) );
assertThat( testEntity.getSecondLobField(), is( value2 ) );
final Clob clobField = testEntity.getClobField();
try {
assertThat( clobField.getSubString( 1, (int) clobField.length() ), is( value2 ) );
}
catch (SQLException e) {
fail( e.getMessage() );
}
} );
}
@Test
@TestForIssue(jiraKey = "HHH-11477")
public void testSelectStringLobAnnotatedInHqlQuery() {
TransactionUtil.doInHibernate( this::sessionFactory, session -> {
final Query query = session.createQuery(
"select t.secondLobField from TestEntity t where LOWER(t.firstLobField) LIKE :value" );
query.setParameter( "value", value1 );
final List<String> results = query.list();
assertThat( results.size(), is( 1 ) );
assertThat( results.get( 0 ), is( value2 ) );
} );
}
@Test
@TestForIssue(jiraKey = "HHH-11477")
public void testUsingLobPropertyInHqlQuery() {
TransactionUtil.doInHibernate( this::sessionFactory, session -> {
final Query query = session.createQuery(
"select t.secondLobField from TestEntity t where LOWER(t.clobField) LIKE :value" );
query.setParameter( "value", ClobProxy.generateProxy( value2 ) );
final List<String> results = query.list();
assertThat( results.size(), is( 1 ) );
assertThat( results.get( 0 ), is( value2 ) );
} );
}
@Test
@TestForIssue(jiraKey = "HHH-11477")
public void testSelectClobPropertyInHqlQuery() {
TransactionUtil.doInHibernate( this::sessionFactory, session -> {
final Query query = session.createQuery(
"select t.clobField from TestEntity t where LOWER(t.clobField) LIKE :value" );
query.setParameter( "value", ClobProxy.generateProxy( value2 ) );
final List<Clob> results = query.list();
assertThat( results.size(), is( 1 ) );
final Clob clobField = results.get( 0 );
try {
assertThat( clobField.getSubString( 1, (int) clobField.length() ), is( value2 ) );
}
catch (SQLException e) {
fail( e.getMessage() );
}
} );
}
@Entity(name = "TestEntity")
@Table(name = "TEST_ENTITY")
public static class TestEntity {
@Id
@GeneratedValue
private long id;
@Lob
@Column(length = LONG_STRING_SIZE) //needed by HSQLDialect
String firstLobField;
@Lob
@Column(length = LONG_STRING_SIZE) //needed by HSQLDialect
String secondLobField;
@Lob
@Column(length = LONG_STRING_SIZE) //needed by HSQLDialect
Clob clobField;
public long getId() {
return id;
}
public String getFirstLobField() {
return firstLobField;
}
public void setFirstLobField(String firstLobField) {
this.firstLobField = firstLobField;
}
public String getSecondLobField() {
return secondLobField;
}
public void setSecondLobField(String secondLobField) {
this.secondLobField = secondLobField;
}
public Clob getClobField() {
return clobField;
}
public void setClobField(Clob clobField) {
this.clobField = clobField;
}
}
@Override
protected boolean isCleanupTestDataRequired() {
return true;
}
private String buildRecursively(int size, char baseChar) {
StringBuilder buff = new StringBuilder();
for ( int i = 0; i < size; i++ ) {
buff.append( baseChar );
}
return buff.toString();
}
}

View File

@ -5,14 +5,22 @@
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.test.lob;
import java.util.List;
import org.junit.Test;
import org.hibernate.Session;
import org.hibernate.dialect.SybaseASE15Dialect;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.hibernate.query.Query;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.hibernate.testing.transaction.TransactionUtil;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
/**
* Tests eager materialization and mutation of long strings.
@ -22,13 +30,13 @@ import static org.junit.Assert.assertNull;
@SuppressWarnings( {"UnusedDeclaration"})
public abstract class LongStringTest extends BaseCoreFunctionalTestCase {
private static final int LONG_STRING_SIZE = 10000;
private static final String EMPTY = "";
private final String original = buildRecursively( LONG_STRING_SIZE, 'x' );
private final String changed = buildRecursively( LONG_STRING_SIZE, 'y' );
@Test
public void testBoundedLongStringAccess() {
String original = buildRecursively( LONG_STRING_SIZE, 'x' );
String changed = buildRecursively( LONG_STRING_SIZE, 'y' );
String empty = "";
Session s = openSession();
s.beginTransaction();
LongStringHolder entity = new LongStringHolder();
@ -38,7 +46,7 @@ public abstract class LongStringTest extends BaseCoreFunctionalTestCase {
s = openSession();
s.beginTransaction();
entity = ( LongStringHolder ) s.get( LongStringHolder.class, entity.getId() );
entity = s.get( LongStringHolder.class, entity.getId() );
assertNull( entity.getLongString() );
entity.setLongString( original );
s.getTransaction().commit();
@ -46,7 +54,7 @@ public abstract class LongStringTest extends BaseCoreFunctionalTestCase {
s = openSession();
s.beginTransaction();
entity = ( LongStringHolder ) s.get( LongStringHolder.class, entity.getId() );
entity = s.get( LongStringHolder.class, entity.getId() );
assertEquals( LONG_STRING_SIZE, entity.getLongString().length() );
assertEquals( original, entity.getLongString() );
entity.setLongString( changed );
@ -55,7 +63,7 @@ public abstract class LongStringTest extends BaseCoreFunctionalTestCase {
s = openSession();
s.beginTransaction();
entity = ( LongStringHolder ) s.get( LongStringHolder.class, entity.getId() );
entity = s.get( LongStringHolder.class, entity.getId() );
assertEquals( LONG_STRING_SIZE, entity.getLongString().length() );
assertEquals( changed, entity.getLongString() );
entity.setLongString( null );
@ -64,23 +72,23 @@ public abstract class LongStringTest extends BaseCoreFunctionalTestCase {
s = openSession();
s.beginTransaction();
entity = ( LongStringHolder ) s.get( LongStringHolder.class, entity.getId() );
entity = s.get( LongStringHolder.class, entity.getId() );
assertNull( entity.getLongString() );
entity.setLongString( empty );
entity.setLongString( EMPTY );
s.getTransaction().commit();
s.close();
s = openSession();
s.beginTransaction();
entity = ( LongStringHolder ) s.get( LongStringHolder.class, entity.getId() );
entity = s.get( LongStringHolder.class, entity.getId() );
if ( entity.getLongString() != null ) {
if(getDialect() instanceof SybaseASE15Dialect){
//Sybase uses a single blank to denote an empty string (this is by design). So, when inserting an empty string '', it is interpreted as single blank ' '.
assertEquals( empty.length(), entity.getLongString().trim().length() );
assertEquals( empty, entity.getLongString().trim() );
//Sybase uses a single blank to denote an EMPTY string (this is by design). So, when inserting an EMPTY string '', it is interpreted as single blank ' '.
assertEquals( EMPTY.length(), entity.getLongString().trim().length() );
assertEquals( EMPTY, entity.getLongString().trim() );
}else{
assertEquals( empty.length(), entity.getLongString().length() );
assertEquals( empty, entity.getLongString() );
assertEquals( EMPTY.length(), entity.getLongString().length() );
assertEquals( EMPTY, entity.getLongString() );
}
}
s.delete( entity );
@ -88,6 +96,49 @@ public abstract class LongStringTest extends BaseCoreFunctionalTestCase {
s.close();
}
@Test
@TestForIssue( jiraKey = "HHH-11477")
public void testUsingLobPropertyInHqlQuery() {
TransactionUtil.doInHibernate( this::sessionFactory, session -> {
LongStringHolder entity = new LongStringHolder();
entity.setLongString( original );
session.save( entity );
} );
TransactionUtil.doInHibernate( this::sessionFactory, session -> {
final Query query = session.createQuery( "from LongStringHolder where longString = :stringValue" );
query.setParameter( "stringValue", original );
final List<LongStringHolder> results = query.list();
assertThat( results.size(), is( 1 ) );
assertThat( results.get( 0 ).getLongString(), is( original ) );
} );
}
@Test
@TestForIssue( jiraKey = "HHH-11477")
public void testSelectLobPropertyInHqlQuery() {
TransactionUtil.doInHibernate( this::sessionFactory, session -> {
LongStringHolder entity = new LongStringHolder();
entity.setLongString( original );
session.save( entity );
} );
TransactionUtil.doInHibernate( this::sessionFactory, session -> {
final Query query = session.createQuery( "select l.longString from LongStringHolder l where l.longString = :stringValue" );
query.setParameter( "stringValue", original );
final List<String> results = query.list();
assertThat( results.size(), is( 1 ) );
assertThat( results.get( 0 ), is( original ) );
} );
}
@Override
protected boolean isCleanupTestDataRequired() {
return true;
}
private String buildRecursively(int size, char baseChar) {
StringBuilder buff = new StringBuilder();
for( int i = 0; i < size; i++ ) {

View File

@ -37,7 +37,6 @@ import static org.junit.Assert.assertThat;
@TestForIssue(jiraKey = "HHH-10364")
@SkipForDialect(value = DB2Dialect.class, comment = "DB2 jdbc driver doesn't support getNClob")
@SkipForDialect(value = MySQLDialect.class, comment = "MySQL/MariadB doesn't support nclob")
@SkipForDialect(value = PostgreSQL81Dialect.class, comment = "PostgreSQL doesn't support nclob")
@SkipForDialect(value = SybaseDialect.class, comment = "Sybase doesn't support nclob")
public class NationalizedLobFieldTest extends BaseCoreFunctionalTestCase {

View File

@ -15,13 +15,11 @@ import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Lob;
import org.hibernate.dialect.PostgreSQL81Dialect;
import org.hibernate.envers.Audited;
import org.hibernate.envers.test.BaseEnversJPAFunctionalTestCase;
import org.hibernate.envers.test.Priority;
import org.hibernate.envers.test.tools.TestTools;
import org.hibernate.testing.SkipForDialect;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.transaction.TransactionUtil;
import org.junit.Test;
@ -32,7 +30,6 @@ import static org.junit.Assert.assertEquals;
* @author Chris Cranford
*/
@TestForIssue(jiraKey = "HHH-9834")
@SkipForDialect(value = PostgreSQL81Dialect.class, jiraKey = "HHH-11477", comment = "@Lob field in HQL predicate fails with error about text = bigint")
public class StringMapLobTest extends BaseEnversJPAFunctionalTestCase {
@Override
protected Class<?>[] getAnnotatedClasses() {

View File

@ -16,13 +16,11 @@ import javax.persistence.Id;
import javax.persistence.Lob;
import org.hibernate.annotations.Nationalized;
import org.hibernate.dialect.PostgreSQL81Dialect;
import org.hibernate.envers.Audited;
import org.hibernate.envers.test.BaseEnversJPAFunctionalTestCase;
import org.hibernate.envers.test.Priority;
import org.hibernate.envers.test.tools.TestTools;
import org.hibernate.testing.SkipForDialect;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.transaction.TransactionUtil;
import org.junit.Test;
@ -33,7 +31,6 @@ import static org.junit.Assert.assertEquals;
* @author Chris Cranford
*/
@TestForIssue(jiraKey = "HHH-9834")
@SkipForDialect(value = PostgreSQL81Dialect.class, jiraKey = "HHH-11477", comment = "@Lob field in HQL predicate fails with error about text = bigint")
public class StringMapNationalizedLobTest extends BaseEnversJPAFunctionalTestCase {
@Override
protected Class<?>[] getAnnotatedClasses() {