HHH-16858 improve typechecking for comparisons/assignments (#6910)
* HHH-16858 improve typechecking for comparisons/assignments In particular, correctly typecheck comparisons between enums and other enums, and literal integers / strings. Actually I'm not a great fan of comparing enums with int/string literals but since we used to support it in 5, and kinda mostly support it in earlier releases of 6, on balance we might as well continue to allow it. * improve typechecking for arguments to min() & max() - use the known JdbcType which previously we didn't have proper access to - and accidentally fix HHH-16859 by side-effect (I didn't really want to fix that one, but it was easier to fix it than to unfix it.) * HHH-16858 handle MySQL enum types correctly in comparison typecheck
This commit is contained in:
parent
9464aecc78
commit
1e4b9e8ffb
|
@ -14,8 +14,8 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
|||
import org.hibernate.type.descriptor.jdbc.JdbcType;
|
||||
|
||||
/**
|
||||
* Specialization of DomainType for types that can be used as a
|
||||
* parameter output for a {@link org.hibernate.procedure.ProcedureCall}
|
||||
* Specialization of {@link org.hibernate.metamodel.model.domain.DomainType} for types that
|
||||
* can be used as a parameter output for a {@link org.hibernate.procedure.ProcedureCall}.
|
||||
*
|
||||
* @apiNote We assume a type that maps to exactly one SQL value, hence {@link #getJdbcType()}
|
||||
*
|
||||
|
|
|
@ -434,22 +434,12 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
|
|||
|
||||
@Override
|
||||
public SqmRoot<R> visitTargetEntity(HqlParser.TargetEntityContext dmlTargetContext) {
|
||||
final HqlParser.EntityNameContext entityNameContext = (HqlParser.EntityNameContext) dmlTargetContext.getChild( 0 );
|
||||
final String identificationVariable;
|
||||
if ( dmlTargetContext.getChildCount() == 1 ) {
|
||||
identificationVariable = null;
|
||||
}
|
||||
else {
|
||||
identificationVariable = applyJpaCompliance(
|
||||
visitVariable(
|
||||
(HqlParser.VariableContext) dmlTargetContext.getChild( 1 )
|
||||
)
|
||||
);
|
||||
}
|
||||
final HqlParser.EntityNameContext entityNameContext = dmlTargetContext.entityName();
|
||||
final HqlParser.VariableContext variable = dmlTargetContext.variable();
|
||||
//noinspection unchecked
|
||||
return new SqmRoot<>(
|
||||
(EntityDomainType<R>) visitEntityName( entityNameContext ),
|
||||
identificationVariable,
|
||||
variable == null ? null : applyJpaCompliance( visitVariable( variable ) ),
|
||||
false,
|
||||
creationContext.getNodeBuilder()
|
||||
);
|
||||
|
@ -457,17 +447,8 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
|
|||
|
||||
@Override
|
||||
public SqmInsertStatement<R> visitInsertStatement(HqlParser.InsertStatementContext ctx) {
|
||||
final int dmlTargetIndex;
|
||||
if ( ctx.getChild( 1 ) instanceof HqlParser.TargetEntityContext ) {
|
||||
dmlTargetIndex = 1;
|
||||
}
|
||||
else {
|
||||
dmlTargetIndex = 2;
|
||||
}
|
||||
final HqlParser.TargetEntityContext dmlTargetContext = (HqlParser.TargetEntityContext) ctx.getChild( dmlTargetIndex );
|
||||
final HqlParser.TargetFieldsContext targetFieldsSpecContext = (HqlParser.TargetFieldsContext) ctx.getChild(
|
||||
dmlTargetIndex + 1
|
||||
);
|
||||
final HqlParser.TargetEntityContext dmlTargetContext = ctx.targetEntity();
|
||||
final HqlParser.TargetFieldsContext targetFieldsSpecContext = ctx.targetFields();
|
||||
final SqmRoot<R> root = visitTargetEntity( dmlTargetContext );
|
||||
if ( root.getModel() instanceof SqmPolymorphicRootDescriptor<?> ) {
|
||||
throw new SemanticException(
|
||||
|
@ -480,7 +461,8 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
|
|||
|
||||
final HqlParser.QueryExpressionContext queryExpressionContext = ctx.queryExpression();
|
||||
if ( queryExpressionContext != null ) {
|
||||
final SqmInsertSelectStatement<R> insertStatement = new SqmInsertSelectStatement<>( root, creationContext.getNodeBuilder() );
|
||||
final SqmInsertSelectStatement<R> insertStatement =
|
||||
new SqmInsertSelectStatement<>( root, creationContext.getNodeBuilder() );
|
||||
parameterCollector = insertStatement;
|
||||
final SqmDmlCreationProcessingState processingState = new SqmDmlCreationProcessingState(
|
||||
insertStatement,
|
||||
|
@ -517,7 +499,8 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
|
|||
|
||||
}
|
||||
else {
|
||||
final SqmInsertValuesStatement<R> insertStatement = new SqmInsertValuesStatement<>( root, creationContext.getNodeBuilder() );
|
||||
final SqmInsertValuesStatement<R> insertStatement =
|
||||
new SqmInsertValuesStatement<>( root, creationContext.getNodeBuilder() );
|
||||
parameterCollector = insertStatement;
|
||||
final SqmDmlCreationProcessingState processingState = new SqmDmlCreationProcessingState(
|
||||
insertStatement,
|
||||
|
@ -553,9 +536,8 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
|
|||
|
||||
@Override
|
||||
public SqmUpdateStatement<R> visitUpdateStatement(HqlParser.UpdateStatementContext ctx) {
|
||||
final boolean versioned = !( ctx.getChild( 1 ) instanceof HqlParser.TargetEntityContext );
|
||||
final int dmlTargetIndex = versioned ? 2 : 1;
|
||||
final HqlParser.TargetEntityContext dmlTargetContext = (HqlParser.TargetEntityContext) ctx.getChild( dmlTargetIndex );
|
||||
final boolean versioned = ctx.VERSIONED() != null;
|
||||
final HqlParser.TargetEntityContext dmlTargetContext = ctx.targetEntity();
|
||||
final SqmRoot<R> root = visitTargetEntity( dmlTargetContext );
|
||||
if ( root.getModel() instanceof SqmPolymorphicRootDescriptor<?> ) {
|
||||
throw new SemanticException(
|
||||
|
@ -577,12 +559,12 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
|
|||
|
||||
try {
|
||||
updateStatement.versioned( versioned );
|
||||
final HqlParser.SetClauseContext setClauseCtx = (HqlParser.SetClauseContext) ctx.getChild( dmlTargetIndex + 1 );
|
||||
final HqlParser.SetClauseContext setClauseCtx = ctx.setClause();
|
||||
for ( ParseTree subCtx : setClauseCtx.children ) {
|
||||
if ( subCtx instanceof HqlParser.AssignmentContext ) {
|
||||
final HqlParser.AssignmentContext assignmentContext = (HqlParser.AssignmentContext) subCtx;
|
||||
//noinspection unchecked
|
||||
final SqmPath<Object> targetPath = (SqmPath<Object>) consumeDomainPath( (HqlParser.SimplePathContext) assignmentContext.getChild( 0 ) );
|
||||
final SqmPath<Object> targetPath = (SqmPath<Object>) consumeDomainPath( assignmentContext.simplePath() );
|
||||
final Class<?> targetPathJavaType = targetPath.getJavaType();
|
||||
final boolean isEnum = targetPathJavaType != null && targetPathJavaType.isEnum();
|
||||
final ParseTree rightSide = assignmentContext.getChild( 2 );
|
||||
|
@ -604,10 +586,9 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
|
|||
}
|
||||
}
|
||||
|
||||
if ( dmlTargetIndex + 2 <= ctx.getChildCount() ) {
|
||||
updateStatement.applyPredicate(
|
||||
visitWhereClause( (HqlParser.WhereClauseContext) ctx.getChild( dmlTargetIndex + 2 ) )
|
||||
);
|
||||
final HqlParser.WhereClauseContext whereClauseContext = ctx.whereClause();
|
||||
if ( whereClauseContext != null ) {
|
||||
updateStatement.applyPredicate( visitWhereClause( whereClauseContext ) );
|
||||
}
|
||||
|
||||
return updateStatement;
|
||||
|
@ -619,14 +600,7 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
|
|||
|
||||
@Override
|
||||
public SqmDeleteStatement<R> visitDeleteStatement(HqlParser.DeleteStatementContext ctx) {
|
||||
final int dmlTargetIndex;
|
||||
if ( ctx.getChild( 1 ) instanceof HqlParser.TargetEntityContext ) {
|
||||
dmlTargetIndex = 1;
|
||||
}
|
||||
else {
|
||||
dmlTargetIndex = 2;
|
||||
}
|
||||
final HqlParser.TargetEntityContext dmlTargetContext = (HqlParser.TargetEntityContext) ctx.getChild( dmlTargetIndex );
|
||||
final HqlParser.TargetEntityContext dmlTargetContext = ctx.targetEntity();
|
||||
final SqmRoot<R> root = visitTargetEntity( dmlTargetContext );
|
||||
|
||||
final SqmDeleteStatement<R> deleteStatement = new SqmDeleteStatement<>( root, SqmQuerySource.HQL, creationContext.getNodeBuilder() );
|
||||
|
@ -642,10 +616,9 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
|
|||
|
||||
processingStateStack.push( sqmDeleteCreationState );
|
||||
try {
|
||||
if ( dmlTargetIndex + 1 <= ctx.getChildCount() ) {
|
||||
deleteStatement.applyPredicate(
|
||||
visitWhereClause( (HqlParser.WhereClauseContext) ctx.getChild( dmlTargetIndex + 1 ) )
|
||||
);
|
||||
final HqlParser.WhereClauseContext whereClauseContext = ctx.whereClause();
|
||||
if ( whereClauseContext != null ) {
|
||||
deleteStatement.applyPredicate( visitWhereClause( whereClauseContext ) );
|
||||
}
|
||||
|
||||
return deleteStatement;
|
||||
|
@ -2472,50 +2445,37 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
|
|||
HqlParser.ExpressionContext rightExpressionContext) {
|
||||
final SqmExpression<?> right;
|
||||
final SqmExpression<?> left;
|
||||
switch ( comparisonOperator ) {
|
||||
case EQUAL:
|
||||
case NOT_EQUAL:
|
||||
case DISTINCT_FROM:
|
||||
case NOT_DISTINCT_FROM: {
|
||||
Map<Class<?>, Enum<?>> possibleEnumValues;
|
||||
if ( ( possibleEnumValues = getPossibleEnumValues( leftExpressionContext ) ) != null ) {
|
||||
right = (SqmExpression<?>) rightExpressionContext.accept( this );
|
||||
left = resolveEnumShorthandLiteral(
|
||||
leftExpressionContext,
|
||||
possibleEnumValues,
|
||||
right.getJavaType()
|
||||
);
|
||||
break;
|
||||
}
|
||||
else if ( ( possibleEnumValues = getPossibleEnumValues( rightExpressionContext ) ) != null ) {
|
||||
left = (SqmExpression<?>) leftExpressionContext.accept( this );
|
||||
right = resolveEnumShorthandLiteral(
|
||||
rightExpressionContext,
|
||||
possibleEnumValues,
|
||||
left.getJavaType()
|
||||
);
|
||||
break;
|
||||
}
|
||||
final SqmExpression<?> l = (SqmExpression<?>) leftExpressionContext.accept( this );
|
||||
final SqmExpression<?> r = (SqmExpression<?>) rightExpressionContext.accept( this );
|
||||
if ( l instanceof AnyDiscriminatorSqmPath && r instanceof SqmLiteralEntityType ) {
|
||||
left = l;
|
||||
right = createDiscriminatorValue( (AnyDiscriminatorSqmPath<?>) left, rightExpressionContext );
|
||||
}
|
||||
else if ( r instanceof AnyDiscriminatorSqmPath && l instanceof SqmLiteralEntityType ) {
|
||||
left = createDiscriminatorValue( (AnyDiscriminatorSqmPath<?>) r, leftExpressionContext );
|
||||
right = r;
|
||||
}
|
||||
else {
|
||||
left = l;
|
||||
right = r;
|
||||
}
|
||||
break;
|
||||
Map<Class<?>, Enum<?>> possibleEnumValues;
|
||||
if ( ( possibleEnumValues = getPossibleEnumValues( leftExpressionContext ) ) != null ) {
|
||||
right = (SqmExpression<?>) rightExpressionContext.accept( this );
|
||||
left = resolveEnumShorthandLiteral(
|
||||
leftExpressionContext,
|
||||
possibleEnumValues,
|
||||
right.getJavaType()
|
||||
);
|
||||
}
|
||||
else if ( ( possibleEnumValues = getPossibleEnumValues( rightExpressionContext ) ) != null ) {
|
||||
left = (SqmExpression<?>) leftExpressionContext.accept( this );
|
||||
right = resolveEnumShorthandLiteral(
|
||||
rightExpressionContext,
|
||||
possibleEnumValues,
|
||||
left.getJavaType()
|
||||
);
|
||||
}
|
||||
else {
|
||||
final SqmExpression<?> l = (SqmExpression<?>) leftExpressionContext.accept( this );
|
||||
final SqmExpression<?> r = (SqmExpression<?>) rightExpressionContext.accept( this );
|
||||
if ( l instanceof AnyDiscriminatorSqmPath && r instanceof SqmLiteralEntityType ) {
|
||||
left = l;
|
||||
right = createDiscriminatorValue( (AnyDiscriminatorSqmPath<?>) left, rightExpressionContext );
|
||||
}
|
||||
default: {
|
||||
left = (SqmExpression<?>) leftExpressionContext.accept( this );
|
||||
right = (SqmExpression<?>) rightExpressionContext.accept( this );
|
||||
break;
|
||||
else if ( r instanceof AnyDiscriminatorSqmPath && l instanceof SqmLiteralEntityType ) {
|
||||
left = createDiscriminatorValue( (AnyDiscriminatorSqmPath<?>) r, leftExpressionContext );
|
||||
right = r;
|
||||
}
|
||||
else {
|
||||
left = l;
|
||||
right = r;
|
||||
}
|
||||
}
|
||||
SqmCriteriaNodeBuilder.assertComparable( left, right );
|
||||
|
|
|
@ -43,7 +43,9 @@ 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;
|
||||
|
@ -79,6 +81,7 @@ 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;
|
||||
|
@ -113,6 +116,8 @@ 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;
|
||||
|
@ -130,6 +135,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;
|
||||
|
||||
/**
|
||||
* {@link Query} implementation based on an SQM
|
||||
|
@ -351,17 +357,13 @@ public class QuerySqmImpl<R>
|
|||
final SqmAssignment<?> assignment = assignments.get( i );
|
||||
final SqmPath<?> targetPath = assignment.getTargetPath();
|
||||
final SqmExpression<?> expression = assignment.getValue();
|
||||
if ( targetPath.getNodeJavaType() == null || expression.getNodeJavaType() == null ) {
|
||||
continue;
|
||||
}
|
||||
if ( targetPath.getNodeJavaType() != expression.getNodeJavaType()
|
||||
&& !targetPath.getNodeJavaType().isWider( expression.getNodeJavaType() ) ) {
|
||||
if ( !isAssignable( targetPath, expression ) ) {
|
||||
throw new SemanticException(
|
||||
String.format(
|
||||
"The assignment expression type [%s] did not match the assignment path type [%s] for the path [%s]",
|
||||
"Cannot assign expression of type '%s' to target path '%s' of type '%s'",
|
||||
expression.getNodeJavaType().getJavaType().getTypeName(),
|
||||
targetPath.getNodeJavaType().getJavaType().getTypeName(),
|
||||
targetPath.toHqlString()
|
||||
targetPath.toHqlString(),
|
||||
targetPath.getNodeJavaType().getJavaType().getTypeName()
|
||||
),
|
||||
hqlString,
|
||||
null
|
||||
|
@ -370,6 +372,34 @@ public class QuerySqmImpl<R>
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
if ( sqmStatement instanceof SqmInsertValuesStatement<?> ) {
|
||||
|
|
|
@ -40,6 +40,7 @@ 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;
|
||||
|
@ -156,7 +157,7 @@ import org.hibernate.type.BasicType;
|
|||
import org.hibernate.type.StandardBasicTypes;
|
||||
import org.hibernate.type.descriptor.java.EnumJavaType;
|
||||
import org.hibernate.type.descriptor.java.JavaType;
|
||||
import org.hibernate.type.descriptor.java.JavaTypeHelper;
|
||||
import org.hibernate.type.descriptor.jdbc.JdbcType;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
||||
import jakarta.persistence.Tuple;
|
||||
|
@ -178,6 +179,7 @@ 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;
|
||||
|
||||
/**
|
||||
* Acts as a JPA {@link jakarta.persistence.criteria.CriteriaBuilder} by
|
||||
|
@ -240,8 +242,12 @@ 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
|
||||
if ( lhsType == null || rhsType == null
|
||||
|| lhsType == rhsType
|
||||
|| isDiscriminatorComparison( lhsType, rhsType )
|
||||
// Allow comparing an embeddable against a tuple literal
|
||||
|| lhsType instanceof EmbeddedSqmPathSource<?> && rhsType instanceof TupleType
|
||||
|
@ -252,21 +258,34 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext,
|
|||
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
|
||||
|| JavaTypeHelper.isUnknown( lhsJavaType )
|
||||
|| JavaTypeHelper.isUnknown( rhsJavaType )
|
||||
// Allow comparing two temporal expressions regardless of their concrete java types
|
||||
|| JavaTypeHelper.isTemporal( lhsJavaType ) && JavaTypeHelper.isTemporal( rhsJavaType )
|
||||
// Assume we can coerce one to another
|
||||
|| lhsJavaType.isWider( rhsJavaType )
|
||||
|| rhsJavaType.isWider( lhsJavaType )
|
||||
// Polymorphic entity comparison
|
||||
|| lhsJavaType.getJavaTypeClass().isAssignableFrom( rhsJavaType.getJavaTypeClass() )
|
||||
|| rhsJavaType.getJavaTypeClass().isAssignableFrom( lhsJavaType.getJavaTypeClass() );
|
||||
// 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) {
|
||||
|
|
|
@ -10,10 +10,10 @@ import java.lang.reflect.Type;
|
|||
import java.sql.Types;
|
||||
import java.util.List;
|
||||
|
||||
import org.hibernate.QueryException;
|
||||
import org.hibernate.metamodel.MappingMetamodel;
|
||||
import org.hibernate.metamodel.mapping.JdbcMapping;
|
||||
import org.hibernate.metamodel.mapping.JdbcMappingContainer;
|
||||
import org.hibernate.metamodel.model.domain.DomainType;
|
||||
import org.hibernate.query.sqm.SqmExpressible;
|
||||
import org.hibernate.query.sqm.tree.SqmTypedNode;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmCollation;
|
||||
|
@ -95,16 +95,7 @@ public class ArgumentTypesValidator implements ArgumentsValidator {
|
|||
if ( nodeType != null ) {
|
||||
JavaType<?> javaType = nodeType.getRelationalJavaType();
|
||||
if (javaType != null) {
|
||||
try {
|
||||
checkType(
|
||||
count, functionName, type,
|
||||
getJdbcType( indicators, javaType ),
|
||||
javaType.getJavaTypeClass()
|
||||
);
|
||||
}
|
||||
catch (JdbcTypeRecommendationException e) {
|
||||
// it's a converter or something like that, and we will check it later
|
||||
}
|
||||
checkArgumentType( functionName, count, argument, indicators, type, javaType );
|
||||
}
|
||||
switch (type) {
|
||||
case TEMPORAL_UNIT:
|
||||
|
@ -134,9 +125,39 @@ public class ArgumentTypesValidator implements ArgumentsValidator {
|
|||
}
|
||||
}
|
||||
|
||||
private void checkArgumentType(
|
||||
String functionName,
|
||||
int count,
|
||||
SqmTypedNode<?> argument,
|
||||
JdbcTypeIndicators indicators,
|
||||
FunctionParameterType type,
|
||||
JavaType<?> javaType) {
|
||||
DomainType<?> domainType = argument.getExpressible().getSqmType();
|
||||
if ( domainType instanceof JdbcMapping ) {
|
||||
checkArgumentType(
|
||||
count, functionName, type,
|
||||
((JdbcMapping) domainType).getJdbcType().getDefaultSqlTypeCode(),
|
||||
javaType.getJavaTypeClass()
|
||||
);
|
||||
}
|
||||
else {
|
||||
//TODO: this branch is now probably obsolete and can be deleted!
|
||||
try {
|
||||
checkArgumentType(
|
||||
count, functionName, type,
|
||||
getJdbcType( indicators, javaType ),
|
||||
javaType.getJavaTypeClass()
|
||||
);
|
||||
}
|
||||
catch (JdbcTypeRecommendationException e) {
|
||||
// it's a converter or something like that, and we will check it later
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int getJdbcType(JdbcTypeIndicators indicators, JavaType<?> javaType) {
|
||||
if ( javaType.getJavaTypeClass().isEnum() ) {
|
||||
// magic value indicates it can be coerced STRING or ORDINAL
|
||||
// we can't tell if the enum is mapped STRING or ORDINAL
|
||||
return ENUM_UNKNOWN_JDBC_TYPE;
|
||||
}
|
||||
else {
|
||||
|
@ -177,7 +198,7 @@ public class ArgumentTypesValidator implements ArgumentsValidator {
|
|||
final JdbcMapping mapping = expressionType.getJdbcMapping( i );
|
||||
FunctionParameterType type = count < types.length ? types[count++] : types[types.length - 1];
|
||||
if (type != null) {
|
||||
checkType(
|
||||
checkArgumentType(
|
||||
count,
|
||||
functionName,
|
||||
type,
|
||||
|
@ -189,19 +210,21 @@ public class ArgumentTypesValidator implements ArgumentsValidator {
|
|||
return count;
|
||||
}
|
||||
|
||||
private void checkType(int count, String functionName, FunctionParameterType type, int code, Type javaType) {
|
||||
private void checkArgumentType(int count, String functionName, FunctionParameterType type, int code, Type javaType) {
|
||||
switch (type) {
|
||||
case COMPARABLE:
|
||||
if ( !isCharacterType(code) && !isTemporalType(code) && !isNumericType(code) && code != UUID ) {
|
||||
if ( javaType == java.util.UUID.class && ( code == Types.BINARY || isCharacterType( code ) ) ) {
|
||||
// We also consider UUID to be comparable when it's a character or binary type
|
||||
return;
|
||||
}
|
||||
if ( !isCharacterType(code) && !isTemporalType(code) && !isNumericType(code) && !isEnumType( code )
|
||||
// both Java and the database consider UUIDs
|
||||
// comparable, so go ahead and accept them
|
||||
&& code != UUID
|
||||
// as a special case, we consider a binary column
|
||||
// comparable when it is mapped by a Java UUID
|
||||
&& !( javaType == java.util.UUID.class && code == Types.BINARY ) ) {
|
||||
throwError(type, javaType, functionName, count);
|
||||
}
|
||||
break;
|
||||
case STRING:
|
||||
if ( !isCharacterType(code) && !isEnumType(code) && code != ENUM_UNKNOWN_JDBC_TYPE) {
|
||||
if ( !isCharacterType(code) && !isEnumType(code) ) {
|
||||
throwError(type, javaType, functionName, count);
|
||||
}
|
||||
break;
|
||||
|
@ -216,7 +239,7 @@ public class ArgumentTypesValidator implements ArgumentsValidator {
|
|||
}
|
||||
break;
|
||||
case INTEGER:
|
||||
if ( !isIntegral(code) && code != ENUM_UNKNOWN_JDBC_TYPE ) {
|
||||
if ( !isIntegral(code) ) {
|
||||
throwError(type, javaType, functionName, count);
|
||||
}
|
||||
break;
|
||||
|
|
|
@ -60,15 +60,4 @@ public class CurrencyJavaType extends AbstractClassJavaType<Currency> {
|
|||
public long getDefaultSqlLength(Dialect dialect, JdbcType jdbcType) {
|
||||
return 3;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWider(JavaType<?> javaType) {
|
||||
// This is necessary to allow comparing/assigning a currency attribute against a literal of the JdbcType
|
||||
switch ( javaType.getJavaType().getTypeName() ) {
|
||||
case "java.lang.String":
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -230,7 +230,7 @@ public class EnumJavaType<T extends Enum<T>> extends AbstractClassJavaType<T> {
|
|||
}
|
||||
|
||||
/**
|
||||
* Interpret a String value as the named value of the enum type
|
||||
* Interpret a string value as the named value of the enum type
|
||||
*/
|
||||
public T fromName(String relationalForm) {
|
||||
if ( relationalForm == null ) {
|
||||
|
@ -239,26 +239,6 @@ public class EnumJavaType<T extends Enum<T>> extends AbstractClassJavaType<T> {
|
|||
return Enum.valueOf( getJavaTypeClass(), relationalForm.trim() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWider(JavaType<?> javaType) {
|
||||
// This is necessary to allow comparing/assigning an enum attribute against a literal of the JdbcType
|
||||
switch ( javaType.getJavaType().getTypeName() ) {
|
||||
case "byte":
|
||||
case "java.lang.Byte":
|
||||
case "short":
|
||||
case "java.lang.Short":
|
||||
case "int":
|
||||
case "java.lang.Integer":
|
||||
case "long":
|
||||
case "java.lang.Long":
|
||||
case "java.lang.String":
|
||||
case "java.lang.Character":
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCheckCondition(String columnName, JdbcType jdbcType, BasicValueConverter<?, ?> converter, Dialect dialect) {
|
||||
if ( converter != null ) {
|
||||
|
|
|
@ -10,7 +10,6 @@ import java.sql.Timestamp;
|
|||
import java.sql.Types;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
|
|
@ -223,6 +223,12 @@ public interface JdbcType extends Serializable {
|
|||
return isCharacterOrClobType( getDdlTypeCode() );
|
||||
}
|
||||
|
||||
default boolean isStringLike() {
|
||||
int ddlTypeCode = getDdlTypeCode();
|
||||
return isCharacterOrClobType( ddlTypeCode )
|
||||
|| isEnumType( ddlTypeCode );
|
||||
}
|
||||
|
||||
default boolean isTemporal() {
|
||||
return isTemporalType( getDdlTypeCode() );
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ public class CompositeIdFkUpdateTest {
|
|||
}
|
||||
catch (IllegalArgumentException ex) {
|
||||
assertThat( ex.getCause() ).isInstanceOf( SemanticException.class );
|
||||
assertThat( ex.getCause() ).hasMessageContaining( "did not match" );
|
||||
assertThat( ex.getCause() ).hasMessageContaining( "Cannot assign expression of type" );
|
||||
}
|
||||
}
|
||||
);
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
package org.hibernate.orm.test.query.hql;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EnumType;
|
||||
import jakarta.persistence.Enumerated;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.Id;
|
||||
import org.hibernate.query.SemanticException;
|
||||
import org.hibernate.testing.orm.junit.DomainModel;
|
||||
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
@SessionFactory
|
||||
@DomainModel(annotatedClasses = EnumComparisonTest.WithEnum.class)
|
||||
public class EnumComparisonTest {
|
||||
@Test
|
||||
void test(SessionFactoryScope scope) {
|
||||
scope.inTransaction(session -> {
|
||||
session.persist(new WithEnum());
|
||||
assertEquals(1,
|
||||
session.createSelectionQuery("from WithEnum where stringEnum > X").getResultList().size());
|
||||
assertEquals(1,
|
||||
session.createSelectionQuery("from WithEnum where ordinalEnum > X").getResultList().size());
|
||||
assertEquals(1,
|
||||
session.createSelectionQuery("from WithEnum where stringEnum > 'X'").getResultList().size());
|
||||
assertEquals(1,
|
||||
session.createSelectionQuery("from WithEnum where ordinalEnum > 1").getResultList().size());
|
||||
try {
|
||||
session.createSelectionQuery("from WithEnum where ordinalEnum > 'X'").getResultList();
|
||||
fail();
|
||||
}
|
||||
catch (SemanticException se) {
|
||||
}
|
||||
try {
|
||||
session.createSelectionQuery("from WithEnum where stringEnum > 1").getResultList();
|
||||
fail();
|
||||
}
|
||||
catch (SemanticException se) {
|
||||
}
|
||||
session.createSelectionQuery("select max(ordinalEnum) from WithEnum").getSingleResult();
|
||||
session.createSelectionQuery("select max(stringEnum) from WithEnum").getSingleResult();
|
||||
});
|
||||
}
|
||||
|
||||
enum Enum { X, Y, Z }
|
||||
|
||||
@Entity(name = "WithEnum")
|
||||
static class WithEnum {
|
||||
@Id
|
||||
@GeneratedValue
|
||||
long id;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
Enum stringEnum = Enum.Y;
|
||||
|
||||
@Enumerated(EnumType.ORDINAL)
|
||||
Enum ordinalEnum = Enum.Z;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue