HHH-9074 - HQL Query with boolean and @Convert

This commit is contained in:
Steve Ebersole 2015-10-08 09:56:14 -05:00
parent 6d590d76b6
commit ac2f06800e
4 changed files with 93 additions and 31 deletions

View File

@ -11,6 +11,7 @@ import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.type.LiteralType; import org.hibernate.type.LiteralType;
import org.hibernate.type.StandardBasicTypes; import org.hibernate.type.StandardBasicTypes;
import org.hibernate.type.Type; import org.hibernate.type.Type;
import org.hibernate.type.descriptor.converter.AttributeConverterTypeAdapter;
/** /**
* Represents a boolean literal within a query. * Represents a boolean literal within a query.
@ -21,13 +22,18 @@ public class BooleanLiteralNode extends LiteralNode implements ExpectedTypeAware
private Type expectedType; private Type expectedType;
public Type getDataType() { public Type getDataType() {
return expectedType == null ? StandardBasicTypes.BOOLEAN : expectedType; return getExpectedType() == null ? StandardBasicTypes.BOOLEAN : getExpectedType();
} }
public Boolean getValue() { public Boolean getValue() {
return getType() == TRUE ; return getType() == TRUE ;
} }
@Override
public void setText(String s) {
super.setText( s );
}
@Override @Override
public void setExpectedType(Type expectedType) { public void setExpectedType(Type expectedType) {
this.expectedType = expectedType; this.expectedType = expectedType;
@ -41,16 +47,20 @@ public class BooleanLiteralNode extends LiteralNode implements ExpectedTypeAware
@Override @Override
@SuppressWarnings( {"unchecked"}) @SuppressWarnings( {"unchecked"})
public String getRenderText(SessionFactoryImplementor sessionFactory) { public String getRenderText(SessionFactoryImplementor sessionFactory) {
final boolean literalValue = getValue();
if ( expectedType instanceof AttributeConverterTypeAdapter ) {
return determineConvertedValue( (AttributeConverterTypeAdapter) expectedType, literalValue );
}
else if ( expectedType instanceof LiteralType ) {
try { try {
return typeAsLiteralType().objectToSQLString( getValue(), sessionFactory.getDialect() ); return ( (LiteralType) expectedType ).objectToSQLString( getValue(), sessionFactory.getDialect() );
} }
catch( Exception t ) { catch( Exception t ) {
throw new QueryException( "Unable to render boolean literal value", t ); throw new QueryException( "Unable to render boolean literal value using expected LiteralType", t );
} }
} }
private LiteralType typeAsLiteralType() { return sessionFactory.getDialect().toBooleanValueString( literalValue );
return (LiteralType) getDataType();
} }
} }

View File

@ -7,10 +7,13 @@
package org.hibernate.hql.internal.ast.tree; package org.hibernate.hql.internal.ast.tree;
import java.sql.Types; import java.sql.Types;
import java.util.Locale;
import javax.persistence.AttributeConverter; import javax.persistence.AttributeConverter;
import org.hibernate.QueryException;
import org.hibernate.hql.internal.antlr.HqlSqlTokenTypes; import org.hibernate.hql.internal.antlr.HqlSqlTokenTypes;
import org.hibernate.hql.internal.ast.util.ColumnHelper; import org.hibernate.hql.internal.ast.util.ColumnHelper;
import org.hibernate.type.LiteralType;
import org.hibernate.type.SingleColumnType; import org.hibernate.type.SingleColumnType;
import org.hibernate.type.StandardBasicTypes; import org.hibernate.type.StandardBasicTypes;
import org.hibernate.type.Type; import org.hibernate.type.Type;
@ -32,8 +35,8 @@ public class LiteralNode extends AbstractSelectExpression implements HqlSqlToken
} }
public Type getDataType() { public Type getDataType() {
if ( expectedType != null ) { if ( getExpectedType() != null ) {
return expectedType; return getExpectedType();
} }
switch ( getType() ) { switch ( getType() ) {
@ -81,18 +84,33 @@ public class LiteralNode extends AbstractSelectExpression implements HqlSqlToken
if ( AttributeConverterTypeAdapter.class.isInstance( expectedType ) ) { if ( AttributeConverterTypeAdapter.class.isInstance( expectedType ) ) {
final AttributeConverterTypeAdapter adapterType = (AttributeConverterTypeAdapter) expectedType; final AttributeConverterTypeAdapter adapterType = (AttributeConverterTypeAdapter) expectedType;
if ( getDataType().getReturnedClass().equals( adapterType.getModelType() ) ) { setText( determineConvertedValue( adapterType, getLiteralValue() ) );
this.expectedType = expectedType;
}
}
@SuppressWarnings("unchecked")
protected String determineConvertedValue(AttributeConverterTypeAdapter converterTypeAdapter, Object literalValue) {
if ( getDataType().getReturnedClass().equals( converterTypeAdapter.getModelType() ) ) {
// apply the converter // apply the converter
final AttributeConverter converter = ( (AttributeConverterTypeAdapter) expectedType ).getAttributeConverter(); final AttributeConverter converter = converterTypeAdapter.getAttributeConverter();
final Object converted = converter.convertToDatabaseColumn( getLiteralValue() ); final Object converted = converter.convertToDatabaseColumn( getLiteralValue() );
if ( isCharacterData( adapterType.sqlType() ) ) { if ( isCharacterData( converterTypeAdapter.sqlType() ) ) {
setText( "'" + converted.toString() + "'" ); return "'" + converted.toString() + "'";
} }
else { else {
setText( converted.toString() ); return converted.toString();
} }
} }
this.expectedType = expectedType; else {
throw new QueryException(
String.format(
Locale.ROOT,
"AttributeConverter domain-model attribute type [%s] did not match query literal type [%s]",
converterTypeAdapter.getModelType().getName(),
getDataType().getReturnedClass().getName()
)
);
} }
} }

View File

@ -19,6 +19,7 @@ import org.hibernate.hql.internal.antlr.HqlSqlTokenTypes;
import org.hibernate.hql.internal.antlr.SqlTokenTypes; import org.hibernate.hql.internal.antlr.SqlTokenTypes;
import org.hibernate.hql.internal.ast.HqlSqlWalker; import org.hibernate.hql.internal.ast.HqlSqlWalker;
import org.hibernate.hql.internal.ast.InvalidPathException; import org.hibernate.hql.internal.ast.InvalidPathException;
import org.hibernate.hql.internal.ast.tree.BooleanLiteralNode;
import org.hibernate.hql.internal.ast.tree.DotNode; import org.hibernate.hql.internal.ast.tree.DotNode;
import org.hibernate.hql.internal.ast.tree.FromClause; import org.hibernate.hql.internal.ast.tree.FromClause;
import org.hibernate.hql.internal.ast.tree.IdentNode; import org.hibernate.hql.internal.ast.tree.IdentNode;
@ -184,17 +185,10 @@ public class LiteralProcessor implements HqlSqlTokenTypes {
} }
public void processBoolean(AST constant) { public void processBoolean(AST constant) {
// TODO: something much better - look at the type of the other expression!
// TODO: Have comparisonExpression and/or arithmeticExpression rules complete the resolution of boolean nodes.
String replacement = (String) walker.getTokenReplacements().get( constant.getText() ); String replacement = (String) walker.getTokenReplacements().get( constant.getText() );
if ( replacement != null ) { if ( replacement != null ) {
constant.setText( replacement ); constant.setText( replacement );
} }
else {
boolean bool = "true".equals( constant.getText().toLowerCase(Locale.ROOT) );
Dialect dialect = walker.getSessionFactoryHelper().getFactory().getDialect();
constant.setText( dialect.toBooleanValueString( bool ) );
}
} }
private void processLiteral(AST constant) { private void processLiteral(AST constant) {

View File

@ -8,6 +8,7 @@ package org.hibernate.test.converter;
import javax.persistence.AttributeConverter; import javax.persistence.AttributeConverter;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.Convert;
import javax.persistence.Converter; import javax.persistence.Converter;
import javax.persistence.Embeddable; import javax.persistence.Embeddable;
import javax.persistence.Embedded; import javax.persistence.Embedded;
@ -15,6 +16,7 @@ import javax.persistence.Entity;
import javax.persistence.Id; import javax.persistence.Id;
import javax.persistence.Table; import javax.persistence.Table;
import org.hibernate.HibernateException;
import org.hibernate.Session; import org.hibernate.Session;
import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase;
@ -23,6 +25,7 @@ import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
/** /**
* Test AttributeConverter functioning in various Query scenarios. * Test AttributeConverter functioning in various Query scenarios.
@ -34,7 +37,7 @@ public class QueryTest extends BaseNonConfigCoreFunctionalTestCase {
public static final float SALARY = 267.89f; public static final float SALARY = 267.89f;
@Test @Test
public void testJpqlLiteral() { public void testJpqlFloatLiteral() {
Session session = openSession(); Session session = openSession();
session.getTransaction().begin(); session.getTransaction().begin();
Employee jDoe = (Employee) session.createQuery( "from Employee e where e.salary = " + SALARY + "f" ).uniqueResult(); Employee jDoe = (Employee) session.createQuery( "from Employee e where e.salary = " + SALARY + "f" ).uniqueResult();
@ -43,6 +46,16 @@ public class QueryTest extends BaseNonConfigCoreFunctionalTestCase {
session.close(); session.close();
} }
@Test
public void testJpqlBooleanLiteral() {
Session session = openSession();
session.getTransaction().begin();
assertNotNull( session.createQuery( "from Employee e where e.active = true" ).uniqueResult() );
assertNull( session.createQuery( "from Employee e where e.active = false" ).uniqueResult() );
session.getTransaction().commit();
session.close();
}
@Override @Override
protected Class<?>[] getAnnotatedClasses() { protected Class<?>[] getAnnotatedClasses() {
return new Class[] { Employee.class, SalaryConverter.class }; return new Class[] { Employee.class, SalaryConverter.class };
@ -74,6 +87,8 @@ public class QueryTest extends BaseNonConfigCoreFunctionalTestCase {
@Embedded @Embedded
public Name name; public Name name;
public Float salary; public Float salary;
@Convert( converter = BooleanConverter.class )
public boolean active = true;
public Employee() { public Employee() {
} }
@ -125,4 +140,29 @@ public class QueryTest extends BaseNonConfigCoreFunctionalTestCase {
return new Float( ( dbData.floatValue() ) / 100 ); return new Float( ( dbData.floatValue() ) / 100 );
} }
} }
public static class BooleanConverter implements AttributeConverter<Boolean,Integer> {
@Override
public Integer convertToDatabaseColumn(Boolean attribute) {
if ( attribute == null ) {
return null;
}
return attribute ? 1 : 0;
}
@Override
public Boolean convertToEntityAttribute(Integer dbData) {
if ( dbData == null ) {
return null;
}
else if ( dbData == 0 ) {
return false;
}
else if ( dbData == 1 ) {
return true;
}
throw new HibernateException( "Unexpected boolean numeric; expecting null, 0 or 1, but found " + dbData );
}
}
} }