HHH-15725 Criteria API Expression.as adds cast even when the cast type is equal to the expression type

This commit is contained in:
Andrea Boriero 2022-11-24 11:16:53 +01:00 committed by Steve Ebersole
parent aece493697
commit 6a1581cf4a
15 changed files with 235 additions and 10 deletions

View File

@ -301,4 +301,9 @@ public class SingularAttributeImpl<D,J>
public SqmPath<J> createSqmPath(SqmPath<?> lhs, SqmPathSource<?> intermediatePathSource) {
return sqmPathSource.createSqmPath( lhs, intermediatePathSource );
}
@Override
public JavaType<?> getRelationalJavaType() {
return sqmPathSource.getRelationalJavaType();
}
}

View File

@ -56,4 +56,6 @@ public interface JpaExpression<T> extends JpaSelection<T>, Expression<T> {
JpaPredicate equalTo(Expression<T> that);
JpaPredicate equalTo(T that);
<X> JpaExpression<X> cast(Class<X> type);
}

View File

@ -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> {
T visitMapEntryFunction(SqmMapEntryReference<?, ?> function);
T visitFullyQualifiedClass(Class<?> namedClass);
T visitAsWrapperExpression(AsWrapperSqmExpression<?> expression);
}

View File

@ -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<Object> {
return null;
}
@Override
public Object visitAsWrapperExpression(AsWrapperSqmExpression expression) {
return null;
}
@Override
public Object visitModifiedSubQueryExpression(SqmModifiedSubQueryExpression expression) {
return null;

View File

@ -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<Obj
return sqmFieldLiteral;
}
@Override
public Object visitAsWrapperExpression(AsWrapperSqmExpression<?> expression) {
expression.getExpression().accept( this );
return expression;
}
}

View File

@ -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<T extends Statement> 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()

View File

@ -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<B> implements Expression, DomainResultProducer<B> {
private final Expression wrappedExpression;
private final BasicType<B> expressionType;
public AsWrappedExpression(Expression wrappedExpression, BasicType<B> 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<B> 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<B>) wrappedExpression ).applySqlSelections( creationState );
}
}

View File

@ -197,4 +197,9 @@ public class SqmBasicValuedSimplePath<T>
public <X> X accept(SemanticQueryWalker<X> walker) {
return walker.visitBasicValuedPath( this );
}
@Override
public JavaType<?> getRelationalJavaType() {
return super.getExpressible().getRelationalJavaType();
}
}

View File

@ -121,4 +121,9 @@ public class SqmEmbeddedValuedSimplePath<T>
public Class<T> getBindableJavaType() {
return getJavaType();
}
@Override
public JavaType<?> getRelationalJavaType() {
return super.getExpressible().getRelationalJavaType();
}
}

View File

@ -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<T> extends AbstractJpaSelection<T> i
@Override
public <X> SqmExpression<X> as(Class<X> type) {
return nodeBuilder().cast( this, type );
final BasicType<X> 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

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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<T> extends AbstractSqmExpression<T> {
private final SqmExpression<?> expression;
AsWrapperSqmExpression(SqmExpressible<T> type, SqmExpression<?> expression) {
super( type, expression.nodeBuilder() );
this.expression = expression;
}
@Override
public <X> X accept(SemanticQueryWalker<X> 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 <X> SqmExpression<X> as(Class<X> type) {
return expression.as( type );
}
@Override
public SqmExpression<T> copy(SqmCopyContext context) {
return new AsWrapperSqmExpression<>( getExpressible(), expression.copy( context ) );
}
public SqmExpression<?> getExpression() {
return expression;
}
@Override
public BasicType<T> getNodeType() {
return (BasicType<T>) super.getNodeType();
}
}

View File

@ -125,4 +125,9 @@ public interface SqmExpression<T> extends SqmSelectableNode<T>, JpaExpression<T>
);
}
@Override
default <X> SqmExpression<X> cast(Class<X> type) {
return castAs( nodeBuilder().getTypeConfiguration().getBasicTypeForJavaType( type ) );
}
}

View File

@ -50,7 +50,6 @@ import org.hibernate.testing.orm.junit.Jpa;
Thing.class,
ThingWithQuantity.class,
VersionedEntity.class
}
)
})
public abstract class AbstractCriteriaTest {
}

View File

@ -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<String> 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<String> 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 ) );
} );
}

View File

@ -94,3 +94,17 @@ You can find more details about embeddable inheritance in the dedicated link:{us
== 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.
[[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()` doesnt do anymore a real type conversions, its 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 )
```