HHH-15071 Apply type inference in SQM for like predicate and avoid NPE in query parameters

This commit is contained in:
Christian Beikov 2022-03-08 15:43:14 +01:00
parent 3d55855a87
commit 7a55c7b34b
6 changed files with 52 additions and 11 deletions

View File

@ -58,6 +58,6 @@ public abstract class AbstractQueryParameter<T> implements QueryParameterImpleme
@Override
public Class<T> getParameterType() {
return anticipatedType.getBindableJavaType();
return anticipatedType == null ? null : anticipatedType.getBindableJavaType();
}
}

View File

@ -92,6 +92,7 @@ import org.hibernate.metamodel.mapping.ordering.OrderByFragment;
import org.hibernate.metamodel.model.convert.internal.OrdinalEnumValueConverter;
import org.hibernate.metamodel.model.convert.spi.BasicValueConverter;
import org.hibernate.metamodel.model.domain.BasicDomainType;
import org.hibernate.metamodel.model.domain.EmbeddableDomainType;
import org.hibernate.metamodel.model.domain.EntityDomainType;
import org.hibernate.metamodel.model.domain.PluralPersistentAttribute;
import org.hibernate.metamodel.model.domain.internal.BasicSqmPathSource;
@ -4805,7 +4806,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
return (BasicValuedMapping) paramSqmType;
}
if ( paramSqmType instanceof CompositeSqmPathSource ) {
if ( paramSqmType instanceof CompositeSqmPathSource || paramSqmType instanceof EmbeddableDomainType<?> ) {
// Try to infer the value mapping since the other side apparently is a path source
final MappingModelExpressible<?> inferredValueMapping = getInferredValueMapping();
if ( inferredValueMapping != null ) {

View File

@ -12,6 +12,7 @@ import java.util.Collection;
import java.util.function.Consumer;
import jakarta.persistence.criteria.Expression;
import org.hibernate.Internal;
import org.hibernate.annotations.Remove;
import org.hibernate.query.ReturnableType;
import org.hibernate.metamodel.model.domain.DomainType;
@ -49,11 +50,8 @@ public interface SqmExpression<T> extends SqmSelectableNode<T>, JpaExpression<T>
* @apiNote The SqmExpressible type parameter is dropped here because
* the inference could technically cause a change in Java type (i.e.
* an implicit cast)
*
* @deprecated - type inference is now handled during the SQM -> SQL AST transformation
*/
@Remove
@Deprecated
@Internal
void applyInferableType(SqmExpressible<?> type);
@Override

View File

@ -6,8 +6,10 @@
*/
package org.hibernate.query.sqm.tree.predicate;
import org.hibernate.query.internal.QueryHelper;
import org.hibernate.query.sqm.NodeBuilder;
import org.hibernate.query.sqm.SemanticQueryWalker;
import org.hibernate.query.sqm.SqmExpressible;
import org.hibernate.query.sqm.tree.SqmCopyContext;
import org.hibernate.query.sqm.tree.expression.SqmExpression;
@ -49,6 +51,15 @@ public class SqmLikePredicate extends AbstractNegatableSqmPredicate {
this.pattern = pattern;
this.escapeCharacter = escapeCharacter;
this.isCaseSensitive = isCaseSensitive;
final SqmExpressible<?> expressibleType = QueryHelper.highestPrecedenceType(
matchExpression.getNodeType(),
pattern.getNodeType()
);
matchExpression.applyInferableType( expressibleType );
pattern.applyInferableType( expressibleType );
if ( escapeCharacter != null ) {
escapeCharacter.applyInferableType( expressibleType );
}
}
public SqmLikePredicate(

View File

@ -10,28 +10,33 @@ import org.hibernate.query.sqm.NodeBuilder;
import org.hibernate.query.sqm.SemanticQueryWalker;
import org.hibernate.query.sqm.tree.SqmCopyContext;
import org.hibernate.query.sqm.tree.domain.SqmPath;
import org.hibernate.query.sqm.tree.domain.SqmPluralValuedSimplePath;
import org.hibernate.query.sqm.tree.expression.SqmExpression;
/**
* @author Steve Ebersole
*/
public class SqmMemberOfPredicate extends AbstractNegatableSqmPredicate {
private final SqmExpression leftHandExpression;
private final SqmExpression<?> leftHandExpression;
private final SqmPath<?> pluralPath;
public SqmMemberOfPredicate(SqmExpression leftHandExpression, SqmPath<?> pluralPath, NodeBuilder nodeBuilder) {
public SqmMemberOfPredicate(SqmExpression<?> leftHandExpression, SqmPath<?> pluralPath, NodeBuilder nodeBuilder) {
this( leftHandExpression, pluralPath, false, nodeBuilder );
}
public SqmMemberOfPredicate(
SqmExpression leftHandExpression,
SqmPath pluralPath,
SqmExpression<?> leftHandExpression,
SqmPath<?> pluralPath,
boolean negated,
NodeBuilder nodeBuilder) {
super( negated, nodeBuilder );
this.pluralPath = pluralPath;
this.leftHandExpression = leftHandExpression;
leftHandExpression.applyInferableType(
( (SqmPluralValuedSimplePath<?>) pluralPath ).getReferencedPathSource().getElementType()
);
}
@Override
@ -53,7 +58,7 @@ public class SqmMemberOfPredicate extends AbstractNegatableSqmPredicate {
return predicate;
}
public SqmExpression getLeftHandExpression() {
public SqmExpression<?> getLeftHandExpression() {
return leftHandExpression;
}

View File

@ -9,11 +9,14 @@ package org.hibernate.orm.test.query.hql;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.Set;
import jakarta.persistence.Embeddable;
import jakarta.persistence.Embedded;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Parameter;
import jakarta.persistence.Temporal;
import jakarta.persistence.TemporalType;
@ -151,6 +154,29 @@ public class ParameterTests extends BaseSqmUnitTest {
);
}
@Test
public void testParamTypes() {
inTransaction(
session -> {
final Set<Parameter<?>> parameters = session.createQuery(
"from Person p where p.pk = :pk and p.name.firstName like :firstName",
Person.class
).getParameters();
assertThat( parameters.size(), equalTo( 2 ) );
for ( Parameter<?> parameter : parameters ) {
switch ( parameter.getName() ) {
case "pk":
assertThat( parameter.getParameterType(), equalTo( Integer.class ) );
break;
case "firstName":
assertThat( parameter.getParameterType(), equalTo( String.class ) );
break;
}
}
}
);
}
@Override
protected boolean exportSchema() {
return true;