HHH-16875 improve typechecking for comparisons of embeddables, tuples, entities

This commit is contained in:
Gavin King 2023-06-29 11:54:45 +02:00
parent 63cccf87dc
commit bc901f5162
16 changed files with 484 additions and 234 deletions

View File

@ -39,7 +39,6 @@ import org.hibernate.metamodel.mapping.EntityVersionMapping;
import org.hibernate.metamodel.model.domain.AbstractIdentifiableType;
import org.hibernate.metamodel.model.domain.DomainType;
import org.hibernate.metamodel.model.domain.EmbeddableDomainType;
import org.hibernate.metamodel.model.domain.EntityDomainType;
import org.hibernate.metamodel.model.domain.IdentifiableDomainType;
import org.hibernate.metamodel.model.domain.ManagedDomainType;
import org.hibernate.metamodel.model.domain.PersistentAttribute;

View File

@ -28,7 +28,6 @@ import org.hibernate.mapping.Component;
import org.hibernate.mapping.MappedSuperclass;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Property;
import org.hibernate.mapping.Value;
import org.hibernate.metamodel.MappingMetamodel;
import org.hibernate.metamodel.model.domain.AbstractIdentifiableType;
import org.hibernate.metamodel.model.domain.BasicDomainType;
@ -51,6 +50,7 @@ import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.java.spi.EntityJavaType;
import org.hibernate.type.descriptor.java.spi.JavaTypeRegistry;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.spi.TypeConfiguration;
import jakarta.persistence.metamodel.Attribute;
@ -806,12 +806,14 @@ public class MetadataContext {
return (BasicDomainType<J>) basicDomainTypeMap.computeIfAbsent(
javaType,
jt -> {
// we cannot use getTypeConfiguration().standardBasicTypeForJavaType(javaType)
// because that doesn't return the right thing for primitive types
final JavaTypeRegistry registry = getTypeConfiguration().getJavaTypeRegistry();
if ( javaType.isPrimitive() ) {
return new PrimitiveBasicTypeImpl<>( registry.resolveDescriptor( javaType ), javaType );
}
return new BasicTypeImpl<>( registry.resolveDescriptor( javaType ) );
JavaType<J> javaTypeDescriptor = registry.resolveDescriptor( javaType );
JdbcType jdbcType = javaTypeDescriptor.getRecommendedJdbcType( typeConfiguration.getCurrentBaseSqlTypeIndicators() );
return javaType.isPrimitive()
? new PrimitiveBasicTypeImpl<>( javaTypeDescriptor, jdbcType , javaType )
: new BasicTypeImpl<>( javaTypeDescriptor, jdbcType );
}
);
}

View File

