From ac2f06800ea45aed66475a042a6f62cc99783565 Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Thu, 8 Oct 2015 09:56:14 -0500 Subject: [PATCH] HHH-9074 - HQL Query with boolean and @Convert --- .../internal/ast/tree/BooleanLiteralNode.java | 30 ++++++++----- .../hql/internal/ast/tree/LiteralNode.java | 44 +++++++++++++------ .../internal/ast/util/LiteralProcessor.java | 8 +--- .../hibernate/test/converter/QueryTest.java | 42 +++++++++++++++++- 4 files changed, 93 insertions(+), 31 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/BooleanLiteralNode.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/BooleanLiteralNode.java index 73b0df4650..168222ae31 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/BooleanLiteralNode.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/BooleanLiteralNode.java @@ -11,6 +11,7 @@ import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.type.LiteralType; import org.hibernate.type.StandardBasicTypes; import org.hibernate.type.Type; +import org.hibernate.type.descriptor.converter.AttributeConverterTypeAdapter; /** * Represents a boolean literal within a query. @@ -21,13 +22,18 @@ public class BooleanLiteralNode extends LiteralNode implements ExpectedTypeAware private Type expectedType; public Type getDataType() { - return expectedType == null ? StandardBasicTypes.BOOLEAN : expectedType; + return getExpectedType() == null ? StandardBasicTypes.BOOLEAN : getExpectedType(); } public Boolean getValue() { return getType() == TRUE ; } + @Override + public void setText(String s) { + super.setText( s ); + } + @Override public void setExpectedType(Type expectedType) { this.expectedType = expectedType; @@ -41,16 +47,20 @@ public class BooleanLiteralNode extends LiteralNode implements ExpectedTypeAware @Override @SuppressWarnings( {"unchecked"}) public String getRenderText(SessionFactoryImplementor sessionFactory) { - try { - return typeAsLiteralType().objectToSQLString( getValue(), sessionFactory.getDialect() ); - } - catch( Exception t ) { - throw new QueryException( "Unable to render boolean literal value", t ); - } - } + final boolean literalValue = getValue(); - private LiteralType typeAsLiteralType() { - return (LiteralType) getDataType(); + if ( expectedType instanceof AttributeConverterTypeAdapter ) { + return determineConvertedValue( (AttributeConverterTypeAdapter) expectedType, literalValue ); + } + else if ( expectedType instanceof LiteralType ) { + try { + return ( (LiteralType) expectedType ).objectToSQLString( getValue(), sessionFactory.getDialect() ); + } + catch( Exception t ) { + throw new QueryException( "Unable to render boolean literal value using expected LiteralType", t ); + } + } + return sessionFactory.getDialect().toBooleanValueString( literalValue ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/LiteralNode.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/LiteralNode.java index 7b10633d13..120b731fce 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/LiteralNode.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/LiteralNode.java @@ -7,10 +7,13 @@ package org.hibernate.hql.internal.ast.tree; import java.sql.Types; +import java.util.Locale; import javax.persistence.AttributeConverter; +import org.hibernate.QueryException; import org.hibernate.hql.internal.antlr.HqlSqlTokenTypes; import org.hibernate.hql.internal.ast.util.ColumnHelper; +import org.hibernate.type.LiteralType; import org.hibernate.type.SingleColumnType; import org.hibernate.type.StandardBasicTypes; import org.hibernate.type.Type; @@ -32,8 +35,8 @@ public class LiteralNode extends AbstractSelectExpression implements HqlSqlToken } public Type getDataType() { - if ( expectedType != null ) { - return expectedType; + if ( getExpectedType() != null ) { + return getExpectedType(); } switch ( getType() ) { @@ -81,21 +84,36 @@ public class LiteralNode extends AbstractSelectExpression implements HqlSqlToken if ( AttributeConverterTypeAdapter.class.isInstance( expectedType ) ) { final AttributeConverterTypeAdapter adapterType = (AttributeConverterTypeAdapter) expectedType; - if ( getDataType().getReturnedClass().equals( adapterType.getModelType() ) ) { - // apply the converter - final AttributeConverter converter = ( (AttributeConverterTypeAdapter) expectedType ).getAttributeConverter(); - final Object converted = converter.convertToDatabaseColumn( getLiteralValue() ); - if ( isCharacterData( adapterType.sqlType() ) ) { - setText( "'" + converted.toString() + "'" ); - } - else { - setText( converted.toString() ); - } - } + 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 + final AttributeConverter converter = converterTypeAdapter.getAttributeConverter(); + final Object converted = converter.convertToDatabaseColumn( getLiteralValue() ); + if ( isCharacterData( converterTypeAdapter.sqlType() ) ) { + return "'" + converted.toString() + "'"; + } + else { + return converted.toString(); + } + } + 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() + ) + ); + } + } + private boolean isCharacterData(int typeCode) { return Types.VARCHAR == typeCode || Types.CHAR == typeCode diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/util/LiteralProcessor.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/util/LiteralProcessor.java index 4418415f38..1b994ec616 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/util/LiteralProcessor.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/util/LiteralProcessor.java @@ -19,6 +19,7 @@ import org.hibernate.hql.internal.antlr.HqlSqlTokenTypes; import org.hibernate.hql.internal.antlr.SqlTokenTypes; import org.hibernate.hql.internal.ast.HqlSqlWalker; 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.FromClause; import org.hibernate.hql.internal.ast.tree.IdentNode; @@ -184,17 +185,10 @@ public class LiteralProcessor implements HqlSqlTokenTypes { } 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() ); if ( replacement != null ) { 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) { diff --git a/hibernate-core/src/test/java/org/hibernate/test/converter/QueryTest.java b/hibernate-core/src/test/java/org/hibernate/test/converter/QueryTest.java index 35fbe1e07a..ed29ad5330 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/converter/QueryTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/converter/QueryTest.java @@ -8,6 +8,7 @@ package org.hibernate.test.converter; import javax.persistence.AttributeConverter; import javax.persistence.Column; +import javax.persistence.Convert; import javax.persistence.Converter; import javax.persistence.Embeddable; import javax.persistence.Embedded; @@ -15,6 +16,7 @@ import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; +import org.hibernate.HibernateException; import org.hibernate.Session; import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; @@ -23,6 +25,7 @@ import org.junit.Before; import org.junit.Test; import static junit.framework.Assert.assertNotNull; +import static org.junit.Assert.assertNull; /** * Test AttributeConverter functioning in various Query scenarios. @@ -34,7 +37,7 @@ public class QueryTest extends BaseNonConfigCoreFunctionalTestCase { public static final float SALARY = 267.89f; @Test - public void testJpqlLiteral() { + public void testJpqlFloatLiteral() { Session session = openSession(); session.getTransaction().begin(); 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(); } + @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 protected Class[] getAnnotatedClasses() { return new Class[] { Employee.class, SalaryConverter.class }; @@ -74,6 +87,8 @@ public class QueryTest extends BaseNonConfigCoreFunctionalTestCase { @Embedded public Name name; public Float salary; + @Convert( converter = BooleanConverter.class ) + public boolean active = true; public Employee() { } @@ -125,4 +140,29 @@ public class QueryTest extends BaseNonConfigCoreFunctionalTestCase { return new Float( ( dbData.floatValue() ) / 100 ); } } + + public static class BooleanConverter implements AttributeConverter { + + @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 ); + } + } }