HHH-16182 - Converted boolean values not always properly handled in predicates

This commit is contained in:
Steve Ebersole 2023-02-27 18:06:38 -06:00
parent 51ef9f494b
commit 0c20980be2
4 changed files with 272 additions and 62 deletions

View File

@ -7,17 +7,25 @@
package org.hibernate.userguide.mapping.basic; package org.hibernate.userguide.mapping.basic;
import java.sql.Types; import java.sql.Types;
import java.util.List;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.internal.BasicAttributeMapping; import org.hibernate.metamodel.mapping.internal.BasicAttributeMapping;
import org.hibernate.metamodel.spi.MappingMetamodelImplementor; import org.hibernate.metamodel.spi.MappingMetamodelImplementor;
import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.query.criteria.HibernateCriteriaBuilder;
import org.hibernate.query.criteria.JpaCriteriaQuery;
import org.hibernate.query.criteria.JpaPath;
import org.hibernate.query.criteria.JpaRoot;
import org.hibernate.type.internal.ConvertedBasicTypeImpl; import org.hibernate.type.internal.ConvertedBasicTypeImpl;
import org.hibernate.testing.orm.junit.DomainModel; import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.Jira; import org.hibernate.testing.orm.junit.Jira;
import org.hibernate.testing.orm.junit.SessionFactory; import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope; import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import jakarta.persistence.Basic; import jakarta.persistence.Basic;
@ -28,6 +36,7 @@ import jakarta.persistence.Table;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.isOneOf; import static org.hamcrest.Matchers.isOneOf;
@ -97,22 +106,211 @@ public class BooleanMappingTests {
} }
} }
@BeforeEach
public void createTestData(SessionFactoryScope scope) {
scope.inTransaction( (session) -> {
final EntityOfBooleans entity = new EntityOfBooleans();
entity.id = 1;
assert !entity.convertedYesNo;
assert !entity.convertedTrueFalse;
assert !entity.convertedNumeric;
session.persist( entity );
} );
}
@AfterEach
public void dropTestData(SessionFactoryScope scope) {
scope.inTransaction( (session) -> {
session.createMutationQuery( "delete EntityOfBooleans" ).executeUpdate();
} );
}
@Test @Test
@Jira( "https://hibernate.atlassian.net/browse/HHH-16182" ) @Jira( "https://hibernate.atlassian.net/browse/HHH-16182" )
public void testQueryLiteralUsage(SessionFactoryScope scope) { public void testComparisonLiteralHandling(SessionFactoryScope scope) {
scope.inTransaction( (session) -> { scope.inTransaction( (session) -> {
session.createSelectionQuery( "from EntityOfBooleans where convertedYesNo = true" ).list(); assertThat(
session.createSelectionQuery( "from EntityOfBooleans where convertedTrueFalse = true" ).list(); session.createSelectionQuery( "from EntityOfBooleans where convertedYesNo = true" ).list(),
session.createSelectionQuery( "from EntityOfBooleans where convertedNumeric = true" ).list(); hasSize( 0 )
);
session.createMutationQuery( "delete EntityOfBooleans where convertedYesNo = true" ).executeUpdate(); assertThat(
session.createMutationQuery( "delete EntityOfBooleans where convertedTrueFalse = true" ).executeUpdate(); session.createSelectionQuery( "from EntityOfBooleans where convertedTrueFalse = true" ).list(),
session.createMutationQuery( "delete EntityOfBooleans where convertedNumeric = true" ).executeUpdate(); hasSize( 0 )
);
session.createMutationQuery( "update EntityOfBooleans set convertedYesNo = true" ).executeUpdate(); assertThat(
session.createMutationQuery( "update EntityOfBooleans set convertedTrueFalse = true" ).executeUpdate(); session.createSelectionQuery( "from EntityOfBooleans where convertedNumeric = true" ).list(),
session.createMutationQuery( "update EntityOfBooleans set convertedNumeric = true" ).executeUpdate(); hasSize( 0 )
);
} ); } );
scope.inTransaction( (session) -> {
assertThat(
session.createSelectionQuery( "from EntityOfBooleans where convertedYesNo = false" ).list(),
hasSize( 1 )
);
assertThat(
session.createSelectionQuery( "from EntityOfBooleans where convertedTrueFalse = false" ).list(),
hasSize( 1 )
);
assertThat(
session.createSelectionQuery( "from EntityOfBooleans where convertedNumeric = false" ).list(),
hasSize( 1 )
);
} );
scope.inTransaction( (session) -> {
assertThat(
session.createSelectionQuery( "from EntityOfBooleans where convertedYesNo != true" ).list(),
hasSize( 1 )
);
assertThat(
session.createSelectionQuery( "from EntityOfBooleans where convertedTrueFalse != true" ).list(),
hasSize( 1 )
);
assertThat(
session.createSelectionQuery( "from EntityOfBooleans where convertedNumeric != true" ).list(),
hasSize( 1 )
);
} );
}
@Test
@Jira( "https://hibernate.atlassian.net/browse/HHH-16182" )
public void testExpressionAsPredicateUsage(SessionFactoryScope scope) {
scope.inTransaction( (session) -> {
assertThat(
session.createSelectionQuery( "from EntityOfBooleans where convertedYesNo" ).list(),
hasSize( 0 )
);
assertThat(
session.createSelectionQuery( "from EntityOfBooleans where convertedTrueFalse" ).list(),
hasSize( 0 )
);
assertThat(
session.createSelectionQuery( "from EntityOfBooleans where convertedNumeric" ).list(),
hasSize( 0 )
);
} );
scope.inTransaction( (session) -> {
assertThat(
session.createSelectionQuery( "from EntityOfBooleans where (convertedYesNo)" ).list(),
hasSize( 0 )
);
assertThat(
session.createSelectionQuery( "from EntityOfBooleans where (convertedTrueFalse)" ).list(),
hasSize( 0 )
);
assertThat(
session.createSelectionQuery( "from EntityOfBooleans where (convertedNumeric)" ).list(),
hasSize( 0 )
);
} );
}
@Test
@Jira( "https://hibernate.atlassian.net/browse/HHH-16182" )
public void testNegatedExpressionAsPredicateUsage(SessionFactoryScope scope) {
scope.inTransaction( (session) -> {
assertThat(
session.createSelectionQuery( "from EntityOfBooleans where not convertedYesNo" ).list(),
hasSize( 1 )
);
assertThat(
session.createSelectionQuery( "from EntityOfBooleans where not convertedTrueFalse" ).list(),
hasSize( 1 )
);
assertThat(
session.createSelectionQuery( "from EntityOfBooleans where not convertedNumeric" ).list(),
hasSize( 1 )
);
} );
scope.inTransaction( (session) -> {
assertThat(
session.createSelectionQuery( "from EntityOfBooleans where not (convertedYesNo)" ).list(),
hasSize( 1 )
);
assertThat(
session.createSelectionQuery( "from EntityOfBooleans where not (convertedTrueFalse)" ).list(),
hasSize( 1 )
);
assertThat(
session.createSelectionQuery( "from EntityOfBooleans where not (convertedNumeric)" ).list(),
hasSize( 1 )
);
} );
}
@Test
@Jira( "https://hibernate.atlassian.net/browse/HHH-16182" )
public void testSetClauseUsage(SessionFactoryScope scope) {
scope.inTransaction( (session) -> {
assertThat(
session.createMutationQuery( "update EntityOfBooleans set convertedYesNo = true" ).executeUpdate(),
equalTo( 1 )
);
assertThat(
session.createMutationQuery( "update EntityOfBooleans set convertedTrueFalse = true" ).executeUpdate(),
equalTo( 1 )
);
assertThat(
session.createMutationQuery( "update EntityOfBooleans set convertedNumeric = true" ).executeUpdate(),
equalTo( 1 )
);
} );
scope.inTransaction( (session) -> {
assertThat(
session.createSelectionQuery( "from EntityOfBooleans where convertedYesNo" ).list(),
hasSize( 1 )
);
assertThat(
session.createSelectionQuery( "from EntityOfBooleans where convertedTrueFalse" ).list(),
hasSize( 1 )
);
assertThat(
session.createSelectionQuery( "from EntityOfBooleans where convertedNumeric" ).list(),
hasSize( 1 )
);
} );
}
@Test
@Jira( "https://hibernate.atlassian.net/browse/HHH-16182" )
public void testCriteriaUsage(SessionFactoryScope scope) {
scope.inTransaction( (session) -> {
assertThat( countByCriteria( "convertedYesNo", true, session ), equalTo( 0 ) );
assertThat( countByCriteria( "convertedTrueFalse", true, session ), equalTo( 0 ) );
assertThat( countByCriteria( "convertedNumeric", true, session ), equalTo( 0 ) );
} );
}
@Test
@Jira( "https://hibernate.atlassian.net/browse/HHH-16182" )
public void testNegatedCriteriaUsage(SessionFactoryScope scope) {
scope.inTransaction( (session) -> {
assertThat( countByCriteria( "convertedYesNo", false, session ), equalTo( 1 ) );
assertThat( countByCriteria( "convertedTrueFalse", false, session ), equalTo( 1 ) );
assertThat( countByCriteria( "convertedNumeric", false, session ), equalTo( 1 ) );
} );
}
private int countByCriteria(String attributeName, boolean matchValue, SessionImplementor session) {
final HibernateCriteriaBuilder builder = session.getCriteriaBuilder();
final JpaCriteriaQuery<Long> criteria = builder.createQuery( Long.class );
criteria.select( builder.count( builder.literal( 1 ) ) );
final JpaRoot<EntityOfBooleans> root = criteria.from( EntityOfBooleans.class );
final JpaPath<Boolean> convertedYesNo = root.get( attributeName );
if ( matchValue ) {
criteria.where( convertedYesNo );
}
else {
criteria.where( builder.not( convertedYesNo ) );
}
final Long result = session.createQuery( criteria ).uniqueResult();
return result.intValue();
} }
@Entity(name = "EntityOfBooleans") @Entity(name = "EntityOfBooleans")

