fix NPE when selecting enum value

This commit is contained in:
Steve Ebersole 2022-01-17 20:38:15 -06:00
parent 53d38262ba
commit 6f1ddc32dd
7 changed files with 219 additions and 13 deletions

View File

@ -82,6 +82,7 @@ import org.hibernate.metamodel.mapping.internal.EmbeddedCollectionPart;
import org.hibernate.metamodel.mapping.internal.EntityCollectionPart;
import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping;
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.EntityDomainType;
@ -91,15 +92,15 @@ import org.hibernate.metamodel.model.domain.internal.DiscriminatorSqmPath;
import org.hibernate.persister.entity.AbstractEntityPersister;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.persister.entity.Joinable;
import org.hibernate.query.ReturnableType;
import org.hibernate.query.BindableType;
import org.hibernate.query.BinaryArithmeticOperator;
import org.hibernate.query.BindableType;
import org.hibernate.query.CastType;
import org.hibernate.query.ComparisonOperator;
import org.hibernate.query.DynamicInstantiationNature;
import org.hibernate.query.FetchClauseType;
import org.hibernate.query.NavigablePath;
import org.hibernate.query.QueryLogging;
import org.hibernate.query.ReturnableType;
import org.hibernate.query.SemanticException;
import org.hibernate.query.SortOrder;
import org.hibernate.query.TemporalUnit;
@ -150,12 +151,12 @@ import org.hibernate.query.sqm.tree.domain.SqmAnyValuedSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmBasicValuedSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmCorrelatedRootJoin;
import org.hibernate.query.sqm.tree.domain.SqmCorrelation;
import org.hibernate.query.sqm.tree.domain.SqmElementAggregateFunction;
import org.hibernate.query.sqm.tree.domain.SqmEmbeddedValuedSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmEntityValuedSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmIndexAggregateFunction;
import org.hibernate.query.sqm.tree.domain.SqmIndexedCollectionAccessPath;
import org.hibernate.query.sqm.tree.domain.SqmMapEntryReference;
import org.hibernate.query.sqm.tree.domain.SqmElementAggregateFunction;
import org.hibernate.query.sqm.tree.domain.SqmIndexAggregateFunction;
import org.hibernate.query.sqm.tree.domain.SqmPath;
import org.hibernate.query.sqm.tree.domain.SqmPluralPartJoin;
import org.hibernate.query.sqm.tree.domain.SqmPluralValuedSimplePath;
@ -270,6 +271,7 @@ import org.hibernate.sql.ast.tree.expression.CaseSimpleExpression;
import org.hibernate.sql.ast.tree.expression.CastTarget;
import org.hibernate.sql.ast.tree.expression.Collation;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.expression.ConvertedQueryLiteral;
import org.hibernate.sql.ast.tree.expression.Distinct;
import org.hibernate.sql.ast.tree.expression.Duration;
import org.hibernate.sql.ast.tree.expression.DurationUnit;
@ -345,7 +347,11 @@ import org.hibernate.sql.results.internal.SqlSelectionImpl;
import org.hibernate.sql.results.internal.StandardEntityGraphTraversalStateImpl;
import org.hibernate.type.BasicType;
import org.hibernate.type.JavaObjectType;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.java.BasicJavaType;
import org.hibernate.type.descriptor.java.EnumJavaTypeDescriptor;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcTypeDescriptorIndicators;
import org.hibernate.type.spi.TypeConfiguration;
import org.hibernate.usertype.UserVersionType;
@ -5274,6 +5280,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
return (Expression) sqmExpression.getDiscriminatorSource().accept( this );
}
@SuppressWarnings({"raw","unchecked"})
@Override
public Object visitEnumLiteral(SqmEnumLiteral<?> sqmEnumLiteral) {
final BasicValuedMapping inferrableType = (BasicValuedMapping) inferrableTypeAccessStack.getCurrent().get();
@ -5285,9 +5292,17 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
return new QueryLiteral<>( jdbcValue, inferredPart );
}
return new QueryLiteral<>(
final EnumJavaTypeDescriptor<?> enumJtd = sqmEnumLiteral.getExpressableJavaTypeDescriptor();
final JdbcType jdbcType = getTypeConfiguration().getJdbcTypeDescriptorRegistry().getDescriptor( SqlTypes.TINYINT );
final BasicJavaType<Integer> relationalJtd = (BasicJavaType) getTypeConfiguration()
.getJavaTypeDescriptorRegistry()
.getDescriptor( Integer.class );
final BasicType<?> jdbcMappingType = getTypeConfiguration().getBasicTypeRegistry().resolve( relationalJtd, jdbcType );
return new ConvertedQueryLiteral(
sqmEnumLiteral.getEnumValue(),
(BasicValuedMapping) determineValueMapping( sqmEnumLiteral )
new OrdinalEnumValueConverter( enumJtd, jdbcType, relationalJtd ),
jdbcMappingType
);
}

View File

