diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/SingularAttributeImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/SingularAttributeImpl.java index be7d75e4f289..23569230db92 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/SingularAttributeImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/SingularAttributeImpl.java @@ -301,4 +301,9 @@ public BindableType getBindableType() { public SqmPath createSqmPath(SqmPath lhs, SqmPathSource intermediatePathSource) { return sqmPathSource.createSqmPath( lhs, intermediatePathSource ); } + + @Override + public JavaType getRelationalJavaType() { + return sqmPathSource.getRelationalJavaType(); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/SemanticQueryWalker.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/SemanticQueryWalker.java index b1be2ba3a4d7..60fdb465ae59 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/SemanticQueryWalker.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/SemanticQueryWalker.java @@ -45,6 +45,7 @@ import org.hibernate.query.sqm.tree.domain.SqmSetJoin; import org.hibernate.query.sqm.tree.domain.SqmSingularJoin; import org.hibernate.query.sqm.tree.domain.SqmTreatedPath; +import org.hibernate.query.sqm.tree.expression.AsWrapperSqmExpression; import org.hibernate.query.sqm.tree.expression.JpaCriteriaParameter; import org.hibernate.query.sqm.tree.expression.SqmAny; import org.hibernate.query.sqm.tree.expression.SqmAnyDiscriminatorValue; @@ -417,4 +418,6 @@ default T visitCorrelatedRoot(SqmCorrelatedRoot correlatedRoot){ T visitMapEntryFunction(SqmMapEntryReference function); T visitFullyQualifiedClass(Class namedClass); + + T visitAsWrapperExpression(AsWrapperSqmExpression expression); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmTreePrinter.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmTreePrinter.java index 515a736951e3..03ba75d415fd 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmTreePrinter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmTreePrinter.java @@ -34,6 +34,7 @@ import org.hibernate.query.sqm.tree.domain.SqmPluralPartJoin; import org.hibernate.query.sqm.tree.domain.SqmPluralValuedSimplePath; import org.hibernate.query.sqm.tree.domain.SqmTreatedPath; +import org.hibernate.query.sqm.tree.expression.AsWrapperSqmExpression; import org.hibernate.query.sqm.tree.expression.JpaCriteriaParameter; import org.hibernate.query.sqm.tree.expression.SqmAny; import org.hibernate.query.sqm.tree.expression.SqmAnyDiscriminatorValue; @@ -1194,6 +1195,11 @@ public Object visitFullyQualifiedClass(Class namedClass) { return null; } + @Override + public Object visitAsWrapperExpression(AsWrapperSqmExpression expression) { + return null; + } + @Override public Object visitModifiedSubQueryExpression(SqmModifiedSubQueryExpression expression) { return null; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/spi/BaseSemanticQueryWalker.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/spi/BaseSemanticQueryWalker.java index f5505c421ec5..b3121d1bc390 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/spi/BaseSemanticQueryWalker.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/spi/BaseSemanticQueryWalker.java @@ -34,6 +34,7 @@ import org.hibernate.query.sqm.tree.domain.SqmPluralPartJoin; import org.hibernate.query.sqm.tree.domain.SqmPluralValuedSimplePath; import org.hibernate.query.sqm.tree.domain.SqmTreatedPath; +import org.hibernate.query.sqm.tree.expression.AsWrapperSqmExpression; import org.hibernate.query.sqm.tree.expression.JpaCriteriaParameter; import org.hibernate.query.sqm.tree.expression.SqmAggregateFunction; import org.hibernate.query.sqm.tree.expression.SqmAny; @@ -981,4 +982,9 @@ public Object visitFieldLiteral(SqmFieldLiteral sqmFieldLiteral) { return sqmFieldLiteral; } + @Override + public Object visitAsWrapperExpression(AsWrapperSqmExpression expression) { + expression.getExpression().accept( this ); + return expression; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java index d1d1cd416c91..da3f5833c5a2 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java @@ -128,6 +128,7 @@ import org.hibernate.query.sqm.produce.function.internal.PatternRenderer; import org.hibernate.query.sqm.spi.BaseSemanticQueryWalker; import org.hibernate.query.sqm.sql.internal.AnyDiscriminatorPathInterpretation; +import org.hibernate.query.sqm.sql.internal.AsWrappedExpression; import org.hibernate.query.sqm.sql.internal.BasicValuedPathInterpretation; import org.hibernate.query.sqm.sql.internal.DiscriminatedAssociationPathInterpretation; import org.hibernate.query.sqm.sql.internal.DiscriminatorPathInterpretation; @@ -174,6 +175,7 @@ import org.hibernate.query.sqm.tree.domain.SqmPluralValuedSimplePath; import org.hibernate.query.sqm.tree.domain.SqmSimplePath; import org.hibernate.query.sqm.tree.domain.SqmTreatedPath; +import org.hibernate.query.sqm.tree.expression.AsWrapperSqmExpression; import org.hibernate.query.sqm.tree.expression.Conversion; import org.hibernate.query.sqm.tree.expression.JpaCriteriaParameter; import org.hibernate.query.sqm.tree.expression.SqmAliasedNodeRef; @@ -8268,6 +8270,14 @@ public Object visitFullyQualifiedClass(Class namedClass) { // .getOrMakeJavaDescriptor( namedClass ); } + @Override + public Object visitAsWrapperExpression(AsWrapperSqmExpression sqmExpression) { + return new AsWrappedExpression<>( + (Expression) sqmExpression.getExpression().accept( this ), + sqmExpression.getNodeType() + ); + } + @Override public Fetch visitIdentifierFetch(EntityResultGraphNode fetchParent) { final EntityIdentifierMapping identifierMapping = fetchParent.getReferencedMappingContainer() diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/AsWrappedExpression.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/AsWrappedExpression.java new file mode 100644 index 000000000000..d45b128bafd3 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/AsWrappedExpression.java @@ -0,0 +1,106 @@ +/* + * 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.query.sqm.sql.internal; + +import org.hibernate.metamodel.mapping.JdbcMappingContainer; +import org.hibernate.sql.ast.SqlAstWalker; +import org.hibernate.sql.ast.spi.SqlAstCreationState; +import org.hibernate.sql.ast.spi.SqlSelection; +import org.hibernate.sql.ast.tree.expression.ColumnReference; +import org.hibernate.sql.ast.tree.expression.Expression; +import org.hibernate.sql.results.graph.DomainResult; +import org.hibernate.sql.results.graph.DomainResultCreationState; +import org.hibernate.sql.results.graph.basic.BasicResult; +import org.hibernate.type.BasicType; +import org.hibernate.type.descriptor.java.JavaType; +import org.hibernate.type.spi.TypeConfiguration; + +public class AsWrappedExpression implements Expression, DomainResultProducer { + private final Expression wrappedExpression; + private final BasicType expressionType; + + public AsWrappedExpression(Expression wrappedExpression, BasicType expressionType) { + assert wrappedExpression instanceof DomainResultProducer : "AsWrappedExpression expected to be an instance of DomainResultProducer"; + this.wrappedExpression = wrappedExpression; + this.expressionType = expressionType; + } + + @Override + public JdbcMappingContainer getExpressionType() { + return expressionType; + } + + @Override + public ColumnReference getColumnReference() { + return wrappedExpression.getColumnReference(); + } + + @Override + public SqlSelection createSqlSelection( + int jdbcPosition, + int valuesArrayPosition, + JavaType javaType, + boolean virtual, + TypeConfiguration typeConfiguration) { + return wrappedExpression.createSqlSelection( + jdbcPosition, + valuesArrayPosition, + javaType, + virtual, + typeConfiguration + ); + } + + @Override + public SqlSelection createDomainResultSqlSelection( + int jdbcPosition, + int valuesArrayPosition, + JavaType javaType, + boolean virtual, + TypeConfiguration typeConfiguration) { + return wrappedExpression.createDomainResultSqlSelection( + jdbcPosition, + valuesArrayPosition, + javaType, + virtual, + typeConfiguration + ); + } + + @Override + public void accept(SqlAstWalker sqlTreeWalker) { + wrappedExpression.accept( sqlTreeWalker ); + } + + @Override + public DomainResult createDomainResult(String resultVariable, DomainResultCreationState creationState) { + final SqlAstCreationState sqlAstCreationState = creationState.getSqlAstCreationState(); + final SqlSelection sqlSelection = sqlAstCreationState.getSqlExpressionResolver() + .resolveSqlSelection( + wrappedExpression, + wrappedExpression.getExpressionType().getSingleJdbcMapping().getJdbcJavaType(), + null, + sqlAstCreationState.getCreationContext() + .getMappingMetamodel().getTypeConfiguration() + ); + return new BasicResult<>( + sqlSelection.getValuesArrayPosition(), + resultVariable, + expressionType.getExpressibleJavaType(), + null, + null, + false, + false + ); + } + + @Override + public void applySqlSelections(DomainResultCreationState creationState) { + //noinspection unchecked + ( (DomainResultProducer) wrappedExpression ).applySqlSelections( creationState ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmBasicValuedSimplePath.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmBasicValuedSimplePath.java index 1543504bab45..0cbe86a6f013 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmBasicValuedSimplePath.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmBasicValuedSimplePath.java @@ -197,4 +197,9 @@ public DomainType getSqmType() { public X accept(SemanticQueryWalker walker) { return walker.visitBasicValuedPath( this ); } + + @Override + public JavaType getRelationalJavaType() { + return super.getExpressible().getRelationalJavaType(); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmEmbeddedValuedSimplePath.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmEmbeddedValuedSimplePath.java index 1c69a62461dc..2c07d79fa844 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmEmbeddedValuedSimplePath.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmEmbeddedValuedSimplePath.java @@ -121,4 +121,9 @@ public Class getJavaType() { public Class getBindableJavaType() { return getJavaType(); } + + @Override + public JavaType getRelationalJavaType() { + return super.getExpressible().getRelationalJavaType(); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/AbstractSqmExpression.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/AbstractSqmExpression.java index b9eadf62c432..cb45f3cfc922 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/AbstractSqmExpression.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/AbstractSqmExpression.java @@ -10,13 +10,13 @@ import java.math.BigInteger; import java.util.Collection; -import org.hibernate.query.criteria.JpaSelection; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SqmExpressible; import org.hibernate.query.sqm.SqmTreeCreationLogger; import org.hibernate.query.sqm.internal.SqmCriteriaNodeBuilder; import org.hibernate.query.sqm.tree.jpa.AbstractJpaSelection; import org.hibernate.query.sqm.tree.predicate.SqmPredicate; +import org.hibernate.type.BasicType; import org.hibernate.type.descriptor.java.JavaType; import jakarta.persistence.criteria.Expression; @@ -94,6 +94,15 @@ public SqmExpression asString() { @Override public SqmExpression as(Class type) { + if ( getExpressible() != null ) { + final BasicType basicTypeForJavaType = nodeBuilder().getTypeConfiguration() + .getBasicTypeForJavaType( type ); + if ( basicTypeForJavaType != null + && getExpressible().getRelationalJavaType().getJavaTypeClass() + == basicTypeForJavaType.getRelationalJavaType().getJavaTypeClass() ) { + return new AsWrapperSqmExpression<>( this, type ); + } + } return nodeBuilder().cast( this, type ); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/AsWrapperSqmExpression.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/AsWrapperSqmExpression.java new file mode 100644 index 000000000000..dccddaebb512 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/AsWrapperSqmExpression.java @@ -0,0 +1,53 @@ +/* + * 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 . + */ +package org.hibernate.query.sqm.tree.expression; + +import org.hibernate.query.sqm.SemanticQueryWalker; +import org.hibernate.query.sqm.SqmExpressible; +import org.hibernate.query.sqm.tree.SqmCopyContext; +import org.hibernate.type.BasicType; + +public class AsWrapperSqmExpression extends AbstractSqmExpression { + private final SqmExpression expression; + + public AsWrapperSqmExpression(SqmExpression expression, Class type) { + super( + expression.nodeBuilder().getTypeConfiguration().getBasicTypeForJavaType( type ), + expression.nodeBuilder() + ); + this.expression = expression; + } + + AsWrapperSqmExpression(SqmExpressible type, SqmExpression expression) { + super( type, expression.nodeBuilder() ); + this.expression = expression; + } + + @Override + public X accept(SemanticQueryWalker walker) { + return walker.visitAsWrapperExpression( this ); + } + + @Override + public void appendHqlString(StringBuilder sb) { + expression.appendHqlString( sb ); + } + + @Override + public SqmExpression copy(SqmCopyContext context) { + return new AsWrapperSqmExpression<>( getExpressible(), expression.copy( context ) ); + } + + public SqmExpression getExpression() { + return expression; + } + + @Override + public BasicType getNodeType() { + return (BasicType) super.getNodeType(); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/criteria/AbstractCriteriaTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/criteria/AbstractCriteriaTest.java index 9a5ed6cd76d9..03406798c7bb 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/criteria/AbstractCriteriaTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/criteria/AbstractCriteriaTest.java @@ -50,6 +50,7 @@ Thing.class, ThingWithQuantity.class, VersionedEntity.class -}) +} +) public abstract class AbstractCriteriaTest { } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/criteria/basic/CastTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/criteria/basic/CastTest.java index e0de08dc0e58..85a2f265cadb 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/criteria/basic/CastTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/criteria/basic/CastTest.java @@ -9,38 +9,46 @@ import java.math.BigDecimal; import java.math.BigInteger; import java.util.List; -import jakarta.persistence.criteria.CriteriaBuilder; -import jakarta.persistence.criteria.CriteriaQuery; -import jakarta.persistence.criteria.Root; -import org.hibernate.dialect.DerbyDialect; import org.hibernate.orm.test.jpa.metamodel.Product; import org.hibernate.orm.test.jpa.metamodel.Product_; -import org.hibernate.orm.test.jpa.criteria.AbstractCriteriaTest; +import org.hibernate.orm.test.jpa.metamodel.ShelfLife; -import org.hibernate.testing.SkipForDialect; -import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.jdbc.SQLStatementInspector; import org.hibernate.testing.orm.junit.EntityManagerFactoryScope; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.Jpa; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -public class CastTest extends AbstractCriteriaTest { - private static final int QUANTITY = 2; +import jakarta.persistence.AttributeConverter; +import jakarta.persistence.Convert; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Root; - @AfterEach - public void tearDown(EntityManagerFactoryScope scope) { - scope.inTransaction( - entityManager -> { - entityManager.createQuery( "delete from Product" ).executeUpdate(); - } - ); - } +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; - @Test - @SkipForDialect(value = DerbyDialect.class, comment = "Derby does not support cast from INTEGER to VARCHAR") - @TestForIssue(jiraKey = "HHH-5755") - public void testCastToString(EntityManagerFactoryScope scope) { +@Jpa(annotatedClasses = { + Product.class, + ShelfLife.class, + CastTest.TestEntity.class +}, + useCollectingStatementInspector = true +) +public class CastTest { + private static final int QUANTITY = 2; + private static final String NAME = "a"; + private static final Integer NAME_CONVERTED_VALUE = 1; + + @BeforeEach + public void setup(EntityManagerFactoryScope scope) { scope.inTransaction( entityManager -> { Product product = new Product(); @@ -52,9 +60,28 @@ public void testCastToString(EntityManagerFactoryScope scope) { product.setSomeBigInteger( BigInteger.valueOf( 987654321 ) ); product.setSomeBigDecimal( BigDecimal.valueOf( 987654.321 ) ); entityManager.persist( product ); + + TestEntity testEntity = new TestEntity( 1, NAME ); + entityManager.persist( testEntity ); } ); + } + @AfterEach + public void tearDown(EntityManagerFactoryScope scope) { + scope.inTransaction( + entityManager -> { + entityManager.createQuery( "delete from Product" ).executeUpdate(); + entityManager.createQuery( "delete from TestEntity" ).executeUpdate(); + } + ); + } + + @Test + @JiraKey("HHH-5755") + public void testCastToString(EntityManagerFactoryScope scope) { + SQLStatementInspector statementInspector = (SQLStatementInspector) scope.getStatementInspector(); + statementInspector.clear(); scope.inTransaction( entityManager -> { CriteriaBuilder builder = entityManager.getCriteriaBuilder(); @@ -66,7 +93,174 @@ public void testCastToString(EntityManagerFactoryScope scope) { ) ); List result = entityManager.createQuery( criteria ).getResultList(); Assertions.assertEquals( 1, result.size() ); + + assertExecuteQueryContainsACast( statementInspector ); + } + ); + } + + @Test + @JiraKey("HHH-15713") + public void testCastIntegerToIntegreDoesNotCreateACast(EntityManagerFactoryScope scope) { + SQLStatementInspector statementInspector = (SQLStatementInspector) scope.getStatementInspector(); + statementInspector.clear(); + scope.inTransaction( + entityManager -> { + CriteriaBuilder builder = entityManager.getCriteriaBuilder(); + CriteriaQuery criteria = builder.createQuery( Product.class ); + Root root = criteria.from( Product.class ); + criteria.where( builder.equal( + root.get( Product_.quantity ).as( Integer.class ), + builder.literal( QUANTITY ) + ) + ); + List result = entityManager.createQuery( criteria ).getResultList(); + Assertions.assertEquals( 1, result.size() ); + + assertExecuteQueryDoesNotContainACast( statementInspector ); } ); } + + @Test + @JiraKey("HHH-15725") + public void testCastAndConvertedAttribute(EntityManagerFactoryScope scope) { + SQLStatementInspector statementInspector = (SQLStatementInspector) scope.getStatementInspector(); + statementInspector.clear(); + scope.inTransaction( + entityManager -> { + CriteriaBuilder builder = entityManager.getCriteriaBuilder(); + CriteriaQuery criteria = builder.createQuery( TestEntity.class ); + Root root = criteria.from( TestEntity.class ); + criteria.where( builder.equal( + root.get( "name" ).as( Integer.class ), + builder.literal( NAME_CONVERTED_VALUE ) + ) + ); + List result = entityManager.createQuery( criteria ).getResultList(); + Assertions.assertEquals( 1, result.size() ); + + assertExecuteQueryDoesNotContainACast( statementInspector ); + } + ); + + statementInspector.clear(); + scope.inTransaction( + entityManager -> { + CriteriaBuilder builder = entityManager.getCriteriaBuilder(); + CriteriaQuery criteria = builder.createQuery( TestEntity.class ); + Root root = criteria.from( TestEntity.class ); + criteria.where( builder.equal( + root.get( "name" ).as( String.class ), + builder.literal( "1" ) + ) + ); + List result = entityManager.createQuery( criteria ).getResultList(); + Assertions.assertEquals( 1, result.size() ); + + assertExecuteQueryContainsACast( statementInspector ); + } + ); + } + + @Test + @JiraKey("HHH-15725") + public void testCastAndConvertedAttribute2(EntityManagerFactoryScope scope) { + SQLStatementInspector statementInspector = (SQLStatementInspector) scope.getStatementInspector(); + statementInspector.clear(); + + scope.inTransaction( + entityManager -> { + CriteriaBuilder builder = entityManager.getCriteriaBuilder(); + CriteriaQuery criteria = builder.createQuery( String.class ); + Root root = criteria.from( TestEntity.class ); + criteria.select( root.get( "name" ).as( String.class ) ); + List result = entityManager.createQuery( criteria ).getResultList(); + Assertions.assertEquals( 1, result.size() ); + String actual = result.get( 0 ); + // When Expression.as() is used the Converted is not applied + Assertions.assertEquals( "1", actual ); + + assertExecuteQueryContainsACast( statementInspector ); + } + ); + + statementInspector.clear(); + scope.inTransaction( + entityManager -> { + CriteriaBuilder builder = entityManager.getCriteriaBuilder(); + CriteriaQuery criteria = builder.createQuery( Integer.class ); + Root root = criteria.from( TestEntity.class ); + criteria.select( root.get( "name" ).as( Integer.class ) ); + List result = entityManager.createQuery( criteria ).getResultList(); + Assertions.assertEquals( 1, result.size() ); + Integer actual = result.get( 0 ); + // When Expression.as() is used the Converted is not applied + + Assertions.assertEquals( 1, actual ); + + assertExecuteQueryDoesNotContainACast( statementInspector ); + } + ); + } + + private static void assertExecuteQueryContainsACast(SQLStatementInspector statementInspector) { + assertTrue( getExecutedQuery( statementInspector ).contains( "cast" ) ); + } + + private static void assertExecuteQueryDoesNotContainACast(SQLStatementInspector statementInspector) { + assertFalse( getExecutedQuery( statementInspector ).contains( "cast" ) ); + } + + private static String getExecutedQuery(SQLStatementInspector statementInspector) { + List sqlQueries = statementInspector.getSqlQueries(); + assertThat( sqlQueries.size() ).isEqualTo( 1 ); + + String executedQuery = sqlQueries.get( 0 ); + return executedQuery; + } + + @Entity(name = "TestEntity") + public static class TestEntity { + @Id + private Integer id; + + @Convert(converter = TestConverter.class) + private String name; + + public TestEntity() { + } + + public TestEntity(Integer id, String name) { + this.id = id; + this.name = name; + } + + public Integer getId() { + return id; + } + + public String getName() { + return name; + } + } + + private static class TestConverter implements AttributeConverter { + @Override + public Integer convertToDatabaseColumn(String attribute) { + if ( attribute.equals( NAME ) ) { + return NAME_CONVERTED_VALUE; + } + return 0; + } + + @Override + public String convertToEntityAttribute(Integer dbData) { + if ( dbData == 1 ) { + return "a"; + } + return "b"; + } + } + }