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 2ad5cdd9b9
commit 1a9732a5c2
3 changed files with 36 additions and 70 deletions

View File

@ -5499,11 +5499,8 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
); );
} }
return new SqmParameterInterpretation( return new SqmParameterInterpretation(
sqmParameter,
queryParameter,
jdbcParametersForSqm, jdbcParametersForSqm,
valueMapping, valueMapping
qp -> binding
); );
} }

View File

@ -7,9 +7,7 @@
package org.hibernate.query.sqm.sql.internal; package org.hibernate.query.sqm.sql.internal;
import java.util.List; 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.DiscriminatedAssociationModelPart;
import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart; import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart;
import org.hibernate.metamodel.mapping.EntityAssociationMapping; 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.JdbcMapping;
import org.hibernate.metamodel.mapping.MappingModelExpressible; import org.hibernate.metamodel.mapping.MappingModelExpressible;
import org.hibernate.type.descriptor.converter.spi.BasicValueConverter; import org.hibernate.type.descriptor.converter.spi.BasicValueConverter;
import org.hibernate.query.BindableType;
import org.hibernate.query.SemanticException; 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.SqlAstWalker;
import org.hibernate.sql.ast.spi.SqlSelection; import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.ast.tree.expression.Expression; import org.hibernate.sql.ast.tree.expression.Expression;
@ -38,22 +31,13 @@ import org.hibernate.type.descriptor.java.JavaType;
* @author Steve Ebersole * @author Steve Ebersole
*/ */
public class SqmParameterInterpretation implements Expression, DomainResultProducer, SqlTupleContainer { public class SqmParameterInterpretation implements Expression, DomainResultProducer, SqlTupleContainer {
private final SqmParameter<?> sqmParameter;
private final QueryParameterImplementor<?> queryParameter;
private final MappingModelExpressible<?> valueMapping; private final MappingModelExpressible<?> valueMapping;
private final Function<QueryParameterImplementor<?>, QueryParameterBinding<?>> queryParameterBindingResolver;
private final List<JdbcParameter> jdbcParameters; private final List<JdbcParameter> jdbcParameters;
private Expression resolvedExpression; private Expression resolvedExpression;
public SqmParameterInterpretation( public SqmParameterInterpretation(
SqmParameter<?> sqmParameter,
QueryParameterImplementor<?> queryParameter,
List<JdbcParameter> jdbcParameters, List<JdbcParameter> jdbcParameters,
MappingModelExpressible<?> valueMapping, MappingModelExpressible<?> valueMapping) {
Function<QueryParameterImplementor<?>, QueryParameterBinding<?>> queryParameterBindingResolver) {
this.sqmParameter = sqmParameter;
this.queryParameter = queryParameter;
this.queryParameterBindingResolver = queryParameterBindingResolver;
if ( valueMapping instanceof EntityAssociationMapping ) { if ( valueMapping instanceof EntityAssociationMapping ) {
final EntityAssociationMapping mapping = (EntityAssociationMapping) valueMapping; 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" ); throw new SemanticException( "Composite query parameter cannot be used in select" );
} }
BindableType<?> nodeType = sqmParameter.getNodeType(); final JdbcMapping jdbcMapping = resolvedExpression.getExpressionType().getSingleJdbcMapping();
if ( nodeType == null ) { final JavaType<?> jdbcJavaType = jdbcMapping.getJdbcJavaType();
final QueryParameterBinding<?> binding = queryParameterBindingResolver.apply( queryParameter ); final BasicValueConverter<?, ?> converter = jdbcMapping.getValueConverter();
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 SqlSelection sqlSelection = creationState.getSqlAstCreationState().getSqlExpressionResolver().resolveSqlSelection( final SqlSelection sqlSelection = creationState.getSqlAstCreationState().getSqlExpressionResolver().resolveSqlSelection(
resolvedExpression, resolvedExpression,
jdbcJavaType, jdbcJavaType,
null, null,
sessionFactory.getTypeConfiguration() creationState.getSqlAstCreationState().getCreationContext().getSessionFactory().getTypeConfiguration()
); );
return new BasicResult( return new BasicResult(
sqlSelection.getValuesArrayPosition(), sqlSelection.getValuesArrayPosition(),
resultVariable, resultVariable,
sqmExpressible.getExpressibleJavaType(), jdbcMapping.getMappedJavaType(),
converter converter
); );
} }
@ -165,31 +131,11 @@ public class SqmParameterInterpretation implements Expression, DomainResultProdu
throw new SemanticException( "Composite query parameter cannot be used in select" ); 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( return creationState.getSqlAstCreationState().getSqlExpressionResolver().resolveSqlSelection(
resolvedExpression, resolvedExpression,
jdbcJavaType, resolvedExpression.getExpressionType().getSingleJdbcMapping().getMappedJavaType(),
null, 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.Entity;
import jakarta.persistence.GeneratedValue; import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id; import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
@ -37,8 +38,8 @@ public class InsertSelectTests {
public void prepareTestData(SessionFactoryScope scope) { public void prepareTestData(SessionFactoryScope scope) {
scope.inTransaction( scope.inTransaction(
session -> { session -> {
session.persist( new EntitySource( "A" ) ); session.persist( new EntitySource( 1, "A" ) );
session.persist( new EntitySource( "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") @Entity(name = "EntityEntry")
public static class EntityEntry { public static class EntityEntry {
@Id @Id
@GeneratedValue @GeneratedValue
Integer id; Integer id;
String name; String name;
@ManyToOne
EntitySource source;
} }
@Entity(name = "EntitySource") @Entity(name = "EntitySource")
public static class EntitySource { public static class EntitySource {
@Id @Id
@GeneratedValue
Integer id; Integer id;
String name; String name;
public EntitySource() { 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; this.name = name;
} }
} }