Skip to content

Commit

Permalink
HHH-8866 - HQL Query with enum and @convert
Browse files Browse the repository at this point in the history
(cherry picked from commit bd8acae)

Conflicts:
	hibernate-core/src/test/java/org/hibernate/test/type/AttributeConverterTest.java
  • Loading branch information
sebersole authored and gbadner committed Mar 20, 2015
1 parent 0403ee7 commit 5b08a50
Show file tree
Hide file tree
Showing 5 changed files with 248 additions and 7 deletions.
Expand Up @@ -24,14 +24,18 @@
*/
package org.hibernate.hql.internal.ast.tree;

import java.util.Locale;

import org.hibernate.QueryException;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.hql.spi.QueryTranslator;
import org.hibernate.internal.util.ReflectHelper;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.type.LiteralType;
import org.hibernate.type.StringRepresentableType;
import org.hibernate.type.Type;
import org.hibernate.type.descriptor.converter.AttributeConverterTypeAdapter;

/**
* A node representing a static Java constant.
Expand Down Expand Up @@ -83,9 +87,44 @@ public String getRenderText(SessionFactoryImplementor sessionFactory) {
? heuristicType
: expectedType;
try {
final LiteralType literalType = (LiteralType) type;
final Dialect dialect = factory.getDialect();
return literalType.objectToSQLString( constantValue, dialect );
if ( LiteralType.class.isInstance( type ) ) {
final LiteralType literalType = (LiteralType) type;
final Dialect dialect = factory.getDialect();
return literalType.objectToSQLString( constantValue, dialect );
}
else if ( AttributeConverterTypeAdapter.class.isInstance( type ) ) {
final AttributeConverterTypeAdapter converterType = (AttributeConverterTypeAdapter) type;
if ( !converterType.getModelType().isInstance( constantValue ) ) {
throw new QueryException(
String.format(
Locale.ENGLISH,
"Recognized query constant expression [%s] was not resolved to type [%s] expected by defined AttributeConverter [%s]",
constantExpression,
constantValue.getClass().getName(),
converterType.getModelType().getName()
)
);
}
final Object value = converterType.getAttributeConverter().convertToDatabaseColumn( constantValue );
if ( String.class.equals( converterType.getJdbcType() ) ) {
return "'" + value + "'";
}
else {
return value.toString();
}
}
else {
throw new QueryException(
String.format(
Locale.ENGLISH,
"Unrecognized Hibernate Type for handling query constant (%s); expecting LiteralType implementation or AttributeConverter",
constantExpression
)
);
}
}
catch (QueryException e) {
throw e;
}
catch (Exception t) {
throw new QueryException( QueryTranslator.ERROR_CANNOT_FORMAT_LITERAL + constantExpression, t );
Expand Down
Expand Up @@ -22,6 +22,7 @@
* Boston, MA 02110-1301 USA
*/
package org.hibernate.type;

import org.hibernate.HibernateException;

/**
Expand Down
@@ -0,0 +1,60 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2015, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.type.descriptor.java;

import org.hibernate.type.descriptor.WrapperOptions;

/**
* Describes a Java Enum type.
*
* @author Steve Ebersole
*/
public class EnumJavaTypeDescriptor<T extends Enum> extends AbstractTypeDescriptor<T> {
@SuppressWarnings("unchecked")
protected EnumJavaTypeDescriptor(Class<T> type) {
super( type, ImmutableMutabilityPlan.INSTANCE );
}

@Override
public String toString(T value) {
return value == null ? "<null>" : value.name();
}

@Override
public T fromString(String string) {
return string == null ? null : (T) Enum.valueOf( getJavaTypeClass(), string );
}

@Override
@SuppressWarnings("unchecked")
public <X> X unwrap(T value, Class<X> type, WrapperOptions options) {
return (X) value;
}

@Override
@SuppressWarnings("unchecked")
public <X> T wrap(X value, WrapperOptions options) {
return (T) value;
}
}
Expand Up @@ -64,6 +64,12 @@ public <T> JavaTypeDescriptor<T> getDescriptor(Class<T> cls) {
return descriptor;
}

if ( cls.isEnum() ) {
descriptor = new EnumJavaTypeDescriptor( cls );
descriptorsByClass.put( cls, descriptor );
return descriptor;
}

if ( Serializable.class.isAssignableFrom( cls ) ) {
return new SerializableTypeDescriptor( cls );
}
Expand All @@ -80,8 +86,8 @@ public <T> JavaTypeDescriptor<T> getDescriptor(Class<T> cls) {
return new FallbackJavaTypeDescriptor<T>( cls );
}

