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

View File

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

View File

@ -6,8 +6,10 @@
*/ */
package org.hibernate.query.sqm.tree.predicate; package org.hibernate.query.sqm.tree.predicate;
import org.hibernate.query.internal.QueryHelper;
import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.NodeBuilder;
import org.hibernate.query.sqm.SemanticQueryWalker; 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.SqmCopyContext;
import org.hibernate.query.sqm.tree.expression.SqmExpression; import org.hibernate.query.sqm.tree.expression.SqmExpression;
@ -49,6 +51,15 @@ public class SqmLikePredicate extends AbstractNegatableSqmPredicate {
this.pattern = pattern; this.pattern = pattern;
this.escapeCharacter = escapeCharacter; this.escapeCharacter = escapeCharacter;
this.isCaseSensitive = isCaseSensitive; 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( 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.SemanticQueryWalker;
import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmCopyContext;
import org.hibernate.query.sqm.tree.domain.SqmPath; import org.hibernate.query.sqm.tree.domain.SqmPath;
import org.hibernate.query.sqm.tree.domain.SqmPluralValuedSimplePath;
import org.hibernate.query.sqm.tree.expression.SqmExpression; import org.hibernate.query.sqm.tree.expression.SqmExpression;
/** /**
* @author Steve Ebersole * @author Steve Ebersole
*/ */
public class SqmMemberOfPredicate extends AbstractNegatableSqmPredicate { public class SqmMemberOfPredicate extends AbstractNegatableSqmPredicate {
private final SqmExpression leftHandExpression; private final SqmExpression<?> leftHandExpression;
private final SqmPath<?> pluralPath; 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 ); this( leftHandExpression, pluralPath, false, nodeBuilder );
} }
public SqmMemberOfPredicate( public SqmMemberOfPredicate(
SqmExpression leftHandExpression, SqmExpression<?> leftHandExpression,
SqmPath pluralPath, SqmPath<?> pluralPath,
boolean negated, boolean negated,
NodeBuilder nodeBuilder) { NodeBuilder nodeBuilder) {
super( negated, nodeBuilder ); super( negated, nodeBuilder );
this.pluralPath = pluralPath; this.pluralPath = pluralPath;
this.leftHandExpression = leftHandExpression; this.leftHandExpression = leftHandExpression;
leftHandExpression.applyInferableType(
( (SqmPluralValuedSimplePath<?>) pluralPath ).getReferencedPathSource().getElementType()
);
} }
@Override @Override
@ -53,7 +58,7 @@ public class SqmMemberOfPredicate extends AbstractNegatableSqmPredicate {
return predicate; return predicate;
} }
public SqmExpression getLeftHandExpression() { public SqmExpression<?> getLeftHandExpression() {
return leftHandExpression; return leftHandExpression;
} }

View File

@ -9,11 +9,14 @@ package org.hibernate.orm.test.query.hql;
import java.time.Instant; import java.time.Instant;
import java.time.temporal.ChronoUnit; import java.time.temporal.ChronoUnit;
import java.util.Date; import java.util.Date;
import java.util.Set;
import jakarta.persistence.Embeddable; import jakarta.persistence.Embeddable;
import jakarta.persistence.Embedded; import jakarta.persistence.Embedded;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
import jakarta.persistence.Id; import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne; import jakarta.persistence.ManyToOne;
import jakarta.persistence.Parameter;
import jakarta.persistence.Temporal; import jakarta.persistence.Temporal;
import jakarta.persistence.TemporalType; 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 @Override
protected boolean exportSchema() { protected boolean exportSchema() {
return true; return true;