Skip to content

Commit

Permalink
HHH-15725 Criteria API Expression.as adds cast even when the cast typ…
Browse files Browse the repository at this point in the history
…e is equal to the expression type
  • Loading branch information
dreab8 authored and sebersole committed Jul 31, 2024
1 parent aece493 commit 6a1581c
Show file tree
Hide file tree
Showing 15 changed files with 235 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -301,4 +301,9 @@ public BindableType getBindableType() {
public SqmPath<J> createSqmPath(SqmPath<?> lhs, SqmPathSource<?> intermediatePathSource) {
return sqmPathSource.createSqmPath( lhs, intermediatePathSource );
}

@Override
public JavaType<?> getRelationalJavaType() {
return sqmPathSource.getRelationalJavaType();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -417,4 +418,6 @@ default T visitCorrelatedRoot(SqmCorrelatedRoot<?> correlatedRoot){
T visitMapEntryFunction(SqmMapEntryReference<?, ?> function);

T visitFullyQualifiedClass(Class<?> namedClass);

T visitAsWrapperExpression(AsWrapperSqmExpression<?> expression);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -981,4 +982,9 @@ public Object visitFieldLiteral(SqmFieldLiteral<?> sqmFieldLiteral) {
return sqmFieldLiteral;
}

@Override
public Object visitAsWrapperExpression(AsWrapperSqmExpression<?> expression) {
expression.getExpression().accept( this );
return expression;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
@@ -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 );
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -197,4 +197,9 @@ public DomainType<T> getSqmType() {
public <X> X accept(SemanticQueryWalker<X> walker) {
return walker.visitBasicValuedPath( this );
}

@Override
public JavaType<?> getRelationalJavaType() {
return super.getExpressible().getRelationalJavaType();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -121,4 +121,9 @@ public Class<T> getJavaType() {
public Class<T> getBindableJavaType() {
return getJavaType();
}

@Override
public JavaType<?> getRelationalJavaType() {
return super.getExpressible().getRelationalJavaType();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -94,7 +94,11 @@ public SqmExpression<String> asString() {

@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
Expand Down
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -125,4 +125,9 @@ default <X> SqmExpression<X> castAs(DomainType<X> type) {
);
}

@Override
default <X> SqmExpression<X> cast(Class<X> type) {
return castAs( nodeBuilder().getTypeConfiguration().getBasicTypeForJavaType( type ) );
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@
Thing.class,
ThingWithQuantity.class,
VersionedEntity.class
}
)
})
public abstract class AbstractCriteriaTest {
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
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;
Expand Down Expand Up @@ -237,14 +238,14 @@ public void testOverlay(SessionFactoryScope scope) {
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 ) );
} );
}

Expand Down Expand Up @@ -300,12 +301,12 @@ public void testReplace(SessionFactoryScope scope) {
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 ) );
} );
}

Expand Down
16 changes: 15 additions & 1 deletion migration-guide.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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.
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 )
```

0 comments on commit 6a1581c

Please sign in to comment.