From 6a1581cf4a085e6842768eb181ef674a198ae178 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Thu, 24 Nov 2022 11:16:53 +0100 Subject: [PATCH] HHH-15725 Criteria API Expression.as adds cast even when the cast type is equal to the expression type --- .../internal/SingularAttributeImpl.java | 5 + .../query/criteria/JpaExpression.java | 2 + .../query/sqm/SemanticQueryWalker.java | 3 + .../query/sqm/internal/SqmTreePrinter.java | 6 + .../sqm/spi/BaseSemanticQueryWalker.java | 6 + .../sqm/sql/BaseSqmToSqlAstConverter.java | 10 ++ .../sqm/sql/internal/AsWrappedExpression.java | 106 ++++++++++++++++++ .../tree/domain/SqmBasicValuedSimplePath.java | 5 + .../domain/SqmEmbeddedValuedSimplePath.java | 5 + .../expression/AbstractSqmExpression.java | 8 +- .../expression/AsWrapperSqmExpression.java | 54 +++++++++ .../sqm/tree/expression/SqmExpression.java | 5 + .../jpa/criteria/AbstractCriteriaTest.java | 3 +- ...iteriaBuilderNonStandardFunctionsTest.java | 11 +- migration-guide.adoc | 16 ++- 15 files changed, 235 insertions(+), 10 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/AsWrappedExpression.java create mode 100644 hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/AsWrapperSqmExpression.java 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 be7d75e4f2..23569230db 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 class SingularAttributeImpl 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/criteria/JpaExpression.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaExpression.java index b9fdb7f5ab..5e46888cca 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaExpression.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaExpression.java @@ -56,4 +56,6 @@ public interface JpaExpression extends JpaSelection, Expression { JpaPredicate equalTo(Expression that); JpaPredicate equalTo(T that); + + JpaExpression cast(Class type); } 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 b1be2ba3a4..60fdb465ae 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.SqmPluralValuedSimplePath; 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 @@ public interface SemanticQueryWalker { 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 515a736951..03ba75d415 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.SqmIndexAggregateFunction; 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 class SqmTreePrinter implements SemanticQueryWalker { 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 f5505c421e..b3121d1bc3 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.SqmPath; 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 abstract class BaseSemanticQueryWalker implements SemanticQueryWalker 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 d1d1cd416c..da3f5833c5 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.mutation.internal.SqmInsertStrategyHelper; 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.SqmPluralPartJoin; 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 abstract class BaseSqmToSqlAstConverter extends Base // .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 0000000000..d45b128baf --- /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 1543504bab..0cbe86a6f0 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 class SqmBasicValuedSimplePath 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 1c69a62461..2c07d79fa8 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 SqmEmbeddedValuedSimplePath 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 b9eadf62c4..ffc38b02b7 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.BigDecimal; 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,7 +94,11 @@ public abstract class AbstractSqmExpression extends AbstractJpaSelection i @Override public SqmExpression as(Class type) { - return nodeBuilder().cast( this, type ); + final BasicType basicTypeForJavaType = nodeBuilder().getTypeConfiguration().getBasicTypeForJavaType( type ); + if ( basicTypeForJavaType == null ) { + throw new IllegalArgumentException( "Can't cast expression to unknown type: " + type.getCanonicalName() ); + } + return new AsWrapperSqmExpression<>( basicTypeForJavaType, this ); } @Override 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 0000000000..d3be23caef --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/AsWrapperSqmExpression.java @@ -0,0 +1,54 @@ +/* + * 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; + + 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) { + sb.append( "wrap(" ); + expression.appendHqlString( sb ); + sb.append( " as " ); + sb.append( getNodeType().getReturnedClassName() ); + sb.append( ")" ); + } + + @Override + public SqmExpression as(Class type) { + return expression.as( type ); + } + + @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/main/java/org/hibernate/query/sqm/tree/expression/SqmExpression.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmExpression.java index 8ed07fae21..6146346185 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmExpression.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmExpression.java @@ -125,4 +125,9 @@ public interface SqmExpression extends SqmSelectableNode, JpaExpression ); } + @Override + default SqmExpression cast(Class type) { + return castAs( nodeBuilder().getTypeConfiguration().getBasicTypeForJavaType( type ) ); + } + } 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 03406798c7..9a5ed6cd76 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,7 +50,6 @@ import org.hibernate.testing.orm.junit.Jpa; Thing.class, ThingWithQuantity.class, VersionedEntity.class -} -) +}) public abstract class AbstractCriteriaTest { } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/criteria/CriteriaBuilderNonStandardFunctionsTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/criteria/CriteriaBuilderNonStandardFunctionsTest.java index d95d32bf56..41a8fa09b6 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/query/criteria/CriteriaBuilderNonStandardFunctionsTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/criteria/CriteriaBuilderNonStandardFunctionsTest.java @@ -20,6 +20,7 @@ import org.hibernate.dialect.CockroachDialect; import org.hibernate.dialect.DerbyDialect; import org.hibernate.dialect.PostgreSQLDialect; import org.hibernate.query.criteria.HibernateCriteriaBuilder; +import org.hibernate.query.criteria.JpaExpression; import org.hibernate.query.sqm.TemporalUnit; import org.hibernate.testing.orm.domain.StandardDomainModel; @@ -237,14 +238,14 @@ public class CriteriaBuilderNonStandardFunctionsTest { Expression theString = from.get( "theString" ); query.multiselect( cb.overlay( theString, "33", 6 ), -// cb.overlay( theString, from.get( "theInt" ).as( String.class ), 6 ), + cb.overlay( theString, ( (JpaExpression) from.get( "theInt" ) ).cast( String.class ), 6 ), cb.overlay( theString, "1234", from.get( "theInteger" ), 2 ) ).where( cb.equal( from.get( "id" ), 4 ) ); Tuple result = session.createQuery( query ).getSingleResult(); assertEquals( "thirt33n", result.get( 0 ) ); -// assertEquals( "thirt13n", result.get( 1 ) ); - assertEquals( "thi1234een", result.get( 1 ) ); + assertEquals( "thirt13n", result.get( 1 ) ); + assertEquals( "thi1234een", result.get( 2 ) ); } ); } @@ -300,12 +301,12 @@ public class CriteriaBuilderNonStandardFunctionsTest { Expression theString = from.get( "theString" ); query.multiselect( cb.replace( theString, "thi", "12345" ), - cb.replace( theString, "t", from.get( "theString" ) ) + cb.replace( theString, "t", ( (JpaExpression) from.get( "theInteger" ) ).cast( String.class ) ) ).where( cb.equal( from.get( "id" ), 4 ) ); Tuple result = session.createQuery( query ).getSingleResult(); assertEquals( "12345rteen", result.get( 0 ) ); - assertEquals( "thirteenhirthirteeneen", result.get( 1 ) ); + assertEquals( "4hir4een", result.get( 1 ) ); } ); } diff --git a/migration-guide.adoc b/migration-guide.adoc index 56197c85c2..c2c3666575 100644 --- a/migration-guide.adoc +++ b/migration-guide.adoc @@ -93,4 +93,18 @@ You can find more details about embeddable inheritance in the dedicated link:{us [[h2-dialect]] == H2 database and bulk mutation strategy -With ORM 6.6 when a bulk mutation involves multiple tables, H2 dialect will make use of global temporary tables instead of local ones. \ No newline at end of file +With ORM 6.6 when a bulk mutation involves multiple tables, H2 dialect will make use of global temporary tables instead of local ones. + +[[criteria-query]] +== Criteria: `jakarta.persistence.criteria.Expression#as(Class)` + +The behaviour of `jakarta.persistence.criteria.Expression#as(Class)` has been changed to conform to the Jakarta Persistence specification. + +`Expression.as()` doesn’t do anymore a real type conversions, it’s just an unsafe typecast on the Expression object itself. + +In order to perform an actual typecast, `org.hibernate.query.criteria.JpaExpression#cast(Class)` can be used. + +E.g. +``` +( (JpaExpression) from.get( "theInt" ) ).cast( String.class ) +```