handle unqualified enum values in @Query

Signed-off-by: Gavin King <gavin@hibernate.org>
This commit is contained in:
Gavin King 2024-03-28 20:01:03 +01:00
parent f0c9d4ec4c
commit ce317960fc
8 changed files with 125 additions and 51 deletions

View File

@ -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<String, Map<Class<?>, Enum<?>>> getAllowedEnumLiteralTexts();
Set<String> getAllowedEnumLiteralTexts(String enumValue);
EnumJavaType<?> getEnumType(String prefix);

View File

@ -91,7 +91,7 @@ public class JpaMetamodelImpl implements JpaMetamodelImplementor, Serializable {
private final Map<Class<?>, ManagedDomainType<?>> jpaManagedTypeMap = new HashMap<>();
private final Set<ManagedDomainType<?>> jpaManagedTypes = new HashSet<>();
private final Set<EmbeddableDomainType<?>> jpaEmbeddables = new HashSet<>();
private final Map<String, Map<Class<?>, Enum<?>>> allowedEnumLiteralTexts = new HashMap<>();
private final Map<String, Set<String>> allowedEnumLiteralTexts = new HashMap<>();
private final transient Map<String, RootGraphImplementor<?>> entityGraphMap = new ConcurrentHashMap<>();
@ -239,8 +239,8 @@ public class JpaMetamodelImpl implements JpaMetamodelImplementor, Serializable {
}
@Override
public Map<String, Map<Class<?>, Enum<?>>> getAllowedEnumLiteralTexts() {
return allowedEnumLiteralTexts;
public Set<String> 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() );
}
}
} );

View File

@ -520,8 +520,8 @@ public class MappingMetamodelImpl extends QueryParameterBindingTypeResolverImpl
}
@Override
public Map<String, Map<Class<?>, Enum<?>>> getAllowedEnumLiteralTexts() {
return jpaMetamodel.getAllowedEnumLiteralTexts();
public Set<String> getAllowedEnumLiteralTexts(String enumValue) {
return jpaMetamodel.getAllowedEnumLiteralTexts(enumValue);
}
@Override

View File

@ -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<T> extends TupleElement<T>, JpaCriteriaNode {
// todo (6.0) : can this signature just return `Class<T>`?
return getJavaTypeDescriptor() == null ? null : getJavaTypeDescriptor().getJavaTypeClass();
}
default String getJavaTypeName() {
return getJavaTypeDescriptor() == null ? null : getJavaTypeDescriptor().getTypeName();
}
default boolean isEnum() {
return getJavaTypeDescriptor() instanceof EnumJavaType;
}
}

View File

@ -586,18 +586,19 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
final Iterator<SqmPath<?>> 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<Class<?>, Enum<?>> possibleEnumValues;
final Set<String> 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<R> extends HqlParserBaseVisitor<Object> implem
public SqmAssignment<?> visitAssignment(HqlParser.AssignmentContext ctx) {
//noinspection unchecked
final SqmPath<Object> targetPath = (SqmPath<Object>) 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<Class<?>, Enum<?>> possibleEnumValues;
final Set<String> 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<R> extends HqlParserBaseVisitor<Object> implem
HqlParser.ExpressionContext rightExpressionContext) {
final SqmExpression<?> right;
final SqmExpression<?> left;
Map<Class<?>, Enum<?>> possibleEnumValues;
if ( ( possibleEnumValues = getPossibleEnumValues( leftExpressionContext ) ) != null ) {
Set<String> 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<R> extends HqlParserBaseVisitor<Object> implem
);
}
private SqmExpression<?> resolveEnumShorthandLiteral(HqlParser.ExpressionContext expressionContext, Map<Class<?>, 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<String> 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<R> extends HqlParserBaseVisitor<Object> implem
}
}
private Map<Class<?>, Enum<?>> getPossibleEnumValues(HqlParser.ExpressionContext expressionContext) {
private Set<String> 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<R> extends HqlParserBaseVisitor<Object> 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<R> extends HqlParserBaseVisitor<Object> 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<R> extends HqlParserBaseVisitor<Object> implem
if ( parseTree instanceof HqlParser.ExpressionOrPredicateContext ) {
final ParseTree child = parseTree.getChild( 0 );
final HqlParser.ExpressionContext expressionContext;
final Map<Class<?>, Enum<?>> possibleEnumValues;
final Set<String> 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<R> extends HqlParserBaseVisitor<Object> implem
final HqlParser.SimpleCaseWhenContext simpleCaseWhenContext = ctx.simpleCaseWhen( i );
final HqlParser.ExpressionContext testExpression = simpleCaseWhenContext.expression();
final SqmExpression<?> test;
final Map<Class<?>, Enum<?>> possibleEnumValues;
if ( ( possibleEnumValues = getPossibleEnumValues( testExpression ) ) != null ) {
test = resolveEnumShorthandLiteral( testExpression, possibleEnumValues, expression.getJavaType() );
final Set<String> possibleEnumTypes;
if ( ( possibleEnumTypes = getPossibleEnumTypes( testExpression ) ) != null ) {
test = resolveEnumShorthandLiteral(
testExpression,
getPossibleEnumValue( testExpression ),
expression.getJavaTypeName(),
possibleEnumTypes
);
}
else {
test = (SqmExpression<?>) testExpression.accept( this );

View File

@ -105,4 +105,7 @@ public interface Library {
@Query("where type = org.hibernate.processor.test.data.eg.Type.Magazine")
List<Book> magazines();
@Query("where type = Journal")
List<Book> journals();
}

View File

@ -846,12 +846,21 @@ public abstract class MockSessionFactory
return null;
}
@Override
public Set<String> getAllowedEnumLiteralTexts(String enumValue) {
return MockSessionFactory.this.getAllowedEnumLiteralTexts().get(enumValue);
}
@Override
public JpaCompliance getJpaCompliance() {
return jpaCompliance;
}
}
Map<String, Set<String>> getAllowedEnumLiteralTexts() {
return emptyMap();
}
class MockMappedDomainType<X> extends MappedSuperclassTypeImpl<X>{
public MockMappedDomainType(String typeName) {
super(typeName, false, true, false, null, null, metamodel.getJpaMetamodel());

View File

@ -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<String, Set<String>> result = new HashMap<>();
@Override
Map<String, Set<String>> 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) {