View File

@ -88,16 +88,12 @@ import org.hibernate.metamodel.mapping.internal.OneToManyCollectionPart;
import org.hibernate.metamodel.mapping.internal.SqlTypedMappingImpl; import org.hibernate.metamodel.mapping.internal.SqlTypedMappingImpl;
import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping;
import org.hibernate.metamodel.mapping.ordering.OrderByFragment; import org.hibernate.metamodel.mapping.ordering.OrderByFragment;
import org.hibernate.type.descriptor.converter.internal.OrdinalEnumValueConverter;
import org.hibernate.type.descriptor.converter.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.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.AnyDiscriminatorSqmPath; import org.hibernate.metamodel.model.domain.internal.AnyDiscriminatorSqmPath;
import org.hibernate.metamodel.model.domain.internal.AnyDiscriminatorSqmPathSource; import org.hibernate.metamodel.model.domain.internal.AnyDiscriminatorSqmPathSource;
import org.hibernate.query.derived.AnonymousTupleTableGroupProducer;
import org.hibernate.query.derived.AnonymousTupleType;
import org.hibernate.metamodel.model.domain.internal.BasicSqmPathSource; import org.hibernate.metamodel.model.domain.internal.BasicSqmPathSource;
import org.hibernate.metamodel.model.domain.internal.CompositeSqmPathSource; import org.hibernate.metamodel.model.domain.internal.CompositeSqmPathSource;
import org.hibernate.metamodel.model.domain.internal.DiscriminatorSqmPath; import org.hibernate.metamodel.model.domain.internal.DiscriminatorSqmPath;
@ -115,6 +111,8 @@ import org.hibernate.query.criteria.JpaCteCriteriaAttribute;
import org.hibernate.query.criteria.JpaPath; import org.hibernate.query.criteria.JpaPath;
import org.hibernate.query.criteria.JpaSearchOrder; import org.hibernate.query.criteria.JpaSearchOrder;
import org.hibernate.query.derived.AnonymousTupleEntityValuedModelPart; import org.hibernate.query.derived.AnonymousTupleEntityValuedModelPart;
import org.hibernate.query.derived.AnonymousTupleTableGroupProducer;
import org.hibernate.query.derived.AnonymousTupleType;
import org.hibernate.query.spi.QueryEngine; import org.hibernate.query.spi.QueryEngine;
import org.hibernate.query.spi.QueryOptions; import org.hibernate.query.spi.QueryOptions;
import org.hibernate.query.spi.QueryParameterBinding; import org.hibernate.query.spi.QueryParameterBinding;
@ -392,6 +390,8 @@ import org.hibernate.type.CustomType;
import org.hibernate.type.EnumType; import org.hibernate.type.EnumType;
import org.hibernate.type.JavaObjectType; import org.hibernate.type.JavaObjectType;
import org.hibernate.type.SqlTypes; import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.converter.internal.OrdinalEnumValueConverter;
import org.hibernate.type.descriptor.converter.spi.BasicValueConverter;
import org.hibernate.type.descriptor.java.EnumJavaType; import org.hibernate.type.descriptor.java.EnumJavaType;
import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.java.TemporalJavaType; import org.hibernate.type.descriptor.java.TemporalJavaType;
@ -501,6 +501,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
private boolean negativeAdjustment; private boolean negativeAdjustment;
private final Set<AssociationKey> visitedAssociationKeys = new HashSet<>(); private final Set<AssociationKey> visitedAssociationKeys = new HashSet<>();
private final MappingMetamodel domainModel;
public BaseSqmToSqlAstConverter( public BaseSqmToSqlAstConverter(
SqlAstCreationContext creationContext, SqlAstCreationContext creationContext,
@ -569,8 +570,8 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
this.loadQueryInfluencers = loadQueryInfluencers; this.loadQueryInfluencers = loadQueryInfluencers;
this.domainParameterXref = domainParameterXref; this.domainParameterXref = domainParameterXref;
this.domainParameterBindings = domainParameterBindings; this.domainParameterBindings = domainParameterBindings;
this.jpaCriteriaParamResolutions = domainParameterXref.getParameterResolutions() this.jpaCriteriaParamResolutions = domainParameterXref.getParameterResolutions().getJpaCriteriaParamResolutions();
.getJpaCriteriaParamResolutions(); this.domainModel = creationContext.getSessionFactory().getRuntimeMetamodels().getMappingMetamodel();
} }
private static Boolean stackMatchHelper(SqlAstProcessingState processingState, SqlAstProcessingState c) { private static Boolean stackMatchHelper(SqlAstProcessingState processingState, SqlAstProcessingState c) {
@ -4813,14 +4814,11 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
// A case wrapper for non-basic paths is not possible, // A case wrapper for non-basic paths is not possible,
// because a case expression must return a scalar value, // because a case expression must return a scalar value,
// so we instead add the type restriction predicate as conjunct // so we instead add the type restriction predicate as conjunct
final MappingMetamodel domainModel = creationContext.getSessionFactory() final String treatedName = treatedPath.getTreatTarget().getHibernateEntityName();
.getRuntimeMetamodels() final EntityPersister entityDescriptor = domainModel.findEntityDescriptor( treatedName );
.getMappingMetamodel(); conjunctTreatUsages
final EntityPersister entityDescriptor = domainModel.findEntityDescriptor( .computeIfAbsent( wrappedPath, p -> new HashSet<>( 1 ) )
treatedPath.getTreatTarget().getHibernateEntityName() .addAll( entityDescriptor.getSubclassEntityNames() );
);
conjunctTreatUsages.computeIfAbsent( wrappedPath, p -> new HashSet<>( 1 ) )
.addAll( entityDescriptor.getSubclassEntityNames() );
return expression; return expression;
} }
if ( wrappedPath instanceof DiscriminatorSqmPath ) { if ( wrappedPath instanceof DiscriminatorSqmPath ) {
@ -4869,18 +4867,12 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
} }
private Predicate createTreatTypeRestriction(SqmPath<?> lhs, EntityDomainType<?> treatTarget) { private Predicate createTreatTypeRestriction(SqmPath<?> lhs, EntityDomainType<?> treatTarget) {
final MappingMetamodel domainModel = creationContext.getSessionFactory()
.getRuntimeMetamodels()
.getMappingMetamodel();
final EntityPersister entityDescriptor = domainModel.findEntityDescriptor( treatTarget.getHibernateEntityName() ); final EntityPersister entityDescriptor = domainModel.findEntityDescriptor( treatTarget.getHibernateEntityName() );
final Set<String> subclassEntityNames = entityDescriptor.getSubclassEntityNames(); final Set<String> subclassEntityNames = entityDescriptor.getSubclassEntityNames();
return createTreatTypeRestriction( lhs, subclassEntityNames ); return createTreatTypeRestriction( lhs, subclassEntityNames );
} }
private Predicate createTreatTypeRestriction(SqmPath<?> lhs, Set<String> subclassEntityNames) { private Predicate createTreatTypeRestriction(SqmPath<?> lhs, Set<String> subclassEntityNames) {
final MappingMetamodel domainModel = creationContext.getSessionFactory()
.getRuntimeMetamodels()
.getMappingMetamodel();
// Do what visitSelfInterpretingSqmPath does, except for calling preparingReusablePath // Do what visitSelfInterpretingSqmPath does, except for calling preparingReusablePath
// as that would register a type usage for the table group that we don't want here // as that would register a type usage for the table group that we don't want here
final DiscriminatorSqmPath discriminatorSqmPath = (DiscriminatorSqmPath) lhs.type(); final DiscriminatorSqmPath discriminatorSqmPath = (DiscriminatorSqmPath) lhs.type();
@ -5264,25 +5256,31 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
if ( sqmExpression instanceof SqmParameter ) { if ( sqmExpression instanceof SqmParameter ) {
return determineValueMapping( (SqmParameter<?>) sqmExpression ); return determineValueMapping( (SqmParameter<?>) sqmExpression );
} }
else if ( sqmExpression instanceof SqmPath ) {
if ( sqmExpression instanceof SqmPath ) {
log.debugf( "Determining mapping-model type for SqmPath : %s ", sqmExpression ); log.debugf( "Determining mapping-model type for SqmPath : %s ", sqmExpression );
final MappingMetamodel domainModel = creationContext.getSessionFactory()
.getRuntimeMetamodels()
.getMappingMetamodel();
return SqmMappingModelHelper.resolveMappingModelExpressible( return SqmMappingModelHelper.resolveMappingModelExpressible(
sqmExpression, sqmExpression,
domainModel, domainModel,
fromClauseIndex::findTableGroup fromClauseIndex::findTableGroup
); );
} }
if ( sqmExpression instanceof SqmBooleanExpressionPredicate ) {
final SqmBooleanExpressionPredicate expressionPredicate = (SqmBooleanExpressionPredicate) sqmExpression;
return determineValueMapping( expressionPredicate.getBooleanExpression(), fromClauseIndex );
}
// The model type of an enum literal is always inferred // The model type of an enum literal is always inferred
else if ( sqmExpression instanceof SqmEnumLiteral<?> ) { if ( sqmExpression instanceof SqmEnumLiteral<?> ) {
final MappingModelExpressible<?> mappingModelExpressible = resolveInferredType(); final MappingModelExpressible<?> mappingModelExpressible = resolveInferredType();
if ( mappingModelExpressible != null ) { if ( mappingModelExpressible != null ) {
return mappingModelExpressible; return mappingModelExpressible;
} }
} }
else if ( sqmExpression instanceof SqmSubQuery<?> ) {
if ( sqmExpression instanceof SqmSubQuery<?> ) {
final SqmSubQuery<?> subQuery = (SqmSubQuery<?>) sqmExpression; final SqmSubQuery<?> subQuery = (SqmSubQuery<?>) sqmExpression;
final SqmSelectClause selectClause = subQuery.getQuerySpec().getSelectClause(); final SqmSelectClause selectClause = subQuery.getQuerySpec().getSelectClause();
if ( selectClause.getSelections().size() == 1 ) { if ( selectClause.getSelections().size() == 1 ) {
@ -5293,9 +5291,6 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
} }
final SqmExpressible<?> selectionNodeType = subQuerySelection.getNodeType(); final SqmExpressible<?> selectionNodeType = subQuerySelection.getNodeType();
if ( selectionNodeType != null ) { if ( selectionNodeType != null ) {
final MappingMetamodel domainModel = creationContext.getSessionFactory()
.getRuntimeMetamodels()
.getMappingMetamodel();
final MappingModelExpressible<?> expressible = domainModel.resolveMappingExpressible(selectionNodeType, this::findTableGroupByPath ); final MappingModelExpressible<?> expressible = domainModel.resolveMappingExpressible(selectionNodeType, this::findTableGroupByPath );
if ( expressible != null ) { if ( expressible != null ) {
@ -5321,6 +5316,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
// We can't determine the type of the expression // We can't determine the type of the expression
return null; return null;
} }
if ( nodeType instanceof EmbeddedSqmPathSource<?> ) { if ( nodeType instanceof EmbeddedSqmPathSource<?> ) {
if ( sqmExpression instanceof SqmBinaryArithmetic<?> ) { if ( sqmExpression instanceof SqmBinaryArithmetic<?> ) {
final SqmBinaryArithmetic<?> binaryArithmetic = (SqmBinaryArithmetic<?>) sqmExpression; final SqmBinaryArithmetic<?> binaryArithmetic = (SqmBinaryArithmetic<?>) sqmExpression;
@ -5332,9 +5328,8 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
} }
} }
} }
final MappingMetamodel domainModel = creationContext.getSessionFactory()
.getRuntimeMetamodels()
.getMappingMetamodel();
final MappingModelExpressible<?> valueMapping = domainModel.resolveMappingExpressible( final MappingModelExpressible<?> valueMapping = domainModel.resolveMappingExpressible(
nodeType, nodeType,
fromClauseIndex::getTableGroup fromClauseIndex::getTableGroup
@ -5348,7 +5343,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
} }
if ( valueMapping == null ) { if ( valueMapping == null ) {
// For literals it is totally possible that we can't figure out a mapping type // For literals, it is totally possible that we can't figure out a mapping type
if ( sqmExpression instanceof SqmLiteral<?> ) { if ( sqmExpression instanceof SqmLiteral<?> ) {
return null; return null;
} }
@ -6785,10 +6780,10 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
finally { finally {
inferrableTypeAccessStack.pop(); inferrableTypeAccessStack.pop();
} }
ComparisonOperator sqmOperator = predicate.getSqmOperator();
if ( predicate.isNegated() ) { final ComparisonOperator sqmOperator = predicate.isNegated()
sqmOperator = sqmOperator.negated(); ? predicate.getSqmOperator().negated()
} : predicate.getSqmOperator();
return new ComparisonPredicate( lhs, sqmOperator, rhs, getBooleanType() ); return new ComparisonPredicate( lhs, sqmOperator, rhs, getBooleanType() );
} }
@ -7099,20 +7094,21 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
@Override @Override
public Object visitBooleanExpressionPredicate(SqmBooleanExpressionPredicate predicate) { public Object visitBooleanExpressionPredicate(SqmBooleanExpressionPredicate predicate) {
final Expression booleanExpression = (Expression) predicate.getBooleanExpression().accept( this ); final Expression booleanExpression = (Expression) predicate.getBooleanExpression().accept( this );
if ( booleanExpression instanceof SelfRenderingExpression ) { final JdbcMapping jdbcMapping = booleanExpression.getExpressionType().getJdbcMapping( 0 );
final Predicate sqlPredicate = new SelfRenderingPredicate( (SelfRenderingExpression) booleanExpression ); if ( jdbcMapping.getValueConverter() != null ) {
if ( predicate.isNegated() ) { // handle converted booleans (yes-no, etc)
return new NegatedPredicate( sqlPredicate ); return new ComparisonPredicate(
}
return sqlPredicate;
}
else {
return new BooleanExpressionPredicate(
booleanExpression, booleanExpression,
predicate.isNegated(), ComparisonOperator.EQUAL,
getBooleanType() new JdbcLiteral<>( jdbcMapping.convertToRelationalValue( !predicate.isNegated() ), jdbcMapping )
); );
} }
return new BooleanExpressionPredicate(
booleanExpression,
predicate.isNegated(),
getBooleanType()
);
} }
@Override @Override

View File

@ -7,6 +7,7 @@
package org.hibernate.query.sqm.tree.predicate; package org.hibernate.query.sqm.tree.predicate;
import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.NodeBuilder;
import org.hibernate.query.sqm.SqmExpressible;
/** /**
* @author Steve Ebersole * @author Steve Ebersole
@ -19,7 +20,11 @@ public abstract class AbstractNegatableSqmPredicate extends AbstractSqmPredicate
} }
public AbstractNegatableSqmPredicate(boolean negated, NodeBuilder nodeBuilder) { public AbstractNegatableSqmPredicate(boolean negated, NodeBuilder nodeBuilder) {
super( nodeBuilder.getBooleanType(), nodeBuilder ); this( nodeBuilder.getBooleanType(), negated, nodeBuilder );
}
public AbstractNegatableSqmPredicate(SqmExpressible<Boolean> type, boolean negated, NodeBuilder nodeBuilder) {
super( type, nodeBuilder );
this.negated = negated; this.negated = negated;
} }

View File

@ -18,6 +18,7 @@ import jakarta.persistence.criteria.Expression;
/** /**
* Represents an expression whose type is boolean, and can therefore be used as a predicate. * Represents an expression whose type is boolean, and can therefore be used as a predicate.
* E.g. {@code `from Employee e where e.isActive`}
* *
* @author Steve Ebersole * @author Steve Ebersole
*/ */
@ -34,7 +35,7 @@ public class SqmBooleanExpressionPredicate extends AbstractNegatableSqmPredicate
SqmExpression<Boolean> booleanExpression, SqmExpression<Boolean> booleanExpression,
boolean negated, boolean negated,
NodeBuilder nodeBuilder) { NodeBuilder nodeBuilder) {
super( negated, nodeBuilder ); super( booleanExpression.getExpressible(), negated, nodeBuilder );
assert booleanExpression.getNodeType() != null; assert booleanExpression.getNodeType() != null;
final Class<?> expressionJavaType = booleanExpression.getNodeType().getExpressibleJavaType().getJavaTypeClass(); final Class<?> expressionJavaType = booleanExpression.getNodeType().getExpressibleJavaType().getJavaTypeClass();
@ -86,4 +87,14 @@ public class SqmBooleanExpressionPredicate extends AbstractNegatableSqmPredicate
protected SqmNegatablePredicate createNegatedNode() { protected SqmNegatablePredicate createNegatedNode() {
return new SqmBooleanExpressionPredicate( booleanExpression, !isNegated(), nodeBuilder() ); return new SqmBooleanExpressionPredicate( booleanExpression, !isNegated(), nodeBuilder() );
} }
@Override
public String toString() {
if ( isNegated() ) {
return "SqmBooleanExpressionPredicate( (not) " + booleanExpression + " )";
}
else {
return "SqmBooleanExpressionPredicate( " + booleanExpression + " )";
}
}
} }