From ce317960fc7b04881969f66512f2a7baf0eef84b Mon Sep 17 00:00:00 2001 From: Gavin King Date: Thu, 28 Mar 2024 20:01:03 +0100 Subject: [PATCH] handle unqualified enum values in @Query Signed-off-by: Gavin King --- .../metamodel/model/domain/JpaMetamodel.java | 8 +- .../domain/internal/JpaMetamodelImpl.java | 14 +-- .../domain/internal/MappingMetamodelImpl.java | 4 +- .../query/criteria/JpaTupleElement.java | 9 ++ .../hql/internal/SemanticQueryBuilder.java | 103 ++++++++++++------ .../processor/test/data/eg/Library.java | 3 + .../validation/MockSessionFactory.java | 9 ++ .../validation/ProcessorSessionFactory.java | 26 +++++ 8 files changed, 125 insertions(+), 51 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/JpaMetamodel.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/JpaMetamodel.java index d28c1873f1..973287511e 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/JpaMetamodel.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/JpaMetamodel.java @@ -7,7 +7,6 @@ package org.hibernate.metamodel.model.domain; import java.util.List; -import java.util.Map; import java.util.Set; import jakarta.persistence.metamodel.EmbeddableType; import jakarta.persistence.metamodel.EntityType; @@ -79,12 +78,7 @@ public interface JpaMetamodel extends Metamodel { String qualifyImportableName(String queryName); - /** - * Returns a map that gives access to the enum literal expressions that can be used in queries. - * The key is the shorthand enum literal. The value is a map, from enum class to the actual enum value. - * This is needed for parsing shorthand enum literals that don't use FQNs. - */ - Map, Enum>> getAllowedEnumLiteralTexts(); + Set getAllowedEnumLiteralTexts(String enumValue); EnumJavaType getEnumType(String prefix); diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/JpaMetamodelImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/JpaMetamodelImpl.java index 4800674942..d0ffaef2cb 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/JpaMetamodelImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/JpaMetamodelImpl.java @@ -91,7 +91,7 @@ public class JpaMetamodelImpl implements JpaMetamodelImplementor, Serializable { private final Map, ManagedDomainType> jpaManagedTypeMap = new HashMap<>(); private final Set> jpaManagedTypes = new HashSet<>(); private final Set> jpaEmbeddables = new HashSet<>(); - private final Map, Enum>> allowedEnumLiteralTexts = new HashMap<>(); + private final Map> allowedEnumLiteralTexts = new HashMap<>(); private final transient Map> entityGraphMap = new ConcurrentHashMap<>(); @@ -239,8 +239,8 @@ public class JpaMetamodelImpl implements JpaMetamodelImplementor, Serializable { } @Override - public Map, Enum>> getAllowedEnumLiteralTexts() { - return allowedEnumLiteralTexts; + public Set getAllowedEnumLiteralTexts(String enumValue) { + return allowedEnumLiteralTexts.get(enumValue); } @Override @@ -604,13 +604,13 @@ public class JpaMetamodelImpl implements JpaMetamodelImplementor, Serializable { final Enum[] enumConstants = enumJavaClass.getEnumConstants(); for ( Enum enumConstant : enumConstants ) { allowedEnumLiteralTexts - .computeIfAbsent( enumConstant.name(), (s) -> new HashMap<>() ) - .put( enumJavaClass, enumConstant ); + .computeIfAbsent( enumConstant.name(), s -> new HashSet<>() ) + .add( enumJavaClass.getName() ); final String simpleQualifiedName = enumJavaClass.getSimpleName() + "." + enumConstant.name(); allowedEnumLiteralTexts - .computeIfAbsent( simpleQualifiedName, (s) -> new HashMap<>() ) - .put( enumJavaClass, enumConstant ); + .computeIfAbsent( simpleQualifiedName, s -> new HashSet<>() ) + .add( enumJavaClass.getName() ); } } } ); diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/MappingMetamodelImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/MappingMetamodelImpl.java index ec14f230a2..15b691cb3c 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/MappingMetamodelImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/MappingMetamodelImpl.java @@ -520,8 +520,8 @@ public class MappingMetamodelImpl extends QueryParameterBindingTypeResolverImpl } @Override - public Map, Enum>> getAllowedEnumLiteralTexts() { - return jpaMetamodel.getAllowedEnumLiteralTexts(); + public Set getAllowedEnumLiteralTexts(String enumValue) { + return jpaMetamodel.getAllowedEnumLiteralTexts(enumValue); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaTupleElement.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaTupleElement.java index 5324d4164c..fbf5f779a4 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaTupleElement.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaTupleElement.java @@ -8,6 +8,7 @@ package org.hibernate.query.criteria; import jakarta.persistence.TupleElement; +import org.hibernate.type.descriptor.java.EnumJavaType; import org.hibernate.type.descriptor.java.JavaType; /** @@ -23,4 +24,12 @@ public interface JpaTupleElement extends TupleElement, JpaCriteriaNode { // todo (6.0) : can this signature just return `Class`? return getJavaTypeDescriptor() == null ? null : getJavaTypeDescriptor().getJavaTypeClass(); } + + default String getJavaTypeName() { + return getJavaTypeDescriptor() == null ? null : getJavaTypeDescriptor().getTypeName(); + } + + default boolean isEnum() { + return getJavaTypeDescriptor() instanceof EnumJavaType; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java index b7da1c7c99..5cd6ab4590 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java @@ -586,18 +586,19 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem final Iterator> iterator = insertStatement.getInsertionTargetPaths().iterator(); for ( int j = 1; j < values.getChildCount(); j += 2 ) { final SqmPath targetPath = iterator.next(); - final Class targetPathJavaType = targetPath.getJavaType(); - final boolean isEnum = targetPathJavaType != null && targetPathJavaType.isEnum(); + final String targetPathJavaType = targetPath.getJavaTypeName(); + final boolean isEnum = targetPath.isEnum(); final ParseTree valuesContext = values.getChild( j ); final HqlParser.ExpressionContext expressionContext; - final Map, Enum> possibleEnumValues; + final Set possibleEnumTypes; final SqmExpression value; if ( isEnum && valuesContext.getChild( 0 ) instanceof HqlParser.ExpressionContext - && ( possibleEnumValues = getPossibleEnumValues( expressionContext = (HqlParser.ExpressionContext) valuesContext.getChild( 0 ) ) ) != null ) { + && ( possibleEnumTypes = getPossibleEnumTypes( expressionContext = (HqlParser.ExpressionContext) valuesContext.getChild( 0 ) ) ) != null ) { value = resolveEnumShorthandLiteral( expressionContext, - possibleEnumValues, - targetPathJavaType + getPossibleEnumValue( expressionContext ), + targetPathJavaType, + possibleEnumTypes ); } else { @@ -692,18 +693,19 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem public SqmAssignment visitAssignment(HqlParser.AssignmentContext ctx) { //noinspection unchecked final SqmPath targetPath = (SqmPath) consumeDomainPath( ctx.simplePath() ); - final Class targetPathJavaType = targetPath.getJavaType(); - final boolean isEnum = targetPathJavaType != null && targetPathJavaType.isEnum(); + final String targetPathJavaType = targetPath.getJavaTypeName(); + final boolean isEnum = targetPath.isEnum(); final ParseTree rightSide = ctx.getChild( 2 ); final HqlParser.ExpressionContext expressionContext; - final Map, Enum> possibleEnumValues; + final Set possibleEnumValues; final SqmExpression value; if ( isEnum && rightSide.getChild( 0 ) instanceof HqlParser.ExpressionContext - && ( possibleEnumValues = getPossibleEnumValues( expressionContext = (HqlParser.ExpressionContext) rightSide.getChild( 0 ) ) ) != null ) { + && ( possibleEnumValues = getPossibleEnumTypes( expressionContext = (HqlParser.ExpressionContext) rightSide.getChild( 0 ) ) ) != null ) { value = resolveEnumShorthandLiteral( expressionContext, - possibleEnumValues, - targetPathJavaType + getPossibleEnumValue( expressionContext ), + targetPathJavaType, + possibleEnumValues ); } else { @@ -2518,21 +2520,23 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem HqlParser.ExpressionContext rightExpressionContext) { final SqmExpression right; final SqmExpression left; - Map, Enum> possibleEnumValues; - if ( ( possibleEnumValues = getPossibleEnumValues( leftExpressionContext ) ) != null ) { + Set possibleEnumTypes; + if ( ( possibleEnumTypes = getPossibleEnumTypes( leftExpressionContext ) ) != null ) { right = (SqmExpression) rightExpressionContext.accept( this ); left = resolveEnumShorthandLiteral( leftExpressionContext, - possibleEnumValues, - right.getJavaType() + getPossibleEnumValue( leftExpressionContext ), + right.getJavaTypeName(), + possibleEnumTypes ); } - else if ( ( possibleEnumValues = getPossibleEnumValues( rightExpressionContext ) ) != null ) { + else if ( ( possibleEnumTypes = getPossibleEnumTypes( rightExpressionContext ) ) != null ) { left = (SqmExpression) leftExpressionContext.accept( this ); right = resolveEnumShorthandLiteral( rightExpressionContext, - possibleEnumValues, - left.getJavaType() + getPossibleEnumValue( rightExpressionContext ), + left.getJavaTypeName(), + possibleEnumTypes ); } else { @@ -2570,12 +2574,13 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem ); } - private SqmExpression resolveEnumShorthandLiteral(HqlParser.ExpressionContext expressionContext, Map, Enum> possibleEnumValues, Class enumType) { - final Enum enumValue; - if ( possibleEnumValues != null && ( enumValue = possibleEnumValues.get( enumType ) ) != null ) { + private SqmExpression resolveEnumShorthandLiteral( + HqlParser.ExpressionContext expressionContext, + String enumValue, String enumType, Set enumTypes) { + if ( enumValue != null && enumType != null && enumTypes.contains(enumType) ) { DotIdentifierConsumer dotIdentifierConsumer = dotIdentifierConsumerStack.getCurrent(); - dotIdentifierConsumer.consumeIdentifier( enumValue.getClass().getName(), true, false ); - dotIdentifierConsumer.consumeIdentifier( enumValue.name(), false, true ); + dotIdentifierConsumer.consumeIdentifier( enumType, true, false ); + dotIdentifierConsumer.consumeIdentifier( enumValue, false, true ); return (SqmExpression) dotIdentifierConsumerStack.getCurrent().getConsumedPart(); } else { @@ -2583,7 +2588,7 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem } } - private Map, Enum> getPossibleEnumValues(HqlParser.ExpressionContext expressionContext) { + private Set getPossibleEnumTypes(HqlParser.ExpressionContext expressionContext) { ParseTree ctx; // Traverse the expression structure according to the grammar if ( expressionContext instanceof HqlParser.BarePrimaryExpressionContext && expressionContext.getChildCount() == 1 ) { @@ -2597,7 +2602,29 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem ctx = ctx.getChild( 0 ); if ( ctx instanceof HqlParser.SimplePathContext ) { - return creationContext.getJpaMetamodel().getAllowedEnumLiteralTexts().get( ctx.getText() ); + return creationContext.getJpaMetamodel().getAllowedEnumLiteralTexts( ctx.getText() ); + } + } + } + + return null; + } + + private String getPossibleEnumValue(HqlParser.ExpressionContext expressionContext) { + ParseTree ctx; + // Traverse the expression structure according to the grammar + if ( expressionContext instanceof HqlParser.BarePrimaryExpressionContext && expressionContext.getChildCount() == 1 ) { + ctx = expressionContext.getChild( 0 ); + + while ( ctx instanceof HqlParser.PrimaryExpressionContext && ctx.getChildCount() == 1 ) { + ctx = ctx.getChild( 0 ); + } + + if ( ctx instanceof HqlParser.GeneralPathFragmentContext && ctx.getChildCount() == 1 ) { + ctx = ctx.getChild( 0 ); + + if ( ctx instanceof HqlParser.SimplePathContext ) { + return ctx.getText(); } } } @@ -2689,8 +2716,8 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem final HqlParser.ExplicitTupleInListContext tupleExpressionListContext = (HqlParser.ExplicitTupleInListContext) inListContext; final int size = tupleExpressionListContext.getChildCount(); final int estimatedSize = size >> 1; - final Class testExpressionJavaType = testExpression.getJavaType(); - final boolean isEnum = testExpressionJavaType != null && testExpressionJavaType.isEnum(); + final String testExpressionJavaType = testExpression.getJavaTypeName(); + final boolean isEnum = testExpression.isEnum(); // Multivalued bindings are only allowed if there is a single list item, hence size 3 (LP, RP and param) parameterDeclarationContextStack.push( () -> size == 3 ); try { @@ -2700,14 +2727,15 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem if ( parseTree instanceof HqlParser.ExpressionOrPredicateContext ) { final ParseTree child = parseTree.getChild( 0 ); final HqlParser.ExpressionContext expressionContext; - final Map, Enum> possibleEnumValues; + final Set possibleEnumTypes; if ( isEnum && child instanceof HqlParser.ExpressionContext - && ( possibleEnumValues = getPossibleEnumValues( expressionContext = (HqlParser.ExpressionContext) child ) ) != null ) { + && ( possibleEnumTypes = getPossibleEnumTypes( expressionContext = (HqlParser.ExpressionContext) child ) ) != null ) { listExpressions.add( resolveEnumShorthandLiteral( expressionContext, - possibleEnumValues, - testExpressionJavaType + getPossibleEnumValue( expressionContext ), + testExpressionJavaType, + possibleEnumTypes ) ); } @@ -3229,9 +3257,14 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem final HqlParser.SimpleCaseWhenContext simpleCaseWhenContext = ctx.simpleCaseWhen( i ); final HqlParser.ExpressionContext testExpression = simpleCaseWhenContext.expression(); final SqmExpression test; - final Map, Enum> possibleEnumValues; - if ( ( possibleEnumValues = getPossibleEnumValues( testExpression ) ) != null ) { - test = resolveEnumShorthandLiteral( testExpression, possibleEnumValues, expression.getJavaType() ); + final Set possibleEnumTypes; + if ( ( possibleEnumTypes = getPossibleEnumTypes( testExpression ) ) != null ) { + test = resolveEnumShorthandLiteral( + testExpression, + getPossibleEnumValue( testExpression ), + expression.getJavaTypeName(), + possibleEnumTypes + ); } else { test = (SqmExpression) testExpression.accept( this ); diff --git a/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/eg/Library.java b/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/eg/Library.java index 22a32e3fd8..7a0c1fb478 100644 --- a/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/eg/Library.java +++ b/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/eg/Library.java @@ -105,4 +105,7 @@ public interface Library { @Query("where type = org.hibernate.processor.test.data.eg.Type.Magazine") List magazines(); + + @Query("where type = Journal") + List journals(); } diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/validation/MockSessionFactory.java b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/validation/MockSessionFactory.java index 9e5bcb3cd4..7ee4d912b6 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/validation/MockSessionFactory.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/validation/MockSessionFactory.java @@ -846,12 +846,21 @@ public abstract class MockSessionFactory return null; } + @Override + public Set getAllowedEnumLiteralTexts(String enumValue) { + return MockSessionFactory.this.getAllowedEnumLiteralTexts().get(enumValue); + } + @Override public JpaCompliance getJpaCompliance() { return jpaCompliance; } } + Map> getAllowedEnumLiteralTexts() { + return emptyMap(); + } + class MockMappedDomainType extends MappedSuperclassTypeImpl{ public MockMappedDomainType(String typeName) { super(typeName, false, true, false, null, null, metamodel.getJpaMetamodel()); diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/validation/ProcessorSessionFactory.java b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/validation/ProcessorSessionFactory.java index 892cc1db28..e0d7f45eaf 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/validation/ProcessorSessionFactory.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/validation/ProcessorSessionFactory.java @@ -43,8 +43,10 @@ import javax.lang.model.util.Types; import java.beans.Introspector; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import static java.util.Arrays.stream; import static org.hibernate.internal.util.StringHelper.qualify; @@ -194,6 +196,30 @@ public abstract class ProcessorSessionFactory extends MockSessionFactory { : IntegerJdbcType.INSTANCE; } + final Map> result = new HashMap<>(); + + @Override + Map> getAllowedEnumLiteralTexts() { + //TODO: elementUtil.getAllModuleElements(); + if ( result.isEmpty() ) { + for (Element mod : elementUtil.getModuleElement("").getEnclosedElements()) { + for (Element element : mod.getEnclosedElements()) { + if (element.getKind() == ElementKind.ENUM) { + TypeElement typeElement = (TypeElement) element; + for (Element member : element.getEnclosedElements()) { + if (member.getKind() == ElementKind.ENUM_CONSTANT) { + String name = member.getSimpleName().toString(); + result.computeIfAbsent( name, s -> new HashSet<>() ) + .add( typeElement.getQualifiedName().toString() ); + } + } + } + } + } + } + return result; + } + private static Type elementCollectionElementType(TypeElement elementType, String role, String path, AccessType defaultAccessType) {