HHH-11477 - HQL query against field marked with @Lob fails on PostgreSQL
This commit is contained in:
parent
9a9ef4d027
commit
f0016db201
|
@ -188,7 +188,7 @@ public class PostgreSQL81Dialect extends Dialect {
|
|||
break;
|
||||
}
|
||||
case Types.CLOB: {
|
||||
descriptor = ClobTypeDescriptor.CLOB_BINDING;
|
||||
descriptor = ClobTypeDescriptor.POSTGRESQL_CLOB_BINDING;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
|
|
|
@ -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() );
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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++ ) {
|
||||
|
|
|
@ -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 {
|
||||
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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() {
|
||||
|
|
Loading…
Reference in New Issue