public static class FallbackJavaTypeDescriptor<T> extends AbstractTypeDescriptor<T> {

public static class FallbackJavaTypeDescriptor<T> extends AbstractTypeDescriptor<T> {
@SuppressWarnings("unchecked")
protected FallbackJavaTypeDescriptor(Class<T> type) {
// MutableMutabilityPlan would be the "safest" option, but we do not necessarily know how to deepCopy etc...
Expand Down Expand Up @@ -112,4 +118,5 @@ public <X> T wrap(X value, WrapperOptions options) {
return (T) value;
}
}

}
Expand Up @@ -34,10 +34,12 @@
import java.sql.Types;

import javax.persistence.AttributeConverter;
import javax.persistence.Column;
import javax.persistence.Convert;
import javax.persistence.Converter;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

import org.hibernate.AnnotationException;
import org.hibernate.IrrelevantEntity;
Expand All @@ -46,18 +48,21 @@
import org.hibernate.cfg.AttributeConverterDefinition;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.cfg.Configuration;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.hql.internal.ast.tree.JavaConstantNode;
import org.hibernate.internal.util.ConfigHelper;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Property;
import org.hibernate.mapping.SimpleValue;
import org.hibernate.testing.FailureExpected;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.junit4.BaseUnitTestCase;
import org.hibernate.type.AbstractStandardBasicType;
import org.hibernate.type.BasicType;
import org.hibernate.type.Type;
import org.hibernate.type.descriptor.converter.AttributeConverterTypeAdapter;
import org.hibernate.type.descriptor.java.EnumJavaTypeDescriptor;
import org.hibernate.type.descriptor.java.StringTypeDescriptor;

import org.junit.Test;

/**
Expand Down Expand Up @@ -282,8 +287,81 @@ public void testBasicTimestampUsage() {
sf.close();
}
}



@Test
@TestForIssue(jiraKey = "HHH-8866")
public void testEnumConverter() {
Configuration cfg = new Configuration();
cfg.addAnnotatedClass( EntityWithConvertibleField.class );
cfg.addAttributeConverter( ConvertibleEnumConverter.class, true );
cfg.setProperty( AvailableSettings.HBM2DDL_AUTO, "create-drop" );
cfg.buildMappings();

{
// first lets validate that the converter was applied...
PersistentClass tester = cfg.getClassMapping( EntityWithConvertibleField.class.getName() );
Property nameProp = tester.getProperty( "convertibleEnum" );
SimpleValue nameValue = (SimpleValue) nameProp.getValue();
Type type = nameValue.getType();
assertNotNull( type );
assertTyping( BasicType.class, type );
if ( !AttributeConverterTypeAdapter.class.isInstance( type ) ) {
fail( "AttributeConverter not applied" );
}
AbstractStandardBasicType basicType = assertTyping( AbstractStandardBasicType.class, type );
assertTyping( EnumJavaTypeDescriptor.class, basicType.getJavaTypeDescriptor() );
assertEquals( Types.VARCHAR, basicType.getSqlTypeDescriptor().getSqlType() );

// then lets build the SF and verify its use...
final SessionFactory sf = cfg.buildSessionFactory();

try {
Session s = sf.openSession();
s.getTransaction().begin();
EntityWithConvertibleField entity = new EntityWithConvertibleField();
entity.setId( "ID" );
entity.setConvertibleEnum( ConvertibleEnum.VALUE );
String entityID = entity.getId();
s.persist( entity );
s.getTransaction().commit();
s.close();

s = sf.openSession();
s.beginTransaction();
entity = (EntityWithConvertibleField) s.load( EntityWithConvertibleField.class, entityID );
assertEquals( ConvertibleEnum.VALUE, entity.getConvertibleEnum() );
s.getTransaction().commit();
s.close();

JavaConstantNode javaConstantNode = new JavaConstantNode();
javaConstantNode.setExpectedType( type );
javaConstantNode.setSessionFactory( (SessionFactoryImplementor) sf );
javaConstantNode.setText( "org.hibernate.test.type.AttributeConverterTest$ConvertibleEnum.VALUE" );
final String outcome = javaConstantNode.getRenderText( (SessionFactoryImplementor) sf );
assertEquals( "'VALUE'", outcome );

s = sf.openSession();
s.beginTransaction();
s.createQuery( "FROM EntityWithConvertibleField e where e.convertibleEnum = org.hibernate.test.type.AttributeConverterTest$ConvertibleEnum.VALUE" )
.list();
s.getTransaction().commit();
s.close();

s = sf.openSession();
s.beginTransaction();
s.delete( entity );
s.getTransaction().commit();
s.close();
}
finally {
try {
sf.close();
}
catch (Exception ignore) {
}
}
}
}

// Entity declarations used in the test ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Expand Down Expand Up @@ -395,6 +473,62 @@ public void setDateCreated(Instant dateCreated) {
}
}

public static enum ConvertibleEnum {
VALUE,
DEFAULT;

public String convertToString() {
switch ( this ) {
case VALUE: {
return "VALUE";
}
default: {
return "DEFAULT";
}
}
}
}

@Converter(autoApply = true)
public static class ConvertibleEnumConverter implements AttributeConverter<ConvertibleEnum, String> {
@Override
public String convertToDatabaseColumn(ConvertibleEnum attribute) {
return attribute.convertToString();
}

@Override
public ConvertibleEnum convertToEntityAttribute(String dbData) {
return ConvertibleEnum.valueOf( dbData );
}
}

@Entity( name = "EntityWithConvertibleField" )
@Table( name = "EntityWithConvertibleField" )
public static class EntityWithConvertibleField {
private String id;
private ConvertibleEnum convertibleEnum;

@Id
@Column(name = "id")
public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}

@Column(name = "testEnum")

public ConvertibleEnum getConvertibleEnum() {
return convertibleEnum;
}

public void setConvertibleEnum(ConvertibleEnum convertibleEnum) {
this.convertibleEnum = convertibleEnum;
}
}


// Converter declarations used in the test ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Expand Down

0 comments on commit 5b08a50

Please sign in to comment.