@ -11,18 +11,23 @@ import java.sql.CallableStatement;
import java.sql.SQLException;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.model.domain.BasicDomainType;
import org.hibernate.type.descriptor.ValueBinder;
import org.hibernate.type.descriptor.ValueExtractor;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.jdbc.JdbcType;
/**
* @author Emmanuel Bernard
*/
public class BasicTypeImpl<J> implements BasicDomainType<J>, Serializable {
public class BasicTypeImpl<J> implements BasicDomainType<J>, JdbcMapping, Serializable {
private final JavaType<J> javaType;
private final JdbcType jdbcType;
public BasicTypeImpl(JavaType<J> javaType) {
public BasicTypeImpl(JavaType<J> javaType, JdbcType jdbcType) {
this.javaType = javaType;
this.jdbcType = jdbcType;
}
public PersistenceType getPersistenceType() {
@ -41,12 +46,12 @@ public class BasicTypeImpl<J> implements BasicDomainType<J>, Serializable {
@Override
public boolean canDoExtraction() {
throw new UnsupportedOperationException();
return true;
}
@Override
public JdbcType getJdbcType() {
throw new UnsupportedOperationException();
return jdbcType;
}
@Override
@ -54,7 +59,7 @@ public class BasicTypeImpl<J> implements BasicDomainType<J>, Serializable {
CallableStatement statement,
int paramIndex,
SharedSessionContractImplementor session) throws SQLException {
throw new UnsupportedOperationException();
return jdbcType.getExtractor( javaType ).extract( statement, paramIndex, session );
}
@Override
@ -62,6 +67,21 @@ public class BasicTypeImpl<J> implements BasicDomainType<J>, Serializable {
CallableStatement statement,
String paramName,
SharedSessionContractImplementor session) throws SQLException {
return jdbcType.getExtractor( javaType ).extract( statement, paramName, session );
}
@Override
public JavaType getJavaTypeDescriptor() {
throw new UnsupportedOperationException();
}
@Override
public ValueExtractor<?> getJdbcValueExtractor() {
return jdbcType.getExtractor( javaType );
}
@Override
public ValueBinder getJdbcValueBinder() {
return jdbcType.getBinder( javaType );
}
}

View File

@ -34,6 +34,14 @@ public class DiscriminatorSqmPathSource<D> extends AbstractSqmPathSource<D>
this.entityMapping = entityMapping;
}
public EntityDomainType<?> getEntityDomainType() {
return entityDomainType;
}
public EntityMappingType getEntityMapping() {
return entityMapping;
}
@Override
public SqmPath<D> createSqmPath(SqmPath<?> lhs, SqmPathSource<?> intermediatePathSource) {
final NavigablePath navigablePath;

View File

@ -7,18 +7,19 @@
package org.hibernate.metamodel.model.domain.internal;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.jdbc.JdbcType;
public class PrimitiveBasicTypeImpl<J> extends BasicTypeImpl<J> {
private final Class<J> javaTypeClass;
private final Class<J> primitiveClass;
public PrimitiveBasicTypeImpl(JavaType<J> javaType, Class<J> javaTypeClass) {
super( javaType );
assert javaTypeClass.isPrimitive();
this.javaTypeClass = javaTypeClass;
public PrimitiveBasicTypeImpl(JavaType<J> javaType, JdbcType jdbcType, Class<J> primitiveClass) {
super( javaType, jdbcType );
assert primitiveClass.isPrimitive();
this.primitiveClass = primitiveClass;
}
@Override
public Class<J> getJavaType() {
return javaTypeClass;
return primitiveClass;
}
}

View File

@ -248,6 +248,7 @@ import static org.hibernate.query.sqm.TemporalUnit.TIMEZONE_HOUR;
import static org.hibernate.query.sqm.TemporalUnit.TIMEZONE_MINUTE;
import static org.hibernate.query.sqm.TemporalUnit.WEEK_OF_MONTH;
import static org.hibernate.query.sqm.TemporalUnit.WEEK_OF_YEAR;
import static org.hibernate.query.sqm.internal.TypecheckUtil.assertComparable;
import static org.hibernate.type.descriptor.DateTimeUtils.DATE_TIME;
import static org.hibernate.type.spi.TypeConfiguration.isJdbcTemporalType;
@ -2478,7 +2479,7 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
right = r;
}
}
SqmCriteriaNodeBuilder.assertComparable( left, right );
assertComparable( left, right, creationContext.getNodeBuilder().getSessionFactory() );
return new SqmComparisonPredicate(
left,
comparisonOperator,
@ -2676,8 +2677,8 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
}
else if ( inListContext instanceof HqlParser.SubqueryInListContext ) {
final HqlParser.SubqueryInListContext subQueryOrParamInListContext = (HqlParser.SubqueryInListContext) inListContext;
final SqmSubQuery<?> subquery = visitSubquery(subQueryOrParamInListContext.subquery());
SqmCriteriaNodeBuilder.assertComparable( testExpression, subquery );
final SqmSubQuery<?> subquery = visitSubquery( subQueryOrParamInListContext.subquery() );
assertComparable( testExpression, subquery, creationContext.getNodeBuilder().getSessionFactory() );
return new SqmInSubQueryPredicate(
testExpression,
subquery,

View File

@ -22,7 +22,6 @@ import org.hibernate.metamodel.model.domain.JpaMetamodel;
import org.hibernate.query.criteria.HibernateCriteriaBuilder;
import org.hibernate.query.criteria.JpaCoalesce;
import org.hibernate.query.criteria.JpaCompoundSelection;
import org.hibernate.query.criteria.JpaCriteriaQuery;
import org.hibernate.query.criteria.JpaExpression;
import org.hibernate.query.criteria.JpaParameterExpression;
import org.hibernate.query.criteria.JpaSearchedCase;

View File

@ -43,9 +43,7 @@ import org.hibernate.id.enhanced.Optimizer;
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.internal.SingleAttributeIdentifierMapping;
import org.hibernate.metamodel.model.domain.DomainType;
import org.hibernate.metamodel.model.domain.EntityDomainType;
import org.hibernate.persister.entity.AbstractEntityPersister;
import org.hibernate.persister.entity.EntityPersister;
@ -81,7 +79,6 @@ import org.hibernate.query.spi.ScrollableResultsImplementor;
import org.hibernate.query.spi.SelectQueryPlan;
import org.hibernate.query.sqm.NodeBuilder;
import org.hibernate.query.sqm.SortOrder;
import org.hibernate.query.sqm.SqmExpressible;
import org.hibernate.query.sqm.SqmPathSource;
import org.hibernate.query.sqm.internal.SqmInterpretationsKey.InterpretationsKeySource;
import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy;
@ -116,8 +113,6 @@ import jakarta.persistence.LockModeType;
import jakarta.persistence.Parameter;
import jakarta.persistence.PersistenceException;
import jakarta.persistence.TemporalType;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import static org.hibernate.jpa.HibernateHints.HINT_CACHEABLE;
import static org.hibernate.jpa.HibernateHints.HINT_CACHE_MODE;
@ -135,7 +130,7 @@ import static org.hibernate.query.sqm.internal.SqmInterpretationsKey.createInter
import static org.hibernate.query.sqm.internal.SqmInterpretationsKey.generateNonSelectKey;
import static org.hibernate.query.sqm.internal.SqmUtil.isSelect;
import static org.hibernate.query.sqm.internal.SqmUtil.verifyIsNonSelectStatement;
import static org.hibernate.type.descriptor.java.JavaTypeHelper.isUnknown;
import static org.hibernate.query.sqm.internal.TypecheckUtil.assertAssignable;
/**
* {@link Query} implementation based on an SQM
@ -357,48 +352,10 @@ public class QuerySqmImpl<R>
final SqmAssignment<?> assignment = assignments.get( i );
final SqmPath<?> targetPath = assignment.getTargetPath();
final SqmExpression<?> expression = assignment.getValue();
if ( !isAssignable( targetPath, expression ) ) {
throw new SemanticException(
String.format(
"Cannot assign expression of type '%s' to target path '%s' of type '%s'",
expression.getNodeJavaType().getJavaType().getTypeName(),
targetPath.toHqlString(),
targetPath.getNodeJavaType().getJavaType().getTypeName()
),
hqlString,
null
);
}
}
}
/**
* @see SqmCriteriaNodeBuilder#areTypesComparable(SqmExpressible, SqmExpressible)
*/
public static boolean isAssignable(SqmPath<?> targetPath, SqmExpression<?> expression) {
DomainType<?> lhsDomainType = targetPath.getExpressible().getSqmType();
DomainType<?> rhsDomainType = expression.getExpressible().getSqmType();
if ( lhsDomainType instanceof JdbcMapping && rhsDomainType instanceof JdbcMapping ) {
JdbcType lhsJdbcType = ((JdbcMapping) lhsDomainType).getJdbcType();
JdbcType rhsJdbcType = ((JdbcMapping) rhsDomainType).getJdbcType();
if ( lhsJdbcType.getJdbcTypeCode() == rhsJdbcType.getJdbcTypeCode()
|| lhsJdbcType.isStringLike() && rhsJdbcType.isStringLike()
|| lhsJdbcType.isInteger() && rhsJdbcType.isInteger() ) {
return true;
assertAssignable( hqlString, targetPath, expression, getSessionFactory() );
}
}
JavaType<?> targetType = targetPath.getNodeJavaType();
JavaType<?> assignedType = expression.getNodeJavaType();
return targetType == assignedType
// If we don't know the java types, let's just be lenient
|| isUnknown( targetType)
|| isUnknown( assignedType )
// Assume we can coerce one to another
|| targetType.isWider( assignedType )
// Enum assignment, other strange user type mappings
|| targetType.getJavaTypeClass().isAssignableFrom( assignedType.getJavaTypeClass() );
}
private void verifyInsertTypesMatch(String hqlString, SqmInsertStatement<R> sqmStatement) {
final List<SqmPath<?>> insertionTargetPaths = sqmStatement.getInsertionTargetPaths();
@ -439,21 +396,23 @@ public class QuerySqmImpl<R>
}
for ( int i = 0; i < expressionsSize; i++ ) {
final SqmTypedNode<?> expression = expressions.get( i );
if ( expression.getNodeJavaType() == null ) {
continue;
}
if ( insertionTargetPaths.get( i ).getJavaTypeDescriptor() != expression.getNodeJavaType() ) {
throw new SemanticException(
String.format(
"Expected insert attribute type [%s] did not match Query selection type [%s] at selection index [%d]",
insertionTargetPaths.get( i ).getJavaTypeDescriptor().getJavaType().getTypeName(),
expression.getNodeJavaType().getJavaType().getTypeName(),
i
),
hqlString,
null
);
}
final SqmPath<?> targetPath = insertionTargetPaths.get(i);
assertAssignable( hqlString, targetPath, expression, getSessionFactory() );
// if ( expression.getNodeJavaType() == null ) {
// continue;
// }
// if ( insertionTargetPaths.get( i ).getJavaTypeDescriptor() != expression.getNodeJavaType() ) {
// throw new SemanticException(
// String.format(
// "Expected insert attribute type [%s] did not match Query selection type [%s] at selection index [%d]",
// insertionTargetPaths.get( i ).getJavaTypeDescriptor().getJavaType().getTypeName(),
// expression.getNodeJavaType().getJavaType().getTypeName(),
// i
// ),
// hqlString,
// null
// );
// }
}
}

View File

@ -40,14 +40,10 @@ import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.SessionFactoryRegistry;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.model.domain.BasicDomainType;
import org.hibernate.metamodel.model.domain.DomainType;
import org.hibernate.metamodel.model.domain.JpaMetamodel;
import org.hibernate.metamodel.model.domain.TupleType;
import org.hibernate.metamodel.model.domain.internal.BasicTypeImpl;
import org.hibernate.metamodel.model.domain.internal.DiscriminatorSqmPathSource;
import org.hibernate.metamodel.model.domain.internal.EmbeddedSqmPathSource;
import org.hibernate.metamodel.model.domain.spi.JpaMetamodelImplementor;
import org.hibernate.metamodel.spi.MappingMetamodelImplementor;
import org.hibernate.query.BindableType;
@ -174,12 +170,11 @@ import jakarta.persistence.criteria.Selection;
import jakarta.persistence.criteria.SetJoin;
import jakarta.persistence.criteria.Subquery;
import jakarta.persistence.metamodel.Bindable;
import jakarta.persistence.metamodel.EntityType;
import static java.util.Arrays.asList;
import static org.hibernate.query.internal.QueryHelper.highestPrecedenceType;
import static org.hibernate.query.sqm.TrimSpec.fromCriteriaTrimSpec;
import static org.hibernate.type.descriptor.java.JavaTypeHelper.isUnknown;
import static org.hibernate.query.sqm.internal.TypecheckUtil.assertComparable;
/**
* Acts as a JPA {@link jakarta.persistence.criteria.CriteriaBuilder} by
@ -242,81 +237,7 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext,
return sessionFactory.get();
}
/**
* @see QuerySqmImpl#isAssignable(SqmPath, SqmExpression)
*/
public static boolean areTypesComparable(SqmExpressible<?> lhsType, SqmExpressible<?> rhsType) {
if ( lhsType == null || rhsType == null
|| lhsType == rhsType
|| isDiscriminatorComparison( lhsType, rhsType )
// Allow comparing an embeddable against a tuple literal
|| lhsType instanceof EmbeddedSqmPathSource<?> && rhsType instanceof TupleType
|| rhsType instanceof EmbeddedSqmPathSource<?> && lhsType instanceof TupleType
// Since we don't know any better, we just allow any comparison with multivalued parameters
|| lhsType instanceof MultiValueParameterType<?>
|| rhsType instanceof MultiValueParameterType<?>) {
return true;
}
DomainType<?> lhsDomainType = lhsType.getSqmType();
DomainType<?> rhsDomainType = rhsType.getSqmType();
if ( lhsDomainType instanceof JdbcMapping && rhsDomainType instanceof JdbcMapping ) {
JdbcType lhsJdbcType = ((JdbcMapping) lhsDomainType).getJdbcType();
JdbcType rhsJdbcType = ((JdbcMapping) rhsDomainType).getJdbcType();
if ( lhsJdbcType.getJdbcTypeCode() == rhsJdbcType.getJdbcTypeCode()
|| lhsJdbcType.isStringLike() && rhsJdbcType.isStringLike()
|| lhsJdbcType.isTemporal() && rhsJdbcType.isTemporal()
|| lhsJdbcType.isNumber() && rhsJdbcType.isNumber() ) {
return true;
}
}
final JavaType<?> lhsJavaType = lhsType.getExpressibleJavaType();
final JavaType<?> rhsJavaType = rhsType.getExpressibleJavaType();
return lhsJavaType == rhsJavaType
// If we don't know the java types, let's just be lenient
|| isUnknown( lhsJavaType )
|| isUnknown( rhsJavaType )
// Allow comparing two temporal expressions regardless of their concrete java types
|| lhsJavaType.isTemporalType() && rhsJavaType.isTemporalType()
// Assume we can coerce one to another
|| lhsJavaType.isWider( rhsJavaType )
|| rhsJavaType.isWider( lhsJavaType )
// Enum comparison, other strange user type mappings,
// Polymorphic entity comparison
|| lhsJavaType.getJavaTypeClass().isAssignableFrom( rhsJavaType.getJavaTypeClass() )
|| rhsJavaType.getJavaTypeClass().isAssignableFrom( lhsJavaType.getJavaTypeClass() );
}
private static boolean isDiscriminatorComparison(SqmExpressible<?> lhsType, SqmExpressible<?> rhsType) {
final SqmExpressible<?> nonDiscriminator;
if ( lhsType instanceof DiscriminatorSqmPathSource<?> ) {
nonDiscriminator = rhsType;
}
else if ( rhsType instanceof DiscriminatorSqmPathSource<?> ) {
nonDiscriminator = lhsType;
}
else {
return false;
}
// Comparing the discriminator against an entity type is fine
if ( nonDiscriminator instanceof EntityType<?> ) {
return true;
}
final JavaType<?> nonDiscriminatorJavaType = nonDiscriminator.getExpressibleJavaType();
// Comparing the discriminator against the discriminator value is fine
switch ( nonDiscriminatorJavaType.getJavaTypeClass().getTypeName() ) {
case "java.lang.String":
case "char":
case "java.lang.Character":
case "int":
case "java.lang.Integer":
return true;
default:
return false;
}
}
@Override
public BasicType<Boolean> getBooleanType() {
@ -1246,10 +1167,13 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext,
return null;
}
else {
Class type = value.getClass();
BasicType<T> result = typeConfiguration.getBasicTypeForJavaType( type );
final Class type = value.getClass();
final BasicType<T> result = typeConfiguration.getBasicTypeForJavaType( type );
if ( result == null && value instanceof Enum ) {
return new BasicTypeImpl<>( new EnumJavaType<>( type ) );
final EnumJavaType javaType = new EnumJavaType<>( type );
final JdbcType jdbcType =
javaType.getRecommendedJdbcType( typeConfiguration.getCurrentBaseSqlTypeIndicators() );
return new BasicTypeImpl<>( javaType, jdbcType );
}
else {
return result;
@ -1306,12 +1230,12 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext,
@Override
public <T> SqmExpression<T> nullLiteral(Class<T> resultClass) {
final TypeConfiguration typeConfiguration = getTypeConfiguration();
final BasicType<T> basicTypeForJavaType = typeConfiguration.getBasicTypeForJavaType( resultClass );
final BasicType<T> basicTypeForJavaType = getTypeConfiguration().getBasicTypeForJavaType( resultClass );
// if there's no basic type, it might be an entity type
final SqmExpressible<T> sqmExpressible = basicTypeForJavaType == null
? getDomainModel().managedType( resultClass )
: basicTypeForJavaType;
return new SqmLiteralNull<>(sqmExpressible, this );
return new SqmLiteralNull<>( sqmExpressible, this );
}
class MultiValueParameterType<T> implements SqmExpressible<T> {
@ -1674,7 +1598,7 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext,
@Override
public <T> SqmFunction<T> function(String name, Class<T> type, Expression<?>[] args) {
SqmFunctionDescriptor functionTemplate = getFunctionDescriptor( name );
final BasicType<T> resultType = getTypeConfiguration().getBasicTypeForJavaType( type );
final BasicType<T> resultType = getTypeConfiguration().standardBasicTypeForJavaType( type );
if ( functionTemplate == null ) {
functionTemplate = new NamedSqmFunctionDescriptor(
name,
@ -2043,8 +1967,8 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext,
@Override
public <Y extends Comparable<? super Y>> SqmPredicate between(Expression<? extends Y> value, Expression<? extends Y> lower, Expression<? extends Y> upper) {
assertComparable( value, lower );
assertComparable( value, upper );
assertComparable( value, lower, getNodeBuilder().getSessionFactory() );
assertComparable( value, upper, getNodeBuilder().getSessionFactory() );
return new SqmBetweenPredicate(
(SqmExpression<? extends Y>) value,
(SqmExpression<? extends Y>) lower,
@ -2059,8 +1983,8 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext,
final SqmExpression<? extends Y> valueExpression = (SqmExpression<? extends Y>) value;
final SqmExpression<?> lowerExpr = value( lower, valueExpression );
final SqmExpression<?> upperExpr = value( upper, valueExpression );
assertComparable( valueExpression, lowerExpr );
assertComparable( valueExpression, upperExpr );
assertComparable( valueExpression, lowerExpr, getNodeBuilder().getSessionFactory() );
assertComparable( valueExpression, upperExpr, getNodeBuilder().getSessionFactory() );
return new SqmBetweenPredicate(
valueExpression,
lowerExpr,
@ -2070,26 +1994,6 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext,
);
}
public static void assertComparable(Expression<?> x, Expression<?> y) {
SqmExpression<?> left = (SqmExpression<?>) x;
SqmExpression<?> right = (SqmExpression<?>) y;
if ( left.getTupleLength() != null && right.getTupleLength() != null
&& left.getTupleLength().intValue() != right.getTupleLength().intValue() ) {
throw new SemanticException( "Cannot compare tuples of different lengths" );
}
final SqmExpressible<?> leftType = left.getNodeType();
final SqmExpressible<?> rightType = right.getNodeType();
if ( !areTypesComparable( leftType, rightType ) ) {
throw new SemanticException(
String.format(
"Cannot compare left expression of type '%s' with right expression of type '%s'",
leftType.getTypeName(),
rightType.getTypeName()
)
);
}
}
public static void assertNumeric(SqmExpression<?> expression, BinaryArithmeticOperator op) {
final SqmExpressible<?> nodeType = expression.getNodeType();
if ( nodeType != null ) {
@ -2130,7 +2034,7 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext,
@Override
public SqmPredicate equal(Expression<?> x, Expression<?> y) {
assertComparable( x, y );
assertComparable( x, y, getNodeBuilder().getSessionFactory() );
return new SqmComparisonPredicate(
(SqmExpression<?>) x,
ComparisonOperator.EQUAL,
@ -2142,7 +2046,7 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext,
@Override
public SqmPredicate equal(Expression<?> x, Object y) {
final SqmExpression<?> yExpr = value( y, (SqmExpression<?>) x );
assertComparable( x, yExpr );
assertComparable( x, yExpr, getNodeBuilder().getSessionFactory() );
return new SqmComparisonPredicate(
(SqmExpression<?>) x,
ComparisonOperator.EQUAL,
@ -2153,7 +2057,7 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext,
@Override
public SqmPredicate notEqual(Expression<?> x, Expression<?> y) {
assertComparable( x, y );
assertComparable( x, y, getNodeBuilder().getSessionFactory() );
return new SqmComparisonPredicate(
(SqmExpression<?>) x,
ComparisonOperator.NOT_EQUAL,
@ -2165,7 +2069,7 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext,
@Override
public SqmPredicate notEqual(Expression<?> x, Object y) {
final SqmExpression<?> yExpr = value( y, (SqmExpression<?>) x );
assertComparable( x, yExpr );
assertComparable( x, yExpr, getNodeBuilder().getSessionFactory() );
return new SqmComparisonPredicate(
(SqmExpression<?>) x,
ComparisonOperator.NOT_EQUAL,
@ -2176,7 +2080,7 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext,
@Override
public SqmPredicate distinctFrom(Expression<?> x, Expression<?> y) {
assertComparable( x, y );
assertComparable( x, y, getNodeBuilder().getSessionFactory() );
return new SqmComparisonPredicate(
(SqmExpression<?>) x,
ComparisonOperator.DISTINCT_FROM,
@ -2188,7 +2092,7 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext,
@Override
public SqmPredicate distinctFrom(Expression<?> x, Object y) {
final SqmExpression<?> yExpr = value( y, (SqmExpression<?>) x );
assertComparable( x, yExpr );
assertComparable( x, yExpr, getNodeBuilder().getSessionFactory() );
return new SqmComparisonPredicate(
(SqmExpression<?>) x,
ComparisonOperator.DISTINCT_FROM,
@ -2199,7 +2103,7 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext,
@Override
public SqmPredicate notDistinctFrom(Expression<?> x, Expression<?> y) {
assertComparable( x, y );
assertComparable( x, y, getNodeBuilder().getSessionFactory() );
return new SqmComparisonPredicate(
(SqmExpression<?>) x,
ComparisonOperator.NOT_DISTINCT_FROM,
@ -2211,7 +2115,7 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext,
@Override
public SqmPredicate notDistinctFrom(Expression<?> x, Object y) {
final SqmExpression<?> yExpr = value( y, (SqmExpression<?>) x );
assertComparable( x, yExpr );
assertComparable( x, yExpr, getNodeBuilder().getSessionFactory() );
return new SqmComparisonPredicate(
(SqmExpression<?>) x,
ComparisonOperator.NOT_DISTINCT_FROM,
@ -2222,7 +2126,7 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext,
@Override
public <Y extends Comparable<? super Y>> SqmPredicate greaterThan(Expression<? extends Y> x, Expression<? extends Y> y) {
assertComparable( x, y );
assertComparable( x, y, getNodeBuilder().getSessionFactory() );
return new SqmComparisonPredicate(
(SqmExpression<?>) x,
ComparisonOperator.GREATER_THAN,
@ -2234,7 +2138,7 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext,
@Override
public <Y extends Comparable<? super Y>> SqmPredicate greaterThan(Expression<? extends Y> x, Y y) {
final SqmExpression<?> yExpr = value( y, (SqmExpression<?>) x );
assertComparable( x, yExpr );
assertComparable( x, yExpr, getNodeBuilder().getSessionFactory() );
return new SqmComparisonPredicate(
(SqmExpression<?>) x,
ComparisonOperator.GREATER_THAN,
@ -2245,7 +2149,7 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext,
@Override
public <Y extends Comparable<? super Y>> SqmPredicate greaterThanOrEqualTo(Expression<? extends Y> x, Expression<? extends Y> y) {
assertComparable( x, y );
assertComparable( x, y, getNodeBuilder().getSessionFactory() );
return new SqmComparisonPredicate(
(SqmExpression<?>) x,
ComparisonOperator.GREATER_THAN_OR_EQUAL,
@ -2257,7 +2161,7 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext,
@Override
public <Y extends Comparable<? super Y>> SqmPredicate greaterThanOrEqualTo(Expression<? extends Y> x, Y y) {
final SqmExpression<?> yExpr = value( y, (SqmExpression<?>) x );
assertComparable( x, yExpr );
assertComparable( x, yExpr, getNodeBuilder().getSessionFactory() );
return new SqmComparisonPredicate(
(SqmExpression<?>) x,
ComparisonOperator.GREATER_THAN_OR_EQUAL,
@ -2268,7 +2172,7 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext,
@Override
public <Y extends Comparable<? super Y>> SqmPredicate lessThan(Expression<? extends Y> x, Expression<? extends Y> y) {
assertComparable( x, y );
assertComparable( x, y, getNodeBuilder().getSessionFactory() );
return new SqmComparisonPredicate(
(SqmExpression<?>) x,
ComparisonOperator.LESS_THAN,
@ -2280,7 +2184,7 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext,
@Override
public <Y extends Comparable<? super Y>> SqmPredicate lessThan(Expression<? extends Y> x, Y y) {
final SqmExpression<?> yExpr = value( y, (SqmExpression<?>) x );
assertComparable( x, yExpr );
assertComparable( x, yExpr, getNodeBuilder().getSessionFactory() );
return new SqmComparisonPredicate(
(SqmExpression<?>) x,
ComparisonOperator.LESS_THAN,
@ -2302,7 +2206,7 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext,
@Override
public <Y extends Comparable<? super Y>> SqmPredicate lessThanOrEqualTo(Expression<? extends Y> x, Y y) {
final SqmExpression<?> yExpr = value( y, (SqmExpression<?>) x );
assertComparable( x, yExpr );
assertComparable( x, yExpr, getNodeBuilder().getSessionFactory() );
return new SqmComparisonPredicate(
(SqmExpression<?>) x,
ComparisonOperator.LESS_THAN_OR_EQUAL,
@ -2313,7 +2217,7 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext,
@Override
public SqmPredicate gt(Expression<? extends Number> x, Expression<? extends Number> y) {
assertComparable( x, y );
assertComparable( x, y, getNodeBuilder().getSessionFactory() );
return new SqmComparisonPredicate(
(SqmExpression<?>) x,
ComparisonOperator.GREATER_THAN,
@ -2325,7 +2229,7 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext,
@Override
public SqmPredicate gt(Expression<? extends Number> x, Number y) {
final SqmExpression<?> yExpr = value( y, (SqmExpression<?>) x );
assertComparable( x, yExpr );
assertComparable( x, yExpr, getNodeBuilder().getSessionFactory() );
return new SqmComparisonPredicate(
(SqmExpression<?>) x,
ComparisonOperator.GREATER_THAN,
@ -2336,7 +2240,7 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext,
@Override
public SqmPredicate ge(Expression<? extends Number> x, Expression<? extends Number> y) {
assertComparable( x, y );
assertComparable( x, y, getNodeBuilder().getSessionFactory() );
return new SqmComparisonPredicate(
(SqmExpression<?>) x,
ComparisonOperator.GREATER_THAN_OR_EQUAL,
@ -2348,7 +2252,7 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext,
@Override
public SqmPredicate ge(Expression<? extends Number> x, Number y) {
final SqmExpression<?> yExpr = value( y, (SqmExpression<?>) x );
assertComparable( x, yExpr );
assertComparable( x, yExpr, getNodeBuilder().getSessionFactory() );
return new SqmComparisonPredicate(
(SqmExpression<?>) x,
ComparisonOperator.GREATER_THAN_OR_EQUAL,
@ -2359,7 +2263,7 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext,
@Override
public SqmPredicate lt(Expression<? extends Number> x, Expression<? extends Number> y) {
assertComparable( x, y );
assertComparable( x, y, getNodeBuilder().getSessionFactory() );
return new SqmComparisonPredicate(
(SqmExpression<?>) x,
ComparisonOperator.LESS_THAN,
@ -2371,7 +2275,7 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext,
@Override
public SqmPredicate lt(Expression<? extends Number> x, Number y) {
final SqmExpression<?> yExpr = value( y, (SqmExpression<?>) x );
assertComparable( x, yExpr );
assertComparable( x, yExpr, getNodeBuilder().getSessionFactory() );
return new SqmComparisonPredicate(
(SqmExpression<?>) x,
ComparisonOperator.LESS_THAN,
@ -2382,7 +2286,7 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext,
@Override
public SqmPredicate le(Expression<? extends Number> x, Expression<? extends Number> y) {
assertComparable( x, y );
assertComparable( x, y, getNodeBuilder().getSessionFactory() );
return new SqmComparisonPredicate(
(SqmExpression<?>) x,
ComparisonOperator.LESS_THAN_OR_EQUAL,
@ -2394,7 +2298,7 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext,
@Override
public SqmPredicate le(Expression<? extends Number> x, Number y) {
final SqmExpression<?> yExpr = value( y, (SqmExpression<?>) x );
assertComparable( x, yExpr );
assertComparable( x, yExpr, getNodeBuilder().getSessionFactory() );
return new SqmComparisonPredicate(
(SqmExpression<?>) x,
ComparisonOperator.LESS_THAN_OR_EQUAL,
@ -2726,7 +2630,7 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext,
sqmArguments.add( 0, literal( pattern ) );
return getFunctionDescriptor( "sql" ).generateSqmExpression(
sqmArguments,
getTypeConfiguration().getBasicTypeForJavaType( type ),
getTypeConfiguration().standardBasicTypeForJavaType( type ),
queryEngine
);
}

View File

@ -0,0 +1,360 @@
/*
* 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.query.sqm.internal;
import jakarta.persistence.criteria.Expression;
import jakarta.persistence.metamodel.EntityType;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.model.domain.DomainType;
import org.hibernate.metamodel.model.domain.TupleType;
import org.hibernate.metamodel.model.domain.internal.DiscriminatorSqmPathSource;
import org.hibernate.metamodel.model.domain.internal.EmbeddedSqmPathSource;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.query.SemanticException;
import org.hibernate.query.sqm.SqmExpressible;
import org.hibernate.query.sqm.SqmPathSource;
import org.hibernate.query.sqm.tree.SqmTypedNode;
import org.hibernate.query.sqm.tree.domain.SqmPath;
import org.hibernate.query.sqm.tree.expression.SqmExpression;
import org.hibernate.query.sqm.tree.expression.SqmLiteralNull;
import org.hibernate.type.BasicType;
import org.hibernate.type.descriptor.jdbc.JdbcType;
/**
* Functions for typechecking comparison expressions and assignments in the SQM tree.
* A comparison expression is any predicate like {@code x = y} or {@code x > y}. An
* assignment is an element of the {@code set} clause in an {@code update} query, or
* an element of the {@code values} list in an {@code insert query}.
* <p>
* The rules here are not the same as the rules for the Java language, nor are they
* identical to the rules for SQL. For example:
* <ul>
* <li>In Java, a comparison expression like {@code '1'.equals(1)} is well-typed, and
* evaluates to {@code false}. In most SQL dialects, this expression evaluates to
* {@code true}, via implicit type conversions. Here we <em>reject</em> such
* comparison expressions.
* <li>In Java, if two classes are related by inheritance, then one is assignable to
* the other. But here, this assignment is only legal if the two classes are entity
* types belonging to the same mapped entity inheritance hierarchy.
* <li>On the other hand, in Java, {@code java.sql.Date} and {@code java.time.LocalDate}
* may not be compared nor assigned. But here they're considered inter-comparable
* and inter-assignable.
* </ul>
* <p>
* Two basic types are considered comparable if they map to the same "family" of
* {@linkplain org.hibernate.type.SqlTypes JDBC type}s. Here we allow some latitude
* so that different numeric types are comparable, and different string types are
* comparable. However, we do not allow comparisons between types which involve more
* questionable/unportable implicit type conversions (between integers and strings,
* for example). This means that we accept comparisons between basic types which map
* completely unrelated types in Java.
* <p>
* Entity types have identity equality. That is, two entities are considered equal if
* their primary keys are equal.
* <p>
* Embeddable and tuple types have value equality. That is, they're considered equal
* if their members are equal. For convenience, an embeddable object may be compared
* directly to a tuple constructor.
* <p>
* Comparison of discriminators (that is, of literal entity types and {@code type()}
* function application) is legal only when the entity types belong to the same mapped
* entity hierarchy.
*
* @see #assertComparable(Expression, Expression, SessionFactoryImplementor)
* @see #assertAssignable(String, SqmPath, SqmTypedNode, SessionFactoryImplementor)
*
* @author Gavin King
*/
public class TypecheckUtil {
/**
* @implNote The code below will reject some things that are accepted in H5,
* and perhaps even a few things which were accepted in H6.1/6.2. Therefore,
* users will report "bugs". The correct resolution of such bugs is not to
* go naively inserting special cases and secret escape hatches in this code!
* Instead, it's important to practice saying "no" and rejecting the majority
* of such "bug" reports. In cases where a fix really is required, it should
* be done within the framework laid out below, not by adding ad-hoc special
* rules and holes which undermine the type system. It's much more important
* that HQL has simple, predictable, and understandable rules than it is that
* every single user be able to do every single little wierd thing that used
* to work in Hibernate 5.
*
* @param lhsType the type of the expression on the LHS of the comparison operator
* @param rhsType the type of the expression on the RHS of the comparison operator
*
* @see #isTypeAssignable(SqmPathSource, SqmExpressible, SessionFactoryImplementor)
*/
private static boolean areTypesComparable(
SqmExpressible<?> lhsType, SqmExpressible<?> rhsType,
SessionFactoryImplementor factory) {
if ( lhsType == null || rhsType == null || lhsType == rhsType ) {
return true;
}
// since we can't so anything meaningful here, just allow
// any comparison with multivalued parameters
if ( lhsType instanceof SqmCriteriaNodeBuilder.MultiValueParameterType<?>
|| rhsType instanceof SqmCriteriaNodeBuilder.MultiValueParameterType<?>) {
// TODO: do something meaningful here
return true;
}
// for embeddables, the embeddable class must match exactly
if ( lhsType instanceof EmbeddedSqmPathSource && rhsType instanceof EmbeddedSqmPathSource ) {
return areEmbeddableTypesComparable( (EmbeddedSqmPathSource<?>) lhsType, (EmbeddedSqmPathSource<?>) rhsType );
}
// for tuple constructors, we must check each element
if ( lhsType instanceof TupleType && rhsType instanceof TupleType ) {
return areTupleTypesComparable( factory, (TupleType<?>) lhsType, (TupleType<?>) rhsType );
}
// allow comparing an embeddable against a tuple literal
if ( lhsType instanceof EmbeddedSqmPathSource<?> && rhsType instanceof TupleType
|| rhsType instanceof EmbeddedSqmPathSource<?> && lhsType instanceof TupleType ) {
// TODO: do something meaningful here
return true;
}
// entities can be compared if they belong to the same inheritance hierarchy
if ( lhsType instanceof EntityType && rhsType instanceof EntityType ) {
return areEntityTypesComparable( (EntityType<?>) lhsType, (EntityType<?>) rhsType, factory );
}
// entities can be compared to discriminators if they belong to
// the same inheritance hierarchy
if ( lhsType instanceof DiscriminatorSqmPathSource) {
return isDiscriminatorTypeComparable( (DiscriminatorSqmPathSource<?>) lhsType, rhsType, factory );
}
if ( rhsType instanceof DiscriminatorSqmPathSource ) {
return isDiscriminatorTypeComparable( (DiscriminatorSqmPathSource<?>) rhsType, lhsType, factory );
}
// Treat the expressions as comparable if they belong to the same
// "family" of JDBC type. One could object to this approach since
// JDBC types can vary between databases, however it's the only
// decent approach which allows comparison between literals and
// enums, user-defined types, etc.
DomainType<?> lhsDomainType = lhsType.getSqmType();
DomainType<?> rhsDomainType = rhsType.getSqmType();
if ( lhsDomainType instanceof JdbcMapping && rhsDomainType instanceof JdbcMapping ) {
JdbcType lhsJdbcType = ((JdbcMapping) lhsDomainType).getJdbcType();
JdbcType rhsJdbcType = ((JdbcMapping) rhsDomainType).getJdbcType();
if ( lhsJdbcType.getJdbcTypeCode() == rhsJdbcType.getJdbcTypeCode()
// "families" of implicitly-convertible JDBC types
// (this list might need to be extended in future)
|| lhsJdbcType.isStringLike() && rhsJdbcType.isStringLike()
|| lhsJdbcType.isTemporal() && rhsJdbcType.isTemporal()
|| lhsJdbcType.isNumber() && rhsJdbcType.isNumber() ) {
return true;
}
}
// Workaround: these are needed for a handful of slightly "weird" cases
// involving Java field literals and converters, where we don't have
// access to the correct JDBC type above. However, this is exactly the
// sort of hole warned about above, and accepts many things which are
// not well-typed.
// TODO: sort all this out, and remove this branch
if ( isSameJavaType( lhsType, rhsType ) ) {
return true;
}
return false;
}
private static boolean areEmbeddableTypesComparable(EmbeddedSqmPathSource<?> lhsType, EmbeddedSqmPathSource<?> rhsType) {
// no polymorphism for embeddable types
return rhsType.getNodeJavaType() == lhsType.getNodeJavaType();
}
private static boolean areTupleTypesComparable(SessionFactoryImplementor factory, TupleType<?> lhsTuple, TupleType<?> rhsTuple) {
if ( rhsTuple.componentCount() != lhsTuple.componentCount() ) {
return false;
}
else {
for ( int i = 0; i < lhsTuple.componentCount(); i++ ) {
if ( !areTypesComparable( lhsTuple.get(i), rhsTuple.get(i), factory ) ) {
return false;
}
}
}
return true;
}
private static boolean areEntityTypesComparable(
EntityType<?> lhsType, EntityType<?> rhsType,
SessionFactoryImplementor factory) {
EntityPersister lhsEntity = getEntityDescriptor( factory, lhsType.getName() );
EntityPersister rhsEntity = getEntityDescriptor( factory, rhsType.getName() );
return lhsEntity.getRootEntityName().equals( rhsEntity.getRootEntityName() );
}
private static boolean isDiscriminatorTypeComparable(
DiscriminatorSqmPathSource<?> lhsDiscriminator, SqmExpressible<?> rhsType,
SessionFactoryImplementor factory) {
String entityName = lhsDiscriminator.getEntityDomainType().getHibernateEntityName();
EntityPersister lhsEntity = factory.getMappingMetamodel().getEntityDescriptor( entityName );
if ( rhsType instanceof EntityType ) {
String rhsEntityName = ((EntityType<?>) rhsType).getName();
EntityPersister rhsEntity = getEntityDescriptor( factory, rhsEntityName );
return lhsEntity.getRootEntityName().equals( rhsEntity.getRootEntityName() );
}
else if ( rhsType instanceof DiscriminatorSqmPathSource ) {
DiscriminatorSqmPathSource<?> discriminator = (DiscriminatorSqmPathSource<?>) rhsType;
String rhsEntityName = discriminator.getEntityDomainType().getHibernateEntityName();
EntityPersister rhsEntity = factory.getMappingMetamodel().getEntityDescriptor( rhsEntityName );
return rhsEntity.getRootEntityName().equals( lhsEntity.getRootEntityName() );
}
else {
BasicType<?> discriminatorType = (BasicType<?>)
lhsDiscriminator.getEntityMapping().getDiscriminatorMapping().getMappedType();
return areTypesComparable( discriminatorType, rhsType, factory );
}
}
/**
* @param targetType the type of the path expression to which a value is assigned
* @param expressionType the type of the value expression being assigned to the path
*
* @see #areTypesComparable(SqmExpressible, SqmExpressible, SessionFactoryImplementor)
*/
private static boolean isTypeAssignable(
SqmPathSource<?> targetType, SqmExpressible<?> expressionType,
SessionFactoryImplementor factory) {
if ( targetType == null || expressionType == null || targetType == expressionType ) {
return true;
}
// entities can be assigned if they belong to the same inheritance hierarchy
if ( targetType instanceof EntityType && expressionType instanceof EntityType ) {
return isEntityTypeAssignable( (EntityType<?>) targetType, (EntityType<?>) expressionType, factory );
}
// Treat the expression as assignable to the target path if they belong
// to the same "family" of JDBC type. One could object to this approach
// since JDBC types can vary between databases, however it's the only
// decent approach which allows comparison between literals and enums,
// user-defined types, etc.
DomainType<?> lhsDomainType = targetType.getSqmType();
DomainType<?> rhsDomainType = expressionType.getSqmType();
if ( lhsDomainType instanceof JdbcMapping && rhsDomainType instanceof JdbcMapping ) {
JdbcType lhsJdbcType = ((JdbcMapping) lhsDomainType).getJdbcType();
JdbcType rhsJdbcType = ((JdbcMapping) rhsDomainType).getJdbcType();
if ( lhsJdbcType.getJdbcTypeCode() == rhsJdbcType.getJdbcTypeCode()
// "families" of implicitly-convertible JDBC types
// (this list might need to be extended in future)
|| lhsJdbcType.isStringLike() && rhsJdbcType.isStringLike()
|| lhsJdbcType.isInteger() && rhsJdbcType.isInteger()
|| lhsJdbcType.isFloat() && rhsJdbcType.isFloat()
|| lhsJdbcType.isFloat() && rhsJdbcType.isInteger() ) {
return true;
}
}
// Workaround: these are needed for a handful of slightly "weird" cases
// involving Java field literals and converters, where we don't have
// access to the correct JDBC type above. However, this is exactly the
// sort of hole warned about above, and accepts many things which are
// not well-typed.
// TODO: sort all this out, and remove this branch
if ( isSameJavaType( targetType, expressionType ) ) {
return true;
}
return false;
}
private static boolean isSameJavaType(SqmExpressible<?> leftType, SqmExpressible<?> rightType) {
return leftType.getRelationalJavaType() == rightType.getRelationalJavaType()
|| leftType.getExpressibleJavaType() == rightType.getExpressibleJavaType()
|| leftType.getBindableJavaType() == rightType.getBindableJavaType();
}
private static boolean isEntityTypeAssignable(
EntityType<?> lhsType, EntityType<?> rhsType,
SessionFactoryImplementor factory) {
EntityPersister lhsEntity = getEntityDescriptor( factory, lhsType.getName() );
EntityPersister rhsEntity = getEntityDescriptor( factory, rhsType.getName() );
return lhsEntity.isSubclassEntityName( rhsEntity.getEntityName() );
}
private static EntityPersister getEntityDescriptor(SessionFactoryImplementor factory, String name) {
return factory.getMappingMetamodel()
.getEntityDescriptor( factory.getJpaMetamodel().qualifyImportableName( name ) );
}
/**
* @see TypecheckUtil#assertAssignable(String, SqmPath, SqmTypedNode, SessionFactoryImplementor)
*/
public static void assertComparable(Expression<?> x, Expression<?> y, SessionFactoryImplementor factory) {
SqmExpression<?> left = (SqmExpression<?>) x;
SqmExpression<?> right = (SqmExpression<?>) y;
if ( left.getTupleLength() != null && right.getTupleLength() != null
&& left.getTupleLength().intValue() != right.getTupleLength().intValue() ) {
throw new SemanticException( "Cannot compare tuples of different lengths" );
}
// allow comparing literal null to things
if ( !(left instanceof SqmLiteralNull) && !(right instanceof SqmLiteralNull) ) {
final SqmExpressible<?> leftType = left.getNodeType();
final SqmExpressible<?> rightType = right.getNodeType();
if ( !areTypesComparable( leftType, rightType, factory ) ) {
throw new SemanticException(
String.format(
"Cannot compare left expression of type '%s' with right expression of type '%s'",
leftType.getTypeName(),
rightType.getTypeName()
)
);
}
}
}
/**
* @see TypecheckUtil#assertComparable(Expression, Expression, SessionFactoryImplementor)
*/
public static void assertAssignable(
String hqlString,
SqmPath<?> targetPath, SqmTypedNode<?> expression,
SessionFactoryImplementor factory) {
// allow assigning literal null to things
if ( expression instanceof SqmLiteralNull ) {
// TODO: check that the target path is nullable
}
else {
SqmPathSource<?> targetType = targetPath.getNodeType();
SqmExpressible<?> expressionType = expression.getNodeType();
if ( !isTypeAssignable( targetType, expressionType, factory ) ) {
throw new SemanticException(
String.format(
"Cannot assign expression of type '%s' to target path '%s' of type '%s'",
expressionType.getTypeName(),
targetPath.toHqlString(),
targetType.getTypeName()
),
hqlString,
null
);
}
}
}
}

View File

@ -15,12 +15,13 @@ import org.hibernate.query.criteria.JpaExpression;
import org.hibernate.query.internal.QueryHelper;
import org.hibernate.query.sqm.NodeBuilder;
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.expression.SqmExpression;
import jakarta.persistence.criteria.Expression;
import static org.hibernate.query.sqm.internal.TypecheckUtil.assertComparable;
/**
* @author Steve Ebersole
*/
@ -135,7 +136,7 @@ public class SqmInListPredicate<T> extends AbstractNegatableSqmPredicate impleme
}
private void implyListElementType(SqmExpression<?> expression) {
nodeBuilder().assertComparable( getTestExpression(), expression );
assertComparable( getTestExpression(), expression, nodeBuilder().getSessionFactory() );
expression.applyInferableType(
QueryHelper.highestPrecedenceType2( getTestExpression().getExpressible(), expression.getExpressible() )
);

View File

@ -35,10 +35,8 @@ import org.hibernate.type.descriptor.java.BigIntegerJavaType;
import org.hibernate.type.descriptor.java.BlobJavaType;
import org.hibernate.type.descriptor.java.BooleanJavaType;
import org.hibernate.type.descriptor.java.BooleanPrimitiveArrayJavaType;
import org.hibernate.type.descriptor.java.ByteArrayJavaType;
import org.hibernate.type.descriptor.java.ByteJavaType;
import org.hibernate.type.descriptor.java.CalendarJavaType;
import org.hibernate.type.descriptor.java.CharacterArrayJavaType;
import org.hibernate.type.descriptor.java.CharacterJavaType;
import org.hibernate.type.descriptor.java.ClassJavaType;
import org.hibernate.type.descriptor.java.ClobJavaType;

View File

@ -693,7 +693,7 @@ public class TypeConfiguration implements SessionFactoryObserver, Serializable {
return (BasicType<J>) existing;
}
final BasicType<J> registeredType = getBasicTypeRegistry().getRegisteredType( javaType );
final BasicType<J> registeredType = basicTypeRegistry.getRegisteredType( javaType );
if ( registeredType != null ) {
basicTypeByJavaType.put( javaType, registeredType );
return registeredType;

View File

@ -404,8 +404,8 @@ public class BulkManipulationTest extends BaseCoreFunctionalTestCase {
int lt = m.indexOf( "java.lang.Long" );
assertTrue( "type causing error not reported", st > -1 );
assertTrue( "type causing error not reported", lt > -1 );
assertTrue( lt > st );
assertTrue( "wrong position of type error reported", m.indexOf( "index [2]" ) > -1 );
assertTrue( lt < st );
// assertTrue( "wrong position of type error reported", m.indexOf( "index [2]" ) > -1 );
}
finally {
s.close();

View File

@ -1,7 +1,9 @@
package org.hibernate.orm.test.jpa.criteria;
import org.hibernate.query.SemanticException;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.orm.junit.EntityManagerFactoryScope;
import org.hibernate.testing.orm.junit.FailureExpected;
import org.hibernate.testing.orm.junit.Jpa;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
@ -37,7 +39,7 @@ public class ObjectParameterTypeForEmbeddableTest {
);
}
@Test
@Test @FailureExpected(reason = "This query is in my opinion not well-typed, and should be rejected")
public void testSettingParameterOfTypeObject(EntityManagerFactoryScope scope) {
scope.inTransaction(
entityManager -> {
@ -75,8 +77,8 @@ public class ObjectParameterTypeForEmbeddableTest {
@Test
public void testSettingParameterOfTypeWrongType(EntityManagerFactoryScope scope) {
IllegalArgumentException thrown = assertThrows(
IllegalArgumentException.class, () ->
SemanticException thrown = assertThrows(
SemanticException.class, () ->
scope.inTransaction(
entityManager -> {
final CriteriaBuilder cb = entityManager.getCriteriaBuilder();
@ -92,8 +94,7 @@ public class ObjectParameterTypeForEmbeddableTest {
)
);
assertThat( thrown.getMessage() ).startsWith(
"Argument [org.hibernate.orm.test.jpa.criteria.ObjectParameterTypeForEmbeddableTest" );
assertThat( thrown.getMessage() ).startsWith( "Cannot compare left expression" );
}
@Entity(name = "TestEntity")

View File

@ -20,6 +20,7 @@ import jakarta.persistence.Table;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.internal.util.ExceptionHelper;
import org.hibernate.query.Query;
import org.hibernate.query.SemanticException;
import org.hibernate.sql.ast.SqlTreeCreationException;
import org.hibernate.testing.orm.junit.DomainModel;
@ -90,11 +91,7 @@ public class QueryLiteralTest {
fail( "Should throw Exception!" );
}
catch (Exception e) {
final Throwable rootCause = ExceptionHelper.getRootCause( e );
assertThat( rootCause, instanceOf( SqlTreeCreationException.class ) );
assertThat( rootCause.getMessage(), startsWith( "QueryLiteral type [" ) );
assertThat( rootCause.getMessage(), containsString( "] did not match domain Java-type [" ) );
assertThat( rootCause.getMessage(), containsString( "] nor JDBC Java-type [" ) );
assertThat( ExceptionHelper.getRootCause( e ), instanceOf( SemanticException.class ) );
}
}