@ -18,6 +18,7 @@ import org.hibernate.sql.ast.tree.expression.CaseSimpleExpression;
import org.hibernate.sql.ast.tree.expression.CastTarget;
import org.hibernate.sql.ast.tree.expression.Collation;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.expression.ConvertedQueryLiteral;
import org.hibernate.sql.ast.tree.expression.Distinct;
import org.hibernate.sql.ast.tree.expression.Duration;
import org.hibernate.sql.ast.tree.expression.DurationUnit;
@ -157,6 +158,8 @@ public interface SqlAstWalker {
void visitQueryLiteral(QueryLiteral<?> queryLiteral);
void acceptConvertedQueryLiteral(ConvertedQueryLiteral<?,?> convertedQueryLiteral);
void visitUnaryOperationExpression(UnaryOperation unaryOperationExpression);
void visitModifiedSubQueryExpression(ModifiedSubQueryExpression expression);

View File

@ -87,6 +87,7 @@ import org.hibernate.sql.ast.tree.expression.CaseSimpleExpression;
import org.hibernate.sql.ast.tree.expression.CastTarget;
import org.hibernate.sql.ast.tree.expression.Collation;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.expression.ConvertedQueryLiteral;
import org.hibernate.sql.ast.tree.expression.Distinct;
import org.hibernate.sql.ast.tree.expression.Duration;
import org.hibernate.sql.ast.tree.expression.DurationUnit;
@ -4556,6 +4557,11 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
visitLiteral( queryLiteral );
}
@Override
public void acceptConvertedQueryLiteral(ConvertedQueryLiteral<?, ?> convertedQueryLiteral) {
visitLiteral( convertedQueryLiteral );
}
private void visitLiteral(Literal literal) {
if ( literal.getLiteralValue() == null ) {
appendSql( SqlAppender.NULL_KEYWORD );

View File

@ -21,6 +21,7 @@ import org.hibernate.sql.ast.tree.expression.CaseSimpleExpression;
import org.hibernate.sql.ast.tree.expression.CastTarget;
import org.hibernate.sql.ast.tree.expression.Collation;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.expression.ConvertedQueryLiteral;
import org.hibernate.sql.ast.tree.expression.Distinct;
import org.hibernate.sql.ast.tree.expression.Duration;
import org.hibernate.sql.ast.tree.expression.DurationUnit;
@ -469,6 +470,10 @@ public class AbstractSqlAstWalker implements SqlAstWalker {
public void visitQueryLiteral(QueryLiteral<?> queryLiteral) {
}
@Override
public void acceptConvertedQueryLiteral(ConvertedQueryLiteral<?,?> convertedQueryLiteral) {
}
@Override
public void visitEntityTypeLiteral(EntityTypeLiteral expression) {
}

View File

@ -0,0 +1,145 @@
/*
* 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.sql.ast.tree.expression;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import org.hibernate.metamodel.mapping.BasicValuedMapping;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.JdbcMappingContainer;
import org.hibernate.metamodel.model.convert.spi.BasicValueConverter;
import org.hibernate.query.sqm.sql.internal.DomainResultProducer;
import org.hibernate.sql.ast.SqlAstWalker;
import org.hibernate.sql.ast.spi.SqlAstCreationState;
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
import org.hibernate.sql.exec.spi.ExecutionContext;
import org.hibernate.sql.exec.spi.JdbcParameterBindings;
import org.hibernate.sql.results.graph.AssemblerCreationState;
import org.hibernate.sql.results.graph.DomainResult;
import org.hibernate.sql.results.graph.DomainResultAssembler;
import org.hibernate.sql.results.graph.DomainResultCreationState;
import org.hibernate.sql.results.graph.FetchParentAccess;
import org.hibernate.sql.results.jdbc.spi.JdbcValuesSourceProcessingOptions;
import org.hibernate.sql.results.jdbc.spi.RowProcessingState;
import org.hibernate.type.descriptor.java.JavaType;
/**
* Mainly intended for cases where we have a converter for a literal,
* but not a full ConvertibleModelPart.
*
* @author Steve Ebersole
*/
public class ConvertedQueryLiteral<D,R> implements Literal, DomainResultProducer<D> {
private final D domainLiteralValue;
private final R relationalLiteralValue;
private final BasicValueConverter<D,R> converter;
private final BasicValuedMapping relationalMapping;
public ConvertedQueryLiteral(
D domainLiteralValue,
BasicValueConverter<D, R> converter,
BasicValuedMapping relationalMapping) {
this.domainLiteralValue = domainLiteralValue;
this.converter = converter;
this.relationalMapping = relationalMapping;
this.relationalLiteralValue = converter.toRelationalValue( domainLiteralValue );
}
@Override
public Object getLiteralValue() {
return relationalLiteralValue;
}
@Override
public JdbcMapping getJdbcMapping() {
return relationalMapping.getJdbcMapping();
}
@Override
public DomainResult<D> createDomainResult(String resultVariable, DomainResultCreationState creationState) {
applySqlSelections( creationState );
return new ConstantDomainResult<>( domainLiteralValue, converter.getDomainJavaDescriptor(), resultVariable );
}
@Override
public void applySqlSelections(DomainResultCreationState creationState) {
// if there is another DomainResultProducer that generates sql-selections,
// we actually would not even need to generate this. we do not know that
// here.
final SqlAstCreationState sqlAstCreationState = creationState.getSqlAstCreationState();
final SqlExpressionResolver expressionResolver = sqlAstCreationState.getSqlExpressionResolver();
expressionResolver.resolveSqlSelection(
this,
relationalMapping.getExpressableJavaTypeDescriptor(),
sqlAstCreationState.getCreationContext().getDomainModel().getTypeConfiguration()
);
}
@Override
public void bindParameterValue(
PreparedStatement statement,
int startPosition,
JdbcParameterBindings jdbcParameterBindings,
ExecutionContext executionContext) throws SQLException {
//noinspection unchecked
relationalMapping.getJdbcMapping().getJdbcValueBinder().bind(
statement,
relationalLiteralValue,
startPosition,
executionContext.getSession()
);
}
@Override
public void accept(SqlAstWalker sqlTreeWalker) {
sqlTreeWalker.acceptConvertedQueryLiteral( this );
}
@Override
public JdbcMappingContainer getExpressionType() {
return relationalMapping.getJdbcMapping();
}
private static class ConstantDomainResult<D> implements DomainResult<D>, DomainResultAssembler<D> {
private final D literal;
private final JavaType<D> javaType;
private final String resultAlias;
public ConstantDomainResult(D literal, JavaType<D> javaType, String resultAlias) {
this.literal = literal;
this.javaType = javaType;
this.resultAlias = resultAlias;
}
@Override
public String getResultVariable() {
return resultAlias;
}
@Override
public JavaType<?> getResultJavaTypeDescriptor() {
return javaType;
}
@Override
public DomainResultAssembler<D> createResultAssembler(FetchParentAccess parentAccess, AssemblerCreationState creationState) {
return this;
}
@Override
public D assemble(RowProcessingState rowProcessingState, JdbcValuesSourceProcessingOptions options) {
return literal;
}
@Override
public JavaType<D> getAssembledJavaTypeDescriptor() {
return javaType;
}
}
}

View File

@ -12,7 +12,6 @@ import java.sql.SQLException;
import org.hibernate.metamodel.mapping.BasicValuedMapping;
import org.hibernate.metamodel.mapping.ConvertibleModelPart;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.model.convert.spi.BasicValueConverter;
import org.hibernate.query.sqm.sql.internal.DomainResultProducer;
import org.hibernate.sql.ast.SqlAstWalker;
import org.hibernate.sql.ast.spi.SqlExpressionResolver;

View File

@ -7,6 +7,7 @@
package org.hibernate.orm.test.query.hql;
import org.hibernate.testing.orm.domain.StandardDomainModel;
import org.hibernate.testing.orm.domain.animal.Classification;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.ServiceRegistry;
import org.hibernate.testing.orm.junit.SessionFactory;
@ -23,8 +24,11 @@ import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import org.hibernate.query.spi.QueryImplementor;
import org.hibernate.type.descriptor.java.PrimitiveByteArrayJavaTypeDescriptor;
import org.assertj.core.api.Assertions;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
@ -267,17 +271,46 @@ public class LiteralTests {
}
@Test
public void testEnums(SessionFactoryScope scope) {
public void testEnumLiteralInPredicate(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
session.createQuery( "from Zoo where classification=COOL" ).getResultList();
session.createQuery( "from Zoo where classification=Classification.COOL" ).getResultList();
session.createQuery( "from Zoo where classification=org.hibernate.testing.orm.domain.animal.Classification.COOL" ).getResultList();
assertThat( session.createQuery( "select org.hibernate.testing.orm.domain.animal.Classification.LAME" )
.getSingleResult(), is( org.hibernate.testing.orm.domain.animal.Classification.LAME ) );
assertThat( session.createQuery( "select org.hibernate.testing.orm.domain.gambit.EntityOfBasics$Gender.MALE" )
.getSingleResult(), is( org.hibernate.testing.orm.domain.gambit.EntityOfBasics.Gender.MALE ) );
}
);
}
@Test
public void testIntegerLiteralInSelect(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
assertThat( session.createQuery( "select 1" ).getSingleResult(), is( 1 ) );
}
);
}
@Test
public void testEnumLiteralInSelect(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
{
final QueryImplementor<Object[]> query = session.createQuery( "select 1, org.hibernate.testing.orm.domain.animal.Classification.LAME" );
final Object[] result = query.getSingleResult();
final Object classification = result[ 1 ];
Assertions.assertThat( classification ).isEqualTo( Classification.LAME );
}
{
final QueryImplementor<Classification> query = session.createQuery( "select org.hibernate.testing.orm.domain.animal.Classification.LAME" );
final Classification result = query.getSingleResult();
Assertions.assertThat( result ).isEqualTo( Classification.LAME );
}
}
);
}
}