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;
|
import org.hibernate.type.descriptor.jdbc.JdbcType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specialization of DomainType for types that can be used as a
|
* Specialization of {@link org.hibernate.metamodel.model.domain.DomainType} for types that
|
||||||
* parameter output for a {@link org.hibernate.procedure.ProcedureCall}
|
* 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()}
|
* @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
|
@Override
|
||||||
public SqmRoot<R> visitTargetEntity(HqlParser.TargetEntityContext dmlTargetContext) {
|
public SqmRoot<R> visitTargetEntity(HqlParser.TargetEntityContext dmlTargetContext) {
|
||||||
final HqlParser.EntityNameContext entityNameContext = (HqlParser.EntityNameContext) dmlTargetContext.getChild( 0 );
|
final HqlParser.EntityNameContext entityNameContext = dmlTargetContext.entityName();
|
||||||
final String identificationVariable;
|
final HqlParser.VariableContext variable = dmlTargetContext.variable();
|
||||||
if ( dmlTargetContext.getChildCount() == 1 ) {
|
|
||||||
identificationVariable = null;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
identificationVariable = applyJpaCompliance(
|
|
||||||
visitVariable(
|
|
||||||
(HqlParser.VariableContext) dmlTargetContext.getChild( 1 )
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
//noinspection unchecked
|
//noinspection unchecked
|
||||||
return new SqmRoot<>(
|
return new SqmRoot<>(
|
||||||
(EntityDomainType<R>) visitEntityName( entityNameContext ),
|
(EntityDomainType<R>) visitEntityName( entityNameContext ),
|
||||||
identificationVariable,
|
variable == null ? null : applyJpaCompliance( visitVariable( variable ) ),
|
||||||
false,
|
false,
|
||||||
creationContext.getNodeBuilder()
|
creationContext.getNodeBuilder()
|
||||||
);
|
);
|
||||||
|
@ -457,17 +447,8 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SqmInsertStatement<R> visitInsertStatement(HqlParser.InsertStatementContext ctx) {
|
public SqmInsertStatement<R> visitInsertStatement(HqlParser.InsertStatementContext ctx) {
|
||||||
final int dmlTargetIndex;
|
final HqlParser.TargetEntityContext dmlTargetContext = ctx.targetEntity();
|
||||||
if ( ctx.getChild( 1 ) instanceof HqlParser.TargetEntityContext ) {
|
final HqlParser.TargetFieldsContext targetFieldsSpecContext = ctx.targetFields();
|
||||||
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 SqmRoot<R> root = visitTargetEntity( dmlTargetContext );
|
final SqmRoot<R> root = visitTargetEntity( dmlTargetContext );
|
||||||
if ( root.getModel() instanceof SqmPolymorphicRootDescriptor<?> ) {
|
if ( root.getModel() instanceof SqmPolymorphicRootDescriptor<?> ) {
|
||||||
throw new SemanticException(
|
throw new SemanticException(
|
||||||
|
@ -480,7 +461,8 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
|
||||||
|
|
||||||
final HqlParser.QueryExpressionContext queryExpressionContext = ctx.queryExpression();
|
final HqlParser.QueryExpressionContext queryExpressionContext = ctx.queryExpression();
|
||||||
if ( queryExpressionContext != null ) {
|
if ( queryExpressionContext != null ) {
|
||||||
final SqmInsertSelectStatement<R> insertStatement = new SqmInsertSelectStatement<>( root, creationContext.getNodeBuilder() );
|
final SqmInsertSelectStatement<R> insertStatement =
|
||||||
|
new SqmInsertSelectStatement<>( root, creationContext.getNodeBuilder() );
|
||||||
parameterCollector = insertStatement;
|
parameterCollector = insertStatement;
|
||||||
final SqmDmlCreationProcessingState processingState = new SqmDmlCreationProcessingState(
|
final SqmDmlCreationProcessingState processingState = new SqmDmlCreationProcessingState(
|
||||||
insertStatement,
|
insertStatement,
|
||||||
|
@ -517,7 +499,8 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
|
||||||
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
final SqmInsertValuesStatement<R> insertStatement = new SqmInsertValuesStatement<>( root, creationContext.getNodeBuilder() );
|
final SqmInsertValuesStatement<R> insertStatement =
|
||||||
|
new SqmInsertValuesStatement<>( root, creationContext.getNodeBuilder() );
|
||||||
parameterCollector = insertStatement;
|
parameterCollector = insertStatement;
|
||||||
final SqmDmlCreationProcessingState processingState = new SqmDmlCreationProcessingState(
|
final SqmDmlCreationProcessingState processingState = new SqmDmlCreationProcessingState(
|
||||||
insertStatement,
|
insertStatement,
|
||||||
|
@ -553,9 +536,8 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SqmUpdateStatement<R> visitUpdateStatement(HqlParser.UpdateStatementContext ctx) {
|
public SqmUpdateStatement<R> visitUpdateStatement(HqlParser.UpdateStatementContext ctx) {
|
||||||
final boolean versioned = !( ctx.getChild( 1 ) instanceof HqlParser.TargetEntityContext );
|
final boolean versioned = ctx.VERSIONED() != null;
|
||||||
final int dmlTargetIndex = versioned ? 2 : 1;
|
final HqlParser.TargetEntityContext dmlTargetContext = ctx.targetEntity();
|
||||||
final HqlParser.TargetEntityContext dmlTargetContext = (HqlParser.TargetEntityContext) ctx.getChild( dmlTargetIndex );
|
|
||||||
final SqmRoot<R> root = visitTargetEntity( dmlTargetContext );
|
final SqmRoot<R> root = visitTargetEntity( dmlTargetContext );
|
||||||
if ( root.getModel() instanceof SqmPolymorphicRootDescriptor<?> ) {
|
if ( root.getModel() instanceof SqmPolymorphicRootDescriptor<?> ) {
|
||||||
throw new SemanticException(
|
throw new SemanticException(
|
||||||
|
@ -577,12 +559,12 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
|
||||||
|
|
||||||
try {
|
try {
|
||||||
updateStatement.versioned( versioned );
|
updateStatement.versioned( versioned );
|
||||||
final HqlParser.SetClauseContext setClauseCtx = (HqlParser.SetClauseContext) ctx.getChild( dmlTargetIndex + 1 );
|
final HqlParser.SetClauseContext setClauseCtx = ctx.setClause();
|
||||||
for ( ParseTree subCtx : setClauseCtx.children ) {
|
for ( ParseTree subCtx : setClauseCtx.children ) {
|
||||||
if ( subCtx instanceof HqlParser.AssignmentContext ) {
|
if ( subCtx instanceof HqlParser.AssignmentContext ) {
|
||||||
final HqlParser.AssignmentContext assignmentContext = (HqlParser.AssignmentContext) subCtx;
|
final HqlParser.AssignmentContext assignmentContext = (HqlParser.AssignmentContext) subCtx;
|
||||||
//noinspection unchecked
|
//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 Class<?> targetPathJavaType = targetPath.getJavaType();
|
||||||
final boolean isEnum = targetPathJavaType != null && targetPathJavaType.isEnum();
|
final boolean isEnum = targetPathJavaType != null && targetPathJavaType.isEnum();
|
||||||
final ParseTree rightSide = assignmentContext.getChild( 2 );
|
final ParseTree rightSide = assignmentContext.getChild( 2 );
|
||||||
|
@ -604,10 +586,9 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( dmlTargetIndex + 2 <= ctx.getChildCount() ) {
|
final HqlParser.WhereClauseContext whereClauseContext = ctx.whereClause();
|
||||||
updateStatement.applyPredicate(
|
if ( whereClauseContext != null ) {
|
||||||
visitWhereClause( (HqlParser.WhereClauseContext) ctx.getChild( dmlTargetIndex + 2 ) )
|
updateStatement.applyPredicate( visitWhereClause( whereClauseContext ) );
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return updateStatement;
|
return updateStatement;
|
||||||
|
@ -619,14 +600,7 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SqmDeleteStatement<R> visitDeleteStatement(HqlParser.DeleteStatementContext ctx) {
|
public SqmDeleteStatement<R> visitDeleteStatement(HqlParser.DeleteStatementContext ctx) {
|
||||||
final int dmlTargetIndex;
|
final HqlParser.TargetEntityContext dmlTargetContext = ctx.targetEntity();
|
||||||
if ( ctx.getChild( 1 ) instanceof HqlParser.TargetEntityContext ) {
|
|
||||||
dmlTargetIndex = 1;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
dmlTargetIndex = 2;
|
|
||||||
}
|
|
||||||
final HqlParser.TargetEntityContext dmlTargetContext = (HqlParser.TargetEntityContext) ctx.getChild( dmlTargetIndex );
|
|
||||||
final SqmRoot<R> root = visitTargetEntity( dmlTargetContext );
|
final SqmRoot<R> root = visitTargetEntity( dmlTargetContext );
|
||||||
|
|
||||||
final SqmDeleteStatement<R> deleteStatement = new SqmDeleteStatement<>( root, SqmQuerySource.HQL, creationContext.getNodeBuilder() );
|
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 );
|
processingStateStack.push( sqmDeleteCreationState );
|
||||||
try {
|
try {
|
||||||
if ( dmlTargetIndex + 1 <= ctx.getChildCount() ) {
|
final HqlParser.WhereClauseContext whereClauseContext = ctx.whereClause();
|
||||||
deleteStatement.applyPredicate(
|
if ( whereClauseContext != null ) {
|
||||||
visitWhereClause( (HqlParser.WhereClauseContext) ctx.getChild( dmlTargetIndex + 1 ) )
|
deleteStatement.applyPredicate( visitWhereClause( whereClauseContext ) );
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return deleteStatement;
|
return deleteStatement;
|
||||||
|
@ -2472,11 +2445,6 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
|
||||||
HqlParser.ExpressionContext rightExpressionContext) {
|
HqlParser.ExpressionContext rightExpressionContext) {
|
||||||
final SqmExpression<?> right;
|
final SqmExpression<?> right;
|
||||||
final SqmExpression<?> left;
|
final SqmExpression<?> left;
|
||||||
switch ( comparisonOperator ) {
|
|
||||||
case EQUAL:
|
|
||||||
case NOT_EQUAL:
|
|
||||||
case DISTINCT_FROM:
|
|
||||||
case NOT_DISTINCT_FROM: {
|
|
||||||
Map<Class<?>, Enum<?>> possibleEnumValues;
|
Map<Class<?>, Enum<?>> possibleEnumValues;
|
||||||
if ( ( possibleEnumValues = getPossibleEnumValues( leftExpressionContext ) ) != null ) {
|
if ( ( possibleEnumValues = getPossibleEnumValues( leftExpressionContext ) ) != null ) {
|
||||||
right = (SqmExpression<?>) rightExpressionContext.accept( this );
|
right = (SqmExpression<?>) rightExpressionContext.accept( this );
|
||||||
|
@ -2485,7 +2453,6 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
|
||||||
possibleEnumValues,
|
possibleEnumValues,
|
||||||
right.getJavaType()
|
right.getJavaType()
|
||||||
);
|
);
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
else if ( ( possibleEnumValues = getPossibleEnumValues( rightExpressionContext ) ) != null ) {
|
else if ( ( possibleEnumValues = getPossibleEnumValues( rightExpressionContext ) ) != null ) {
|
||||||
left = (SqmExpression<?>) leftExpressionContext.accept( this );
|
left = (SqmExpression<?>) leftExpressionContext.accept( this );
|
||||||
|
@ -2494,8 +2461,8 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
|
||||||
possibleEnumValues,
|
possibleEnumValues,
|
||||||
left.getJavaType()
|
left.getJavaType()
|
||||||
);
|
);
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
final SqmExpression<?> l = (SqmExpression<?>) leftExpressionContext.accept( this );
|
final SqmExpression<?> l = (SqmExpression<?>) leftExpressionContext.accept( this );
|
||||||
final SqmExpression<?> r = (SqmExpression<?>) rightExpressionContext.accept( this );
|
final SqmExpression<?> r = (SqmExpression<?>) rightExpressionContext.accept( this );
|
||||||
if ( l instanceof AnyDiscriminatorSqmPath && r instanceof SqmLiteralEntityType ) {
|
if ( l instanceof AnyDiscriminatorSqmPath && r instanceof SqmLiteralEntityType ) {
|
||||||
|
@ -2510,13 +2477,6 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
|
||||||
left = l;
|
left = l;
|
||||||
right = r;
|
right = r;
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
left = (SqmExpression<?>) leftExpressionContext.accept( this );
|
|
||||||
right = (SqmExpression<?>) rightExpressionContext.accept( this );
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
SqmCriteriaNodeBuilder.assertComparable( left, right );
|
SqmCriteriaNodeBuilder.assertComparable( left, right );
|
||||||
return new SqmComparisonPredicate(
|
return new SqmComparisonPredicate(
|
||||||
|
|
|
@ -43,7 +43,9 @@ import org.hibernate.id.enhanced.Optimizer;
|
||||||
import org.hibernate.internal.CoreLogging;
|
import org.hibernate.internal.CoreLogging;
|
||||||
import org.hibernate.internal.CoreMessageLogger;
|
import org.hibernate.internal.CoreMessageLogger;
|
||||||
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
|
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
|
||||||
|
import org.hibernate.metamodel.mapping.JdbcMapping;
|
||||||
import org.hibernate.metamodel.mapping.internal.SingleAttributeIdentifierMapping;
|
import org.hibernate.metamodel.mapping.internal.SingleAttributeIdentifierMapping;
|
||||||
|
import org.hibernate.metamodel.model.domain.DomainType;
|
||||||
import org.hibernate.metamodel.model.domain.EntityDomainType;
|
import org.hibernate.metamodel.model.domain.EntityDomainType;
|
||||||
import org.hibernate.persister.entity.AbstractEntityPersister;
|
import org.hibernate.persister.entity.AbstractEntityPersister;
|
||||||
import org.hibernate.persister.entity.EntityPersister;
|
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.spi.SelectQueryPlan;
|
||||||
import org.hibernate.query.sqm.NodeBuilder;
|
import org.hibernate.query.sqm.NodeBuilder;
|
||||||
import org.hibernate.query.sqm.SortOrder;
|
import org.hibernate.query.sqm.SortOrder;
|
||||||
|
import org.hibernate.query.sqm.SqmExpressible;
|
||||||
import org.hibernate.query.sqm.SqmPathSource;
|
import org.hibernate.query.sqm.SqmPathSource;
|
||||||
import org.hibernate.query.sqm.internal.SqmInterpretationsKey.InterpretationsKeySource;
|
import org.hibernate.query.sqm.internal.SqmInterpretationsKey.InterpretationsKeySource;
|
||||||
import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy;
|
import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy;
|
||||||
|
@ -113,6 +116,8 @@ import jakarta.persistence.LockModeType;
|
||||||
import jakarta.persistence.Parameter;
|
import jakarta.persistence.Parameter;
|
||||||
import jakarta.persistence.PersistenceException;
|
import jakarta.persistence.PersistenceException;
|
||||||
import jakarta.persistence.TemporalType;
|
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_CACHEABLE;
|
||||||
import static org.hibernate.jpa.HibernateHints.HINT_CACHE_MODE;
|
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.SqmInterpretationsKey.generateNonSelectKey;
|
||||||
import static org.hibernate.query.sqm.internal.SqmUtil.isSelect;
|
import static org.hibernate.query.sqm.internal.SqmUtil.isSelect;
|
||||||
import static org.hibernate.query.sqm.internal.SqmUtil.verifyIsNonSelectStatement;
|
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
|
* {@link Query} implementation based on an SQM
|
||||||
|
@ -351,17 +357,13 @@ public class QuerySqmImpl<R>
|
||||||
final SqmAssignment<?> assignment = assignments.get( i );
|
final SqmAssignment<?> assignment = assignments.get( i );
|
||||||
final SqmPath<?> targetPath = assignment.getTargetPath();
|
final SqmPath<?> targetPath = assignment.getTargetPath();
|
||||||
final SqmExpression<?> expression = assignment.getValue();
|
final SqmExpression<?> expression = assignment.getValue();
|
||||||
if ( targetPath.getNodeJavaType() == null || expression.getNodeJavaType() == null ) {
|
if ( !isAssignable( targetPath, expression ) ) {
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if ( targetPath.getNodeJavaType() != expression.getNodeJavaType()
|
|
||||||
&& !targetPath.getNodeJavaType().isWider( expression.getNodeJavaType() ) ) {
|
|
||||||
throw new SemanticException(
|
throw new SemanticException(
|
||||||
String.format(
|
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(),
|
expression.getNodeJavaType().getJavaType().getTypeName(),
|
||||||
targetPath.getNodeJavaType().getJavaType().getTypeName(),
|
targetPath.toHqlString(),
|
||||||
targetPath.toHqlString()
|
targetPath.getNodeJavaType().getJavaType().getTypeName()
|
||||||
),
|
),
|
||||||
hqlString,
|
hqlString,
|
||||||
null
|
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) {
|
private void verifyInsertTypesMatch(String hqlString, SqmInsertStatement<R> sqmStatement) {
|
||||||
final List<SqmPath<?>> insertionTargetPaths = sqmStatement.getInsertionTargetPaths();
|
final List<SqmPath<?>> insertionTargetPaths = sqmStatement.getInsertionTargetPaths();
|
||||||
if ( sqmStatement instanceof SqmInsertValuesStatement<?> ) {
|
if ( sqmStatement instanceof SqmInsertValuesStatement<?> ) {
|
||||||
|
|
|
@ -40,6 +40,7 @@ import org.hibernate.internal.CoreMessageLogger;
|
||||||
import org.hibernate.internal.SessionFactoryRegistry;
|
import org.hibernate.internal.SessionFactoryRegistry;
|
||||||
import org.hibernate.internal.util.StringHelper;
|
import org.hibernate.internal.util.StringHelper;
|
||||||
import org.hibernate.internal.util.collections.CollectionHelper;
|
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.BasicDomainType;
|
||||||
import org.hibernate.metamodel.model.domain.DomainType;
|
import org.hibernate.metamodel.model.domain.DomainType;
|
||||||
import org.hibernate.metamodel.model.domain.JpaMetamodel;
|
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.StandardBasicTypes;
|
||||||
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.JavaTypeHelper;
|
import org.hibernate.type.descriptor.jdbc.JdbcType;
|
||||||
import org.hibernate.type.spi.TypeConfiguration;
|
import org.hibernate.type.spi.TypeConfiguration;
|
||||||
|
|
||||||
import jakarta.persistence.Tuple;
|
import jakarta.persistence.Tuple;
|
||||||
|
@ -178,6 +179,7 @@ import jakarta.persistence.metamodel.EntityType;
|
||||||
import static java.util.Arrays.asList;
|
import static java.util.Arrays.asList;
|
||||||
import static org.hibernate.query.internal.QueryHelper.highestPrecedenceType;
|
import static org.hibernate.query.internal.QueryHelper.highestPrecedenceType;
|
||||||
import static org.hibernate.query.sqm.TrimSpec.fromCriteriaTrimSpec;
|
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
|
* Acts as a JPA {@link jakarta.persistence.criteria.CriteriaBuilder} by
|
||||||
|
@ -240,8 +242,12 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext,
|
||||||
return sessionFactory.get();
|
return sessionFactory.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see QuerySqmImpl#isAssignable(SqmPath, SqmExpression)
|
||||||
|
*/
|
||||||
public static boolean areTypesComparable(SqmExpressible<?> lhsType, SqmExpressible<?> rhsType) {
|
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 )
|
|| isDiscriminatorComparison( lhsType, rhsType )
|
||||||
// Allow comparing an embeddable against a tuple literal
|
// Allow comparing an embeddable against a tuple literal
|
||||||
|| lhsType instanceof EmbeddedSqmPathSource<?> && rhsType instanceof TupleType
|
|| lhsType instanceof EmbeddedSqmPathSource<?> && rhsType instanceof TupleType
|
||||||
|
@ -252,18 +258,31 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext,
|
||||||
return true;
|
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<?> lhsJavaType = lhsType.getExpressibleJavaType();
|
||||||
final JavaType<?> rhsJavaType = rhsType.getExpressibleJavaType();
|
final JavaType<?> rhsJavaType = rhsType.getExpressibleJavaType();
|
||||||
|
|
||||||
return lhsJavaType == rhsJavaType
|
return lhsJavaType == rhsJavaType
|
||||||
// If we don't know the java types, let's just be lenient
|
// If we don't know the java types, let's just be lenient
|
||||||
|| JavaTypeHelper.isUnknown( lhsJavaType )
|
|| isUnknown( lhsJavaType )
|
||||||
|| JavaTypeHelper.isUnknown( rhsJavaType )
|
|| isUnknown( rhsJavaType )
|
||||||
// Allow comparing two temporal expressions regardless of their concrete java types
|
// Allow comparing two temporal expressions regardless of their concrete java types
|
||||||
|| JavaTypeHelper.isTemporal( lhsJavaType ) && JavaTypeHelper.isTemporal( rhsJavaType )
|
|| lhsJavaType.isTemporalType() && rhsJavaType.isTemporalType()
|
||||||
// Assume we can coerce one to another
|
// Assume we can coerce one to another
|
||||||
|| lhsJavaType.isWider( rhsJavaType )
|
|| lhsJavaType.isWider( rhsJavaType )
|
||||||
|| rhsJavaType.isWider( lhsJavaType )
|
|| rhsJavaType.isWider( lhsJavaType )
|
||||||
|
// Enum comparison, other strange user type mappings,
|
||||||
// Polymorphic entity comparison
|
// Polymorphic entity comparison
|
||||||
|| lhsJavaType.getJavaTypeClass().isAssignableFrom( rhsJavaType.getJavaTypeClass() )
|
|| lhsJavaType.getJavaTypeClass().isAssignableFrom( rhsJavaType.getJavaTypeClass() )
|
||||||
|| rhsJavaType.getJavaTypeClass().isAssignableFrom( lhsJavaType.getJavaTypeClass() );
|
|| rhsJavaType.getJavaTypeClass().isAssignableFrom( lhsJavaType.getJavaTypeClass() );
|
||||||
|
|
|
@ -10,10 +10,10 @@ import java.lang.reflect.Type;
|
||||||
import java.sql.Types;
|
import java.sql.Types;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.hibernate.QueryException;
|
|
||||||
import org.hibernate.metamodel.MappingMetamodel;
|
import org.hibernate.metamodel.MappingMetamodel;
|
||||||
import org.hibernate.metamodel.mapping.JdbcMapping;
|
import org.hibernate.metamodel.mapping.JdbcMapping;
|
||||||
import org.hibernate.metamodel.mapping.JdbcMappingContainer;
|
import org.hibernate.metamodel.mapping.JdbcMappingContainer;
|
||||||
|
import org.hibernate.metamodel.model.domain.DomainType;
|
||||||
import org.hibernate.query.sqm.SqmExpressible;
|
import org.hibernate.query.sqm.SqmExpressible;
|
||||||
import org.hibernate.query.sqm.tree.SqmTypedNode;
|
import org.hibernate.query.sqm.tree.SqmTypedNode;
|
||||||
import org.hibernate.query.sqm.tree.expression.SqmCollation;
|
import org.hibernate.query.sqm.tree.expression.SqmCollation;
|
||||||
|
@ -95,16 +95,7 @@ public class ArgumentTypesValidator implements ArgumentsValidator {
|
||||||
if ( nodeType != null ) {
|
if ( nodeType != null ) {
|
||||||
JavaType<?> javaType = nodeType.getRelationalJavaType();
|
JavaType<?> javaType = nodeType.getRelationalJavaType();
|
||||||
if (javaType != null) {
|
if (javaType != null) {
|
||||||
try {
|
checkArgumentType( functionName, count, argument, indicators, type, javaType );
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case TEMPORAL_UNIT:
|
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) {
|
private int getJdbcType(JdbcTypeIndicators indicators, JavaType<?> javaType) {
|
||||||
if ( javaType.getJavaTypeClass().isEnum() ) {
|
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;
|
return ENUM_UNKNOWN_JDBC_TYPE;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -177,7 +198,7 @@ public class ArgumentTypesValidator implements ArgumentsValidator {
|
||||||
final JdbcMapping mapping = expressionType.getJdbcMapping( i );
|
final JdbcMapping mapping = expressionType.getJdbcMapping( i );
|
||||||
FunctionParameterType type = count < types.length ? types[count++] : types[types.length - 1];
|
FunctionParameterType type = count < types.length ? types[count++] : types[types.length - 1];
|
||||||
if (type != null) {
|
if (type != null) {
|
||||||
checkType(
|
checkArgumentType(
|
||||||
count,
|
count,
|
||||||
functionName,
|
functionName,
|
||||||
type,
|
type,
|
||||||
|
@ -189,19 +210,21 @@ public class ArgumentTypesValidator implements ArgumentsValidator {
|
||||||
return count;
|
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) {
|
switch (type) {
|
||||||
case COMPARABLE:
|
case COMPARABLE:
|
||||||
if ( !isCharacterType(code) && !isTemporalType(code) && !isNumericType(code) && code != UUID ) {
|
if ( !isCharacterType(code) && !isTemporalType(code) && !isNumericType(code) && !isEnumType( code )
|
||||||
if ( javaType == java.util.UUID.class && ( code == Types.BINARY || isCharacterType( code ) ) ) {
|
// both Java and the database consider UUIDs
|
||||||
// We also consider UUID to be comparable when it's a character or binary type
|
// comparable, so go ahead and accept them
|
||||||
return;
|
&& 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);
|
throwError(type, javaType, functionName, count);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case STRING:
|
case STRING:
|
||||||
if ( !isCharacterType(code) && !isEnumType(code) && code != ENUM_UNKNOWN_JDBC_TYPE) {
|
if ( !isCharacterType(code) && !isEnumType(code) ) {
|
||||||
throwError(type, javaType, functionName, count);
|
throwError(type, javaType, functionName, count);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -216,7 +239,7 @@ public class ArgumentTypesValidator implements ArgumentsValidator {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case INTEGER:
|
case INTEGER:
|
||||||
if ( !isIntegral(code) && code != ENUM_UNKNOWN_JDBC_TYPE ) {
|
if ( !isIntegral(code) ) {
|
||||||
throwError(type, javaType, functionName, count);
|
throwError(type, javaType, functionName, count);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -60,15 +60,4 @@ public class CurrencyJavaType extends AbstractClassJavaType<Currency> {
|
||||||
public long getDefaultSqlLength(Dialect dialect, JdbcType jdbcType) {
|
public long getDefaultSqlLength(Dialect dialect, JdbcType jdbcType) {
|
||||||
return 3;
|
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) {
|
public T fromName(String relationalForm) {
|
||||||
if ( relationalForm == null ) {
|
if ( relationalForm == null ) {
|
||||||
|
@ -239,26 +239,6 @@ public class EnumJavaType<T extends Enum<T>> extends AbstractClassJavaType<T> {
|
||||||
return Enum.valueOf( getJavaTypeClass(), relationalForm.trim() );
|
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
|
@Override
|
||||||
public String getCheckCondition(String columnName, JdbcType jdbcType, BasicValueConverter<?, ?> converter, Dialect dialect) {
|
public String getCheckCondition(String columnName, JdbcType jdbcType, BasicValueConverter<?, ?> converter, Dialect dialect) {
|
||||||
if ( converter != null ) {
|
if ( converter != null ) {
|
||||||
|
|
|
@ -10,7 +10,6 @@ import java.sql.Timestamp;
|
||||||
import java.sql.Types;
|
import java.sql.Types;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.time.ZoneId;
|
import java.time.ZoneId;
|
||||||
import java.time.ZoneOffset;
|
import java.time.ZoneOffset;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
|
|
|
@ -223,6 +223,12 @@ public interface JdbcType extends Serializable {
|
||||||
return isCharacterOrClobType( getDdlTypeCode() );
|
return isCharacterOrClobType( getDdlTypeCode() );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
default boolean isStringLike() {
|
||||||
|
int ddlTypeCode = getDdlTypeCode();
|
||||||
|
return isCharacterOrClobType( ddlTypeCode )
|
||||||
|
|| isEnumType( ddlTypeCode );
|
||||||
|
}
|
||||||
|
|
||||||
default boolean isTemporal() {
|
default boolean isTemporal() {
|
||||||
return isTemporalType( getDdlTypeCode() );
|
return isTemporalType( getDdlTypeCode() );
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,7 @@ public class CompositeIdFkUpdateTest {
|
||||||
}
|
}
|
||||||
catch (IllegalArgumentException ex) {
|
catch (IllegalArgumentException ex) {
|
||||||
assertThat( ex.getCause() ).isInstanceOf( SemanticException.class );
|
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