HHH-16786 Fix NPE in SqmParameterInterpretation when binding null for select item in insert-select statement

This commit is contained in:
Christian Beikov 2023-06-13 10:42:03 +02:00
parent 521a36f5d6
commit be38746810
3 changed files with 36 additions and 71 deletions

View File

@ -192,7 +192,6 @@ 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.domain.SqmTreatedRoot;
import org.hibernate.query.sqm.tree.expression.Conversion;
import org.hibernate.query.sqm.tree.expression.JpaCriteriaParameter;
import org.hibernate.query.sqm.tree.expression.SqmAliasedNodeRef;
@ -5509,11 +5508,8 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
);
}
return new SqmParameterInterpretation(
sqmParameter,
queryParameter,
jdbcParametersForSqm,
valueMapping,
qp -> binding
valueMapping
);
}

View File

@ -7,9 +7,7 @@
package org.hibernate.query.sqm.sql.internal;
import java.util.List;
import java.util.function.Function;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.mapping.DiscriminatedAssociationModelPart;
import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart;
import org.hibernate.metamodel.mapping.EntityAssociationMapping;
@ -17,12 +15,7 @@ import org.hibernate.metamodel.mapping.EntityValuedModelPart;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.MappingModelExpressible;
import org.hibernate.type.descriptor.converter.spi.BasicValueConverter;
import org.hibernate.query.BindableType;
import org.hibernate.query.SemanticException;
import org.hibernate.query.spi.QueryParameterBinding;
import org.hibernate.query.spi.QueryParameterImplementor;
import org.hibernate.query.sqm.SqmExpressible;
import org.hibernate.query.sqm.tree.expression.SqmParameter;
import org.hibernate.sql.ast.SqlAstWalker;
import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.ast.tree.expression.Expression;
@ -38,22 +31,13 @@ import org.hibernate.type.descriptor.java.JavaType;
* @author Steve Ebersole
*/
public class SqmParameterInterpretation implements Expression, DomainResultProducer, SqlTupleContainer {
private final SqmParameter<?> sqmParameter;
private final QueryParameterImplementor<?> queryParameter;
private final MappingModelExpressible<?> valueMapping;
private final Function<QueryParameterImplementor<?>, QueryParameterBinding<?>> queryParameterBindingResolver;
private final List<JdbcParameter> jdbcParameters;
private Expression resolvedExpression;
public SqmParameterInterpretation(
SqmParameter<?> sqmParameter,
QueryParameterImplementor<?> queryParameter,
List<JdbcParameter> jdbcParameters,
MappingModelExpressible<?> valueMapping,
Function<QueryParameterImplementor<?>, QueryParameterBinding<?>> queryParameterBindingResolver) {
this.sqmParameter = sqmParameter;
this.queryParameter = queryParameter;
this.queryParameterBindingResolver = queryParameterBindingResolver;
MappingModelExpressible<?> valueMapping) {
if ( valueMapping instanceof EntityAssociationMapping ) {
final EntityAssociationMapping mapping = (EntityAssociationMapping) valueMapping;
@ -109,39 +93,21 @@ public class SqmParameterInterpretation implements Expression, DomainResultProdu
throw new SemanticException( "Composite query parameter cannot be used in select" );
}
BindableType<?> nodeType = sqmParameter.getNodeType();
if ( nodeType == null ) {
final QueryParameterBinding<?> binding = queryParameterBindingResolver.apply( queryParameter );
nodeType = binding.getBindType();
}
final SessionFactoryImplementor sessionFactory = creationState.getSqlAstCreationState()
.getCreationContext()
.getSessionFactory();
final SqmExpressible<?> sqmExpressible = nodeType.resolveExpressible( sessionFactory );
final JavaType<?> jdbcJavaType;
final BasicValueConverter<?, ?> converter;
if ( sqmExpressible instanceof JdbcMapping ) {
final JdbcMapping jdbcMapping = (JdbcMapping) sqmExpressible;
jdbcJavaType = jdbcMapping.getJdbcJavaType();
converter = jdbcMapping.getValueConverter();
}
else {
jdbcJavaType = sqmExpressible.getExpressibleJavaType();
converter = null;
}
final JdbcMapping jdbcMapping = resolvedExpression.getExpressionType().getSingleJdbcMapping();
final JavaType<?> jdbcJavaType = jdbcMapping.getJdbcJavaType();
final BasicValueConverter<?, ?> converter = jdbcMapping.getValueConverter();
final SqlSelection sqlSelection = creationState.getSqlAstCreationState().getSqlExpressionResolver().resolveSqlSelection(
resolvedExpression,
jdbcJavaType,
null,
sessionFactory.getTypeConfiguration()
creationState.getSqlAstCreationState().getCreationContext().getSessionFactory().getTypeConfiguration()
);
return new BasicResult(
sqlSelection.getValuesArrayPosition(),
resultVariable,
sqmExpressible.getExpressibleJavaType(),
jdbcMapping.getMappedJavaType(),
converter
);
}
@ -165,31 +131,11 @@ public class SqmParameterInterpretation implements Expression, DomainResultProdu
throw new SemanticException( "Composite query parameter cannot be used in select" );
}
BindableType<?> nodeType = sqmParameter.getNodeType();
if ( nodeType == null ) {
final QueryParameterBinding<?> binding = queryParameterBindingResolver.apply( queryParameter );
nodeType = binding.getBindType();
}
final SessionFactoryImplementor sessionFactory = creationState.getSqlAstCreationState()
.getCreationContext()
.getSessionFactory();
final SqmExpressible<?> sqmExpressible = nodeType.resolveExpressible( sessionFactory );
final JavaType<?> jdbcJavaType;
if ( sqmExpressible instanceof JdbcMapping ) {
final JdbcMapping jdbcMapping = (JdbcMapping) sqmExpressible;
jdbcJavaType = jdbcMapping.getJdbcJavaType();
}
else {
jdbcJavaType = sqmExpressible.getExpressibleJavaType();
}
return creationState.getSqlAstCreationState().getSqlExpressionResolver().resolveSqlSelection(
resolvedExpression,
jdbcJavaType,
resolvedExpression.getExpressionType().getSingleJdbcMapping().getMappedJavaType(),
null,
sessionFactory.getTypeConfiguration()
creationState.getSqlAstCreationState().getCreationContext().getSessionFactory().getTypeConfiguration()
);
}
}

View File

@ -22,6 +22,7 @@ import org.junit.jupiter.api.Test;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
import static org.junit.jupiter.api.Assertions.assertEquals;
@ -37,8 +38,8 @@ public class InsertSelectTests {
public void prepareTestData(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
session.persist( new EntitySource( "A" ) );
session.persist( new EntitySource( "A" ) );
session.persist( new EntitySource( 1, "A" ) );
session.persist( new EntitySource( 2, "A" ) );
}
);
}
@ -100,25 +101,47 @@ public class InsertSelectTests {
);
}
@Test
@TestForIssue( jiraKey = "HHH-16786")
public void testInsertSelectParameterInference(SessionFactoryScope scope) {
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
scope.inTransaction(
session -> {
statementInspector.clear();
session.createMutationQuery(
"insert into EntityEntry (id, name, source) " +
"select 1, 'abc', :source from EntityEntry e"
).setParameter( "source", null ).executeUpdate();
statementInspector.assertExecutedCount( 1 );
}
);
}
@Entity(name = "EntityEntry")
public static class EntityEntry {
@Id
@GeneratedValue
Integer id;
String name;
@ManyToOne
EntitySource source;
}
@Entity(name = "EntitySource")
public static class EntitySource {
@Id
@GeneratedValue
Integer id;
String name;
public EntitySource() {
}
public EntitySource(String name) {
public EntitySource(Integer id) {
this.id = id;
}
public EntitySource(Integer id, String name) {
this.id = id;
this.name = name;
}
}