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; package org.hibernate.metamodel.model.domain;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import jakarta.persistence.metamodel.EmbeddableType; import jakarta.persistence.metamodel.EmbeddableType;
import jakarta.persistence.metamodel.EntityType; import jakarta.persistence.metamodel.EntityType;
@ -79,12 +78,7 @@ public interface JpaMetamodel extends Metamodel {
String qualifyImportableName(String queryName); String qualifyImportableName(String queryName);
/** Set<String> getAllowedEnumLiteralTexts(String enumValue);
* 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();
EnumJavaType<?> getEnumType(String prefix); 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 Map<Class<?>, ManagedDomainType<?>> jpaManagedTypeMap = new HashMap<>();
private final Set<ManagedDomainType<?>> jpaManagedTypes = new HashSet<>(); private final Set<ManagedDomainType<?>> jpaManagedTypes = new HashSet<>();
private final Set<EmbeddableDomainType<?>> jpaEmbeddables = 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<>(); private final transient Map<String, RootGraphImplementor<?>> entityGraphMap = new ConcurrentHashMap<>();
@ -239,8 +239,8 @@ public class JpaMetamodelImpl implements JpaMetamodelImplementor, Serializable {
} }
@Override @Override
public Map<String, Map<Class<?>, Enum<?>>> getAllowedEnumLiteralTexts() { public Set<String> getAllowedEnumLiteralTexts(String enumValue) {
return allowedEnumLiteralTexts; return allowedEnumLiteralTexts.get(enumValue);
} }
@Override @Override
@ -604,13 +604,13 @@ public class JpaMetamodelImpl implements JpaMetamodelImplementor, Serializable {
final Enum<?>[] enumConstants = enumJavaClass.getEnumConstants(); final Enum<?>[] enumConstants = enumJavaClass.getEnumConstants();
for ( Enum<?> enumConstant : enumConstants ) { for ( Enum<?> enumConstant : enumConstants ) {
allowedEnumLiteralTexts allowedEnumLiteralTexts
.computeIfAbsent( enumConstant.name(), (s) -> new HashMap<>() ) .computeIfAbsent( enumConstant.name(), s -> new HashSet<>() )
.put( enumJavaClass, enumConstant ); .add( enumJavaClass.getName() );
final String simpleQualifiedName = enumJavaClass.getSimpleName() + "." + enumConstant.name(); final String simpleQualifiedName = enumJavaClass.getSimpleName() + "." + enumConstant.name();
allowedEnumLiteralTexts allowedEnumLiteralTexts
.computeIfAbsent( simpleQualifiedName, (s) -> new HashMap<>() ) .computeIfAbsent( simpleQualifiedName, s -> new HashSet<>() )
.put( enumJavaClass, enumConstant ); .add( enumJavaClass.getName() );
} }
} }
} ); } );

View File

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

View File

@ -8,6 +8,7 @@ package org.hibernate.query.criteria;
import jakarta.persistence.TupleElement; import jakarta.persistence.TupleElement;
import org.hibernate.type.descriptor.java.EnumJavaType;
import org.hibernate.type.descriptor.java.JavaType; 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>`? // todo (6.0) : can this signature just return `Class<T>`?
return getJavaTypeDescriptor() == null ? null : getJavaTypeDescriptor().getJavaTypeClass(); 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(); final Iterator<SqmPath<?>> iterator = insertStatement.getInsertionTargetPaths().iterator();
for ( int j = 1; j < values.getChildCount(); j += 2 ) { for ( int j = 1; j < values.getChildCount(); j += 2 ) {
final SqmPath<?> targetPath = iterator.next(); final SqmPath<?> targetPath = iterator.next();
final Class<?> targetPathJavaType = targetPath.getJavaType(); final String targetPathJavaType = targetPath.getJavaTypeName();
final boolean isEnum = targetPathJavaType != null && targetPathJavaType.isEnum(); final boolean isEnum = targetPath.isEnum();
final ParseTree valuesContext = values.getChild( j ); final ParseTree valuesContext = values.getChild( j );
final HqlParser.ExpressionContext expressionContext; final HqlParser.ExpressionContext expressionContext;
final Map<Class<?>, Enum<?>> possibleEnumValues; final Set<String> possibleEnumTypes;
final SqmExpression<?> value; final SqmExpression<?> value;
if ( isEnum && valuesContext.getChild( 0 ) instanceof HqlParser.ExpressionContext 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( value = resolveEnumShorthandLiteral(
expressionContext, expressionContext,
possibleEnumValues, getPossibleEnumValue( expressionContext ),
targetPathJavaType targetPathJavaType,
possibleEnumTypes
); );
} }
else { else {
@ -692,18 +693,19 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
public SqmAssignment<?> visitAssignment(HqlParser.AssignmentContext ctx) { public SqmAssignment<?> visitAssignment(HqlParser.AssignmentContext ctx) {
//noinspection unchecked //noinspection unchecked
final SqmPath<Object> targetPath = (SqmPath<Object>) consumeDomainPath( ctx.simplePath() ); final SqmPath<Object> targetPath = (SqmPath<Object>) consumeDomainPath( ctx.simplePath() );
final Class<?> targetPathJavaType = targetPath.getJavaType(); final String targetPathJavaType = targetPath.getJavaTypeName();
final boolean isEnum = targetPathJavaType != null && targetPathJavaType.isEnum(); final boolean isEnum = targetPath.isEnum();
final ParseTree rightSide = ctx.getChild( 2 ); final ParseTree rightSide = ctx.getChild( 2 );
final HqlParser.ExpressionContext expressionContext; final HqlParser.ExpressionContext expressionContext;
final Map<Class<?>, Enum<?>> possibleEnumValues; final Set<String> possibleEnumValues;
final SqmExpression<?> value; final SqmExpression<?> value;
if ( isEnum && rightSide.getChild( 0 ) instanceof HqlParser.ExpressionContext 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( value = resolveEnumShorthandLiteral(
expressionContext, expressionContext,
possibleEnumValues, getPossibleEnumValue( expressionContext ),
targetPathJavaType targetPathJavaType,
possibleEnumValues
); );
} }
else { else {
@ -2518,21 +2520,23 @@ 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;
Map<Class<?>, Enum<?>> possibleEnumValues; Set<String> possibleEnumTypes;
if ( ( possibleEnumValues = getPossibleEnumValues( leftExpressionContext ) ) != null ) { if ( ( possibleEnumTypes = getPossibleEnumTypes( leftExpressionContext ) ) != null ) {
right = (SqmExpression<?>) rightExpressionContext.accept( this ); right = (SqmExpression<?>) rightExpressionContext.accept( this );
left = resolveEnumShorthandLiteral( left = resolveEnumShorthandLiteral(
leftExpressionContext, leftExpressionContext,
possibleEnumValues, getPossibleEnumValue( leftExpressionContext ),
right.getJavaType() right.getJavaTypeName(),
possibleEnumTypes
); );
} }
else if ( ( possibleEnumValues = getPossibleEnumValues( rightExpressionContext ) ) != null ) { else if ( ( possibleEnumTypes = getPossibleEnumTypes( rightExpressionContext ) ) != null ) {
left = (SqmExpression<?>) leftExpressionContext.accept( this ); left = (SqmExpression<?>) leftExpressionContext.accept( this );
right = resolveEnumShorthandLiteral( right = resolveEnumShorthandLiteral(
rightExpressionContext, rightExpressionContext,
possibleEnumValues, getPossibleEnumValue( rightExpressionContext ),
left.getJavaType() left.getJavaTypeName(),
possibleEnumTypes
); );
} }
else { 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) { private SqmExpression<?> resolveEnumShorthandLiteral(
final Enum<?> enumValue; HqlParser.ExpressionContext expressionContext,
if ( possibleEnumValues != null && ( enumValue = possibleEnumValues.get( enumType ) ) != null ) { String enumValue, String enumType, Set<String> enumTypes) {
if ( enumValue != null && enumType != null && enumTypes.contains(enumType) ) {
DotIdentifierConsumer dotIdentifierConsumer = dotIdentifierConsumerStack.getCurrent(); DotIdentifierConsumer dotIdentifierConsumer = dotIdentifierConsumerStack.getCurrent();
dotIdentifierConsumer.consumeIdentifier( enumValue.getClass().getName(), true, false ); dotIdentifierConsumer.consumeIdentifier( enumType, true, false );
dotIdentifierConsumer.consumeIdentifier( enumValue.name(), false, true ); dotIdentifierConsumer.consumeIdentifier( enumValue, false, true );
return (SqmExpression<?>) dotIdentifierConsumerStack.getCurrent().getConsumedPart(); return (SqmExpression<?>) dotIdentifierConsumerStack.getCurrent().getConsumedPart();
} }
else { 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; ParseTree ctx;
// Traverse the expression structure according to the grammar // Traverse the expression structure according to the grammar
if ( expressionContext instanceof HqlParser.BarePrimaryExpressionContext && expressionContext.getChildCount() == 1 ) { if ( expressionContext instanceof HqlParser.BarePrimaryExpressionContext && expressionContext.getChildCount() == 1 ) {
@ -2597,7 +2602,29 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
ctx = ctx.getChild( 0 ); ctx = ctx.getChild( 0 );
if ( ctx instanceof HqlParser.SimplePathContext ) { 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 HqlParser.ExplicitTupleInListContext tupleExpressionListContext = (HqlParser.ExplicitTupleInListContext) inListContext;
final int size = tupleExpressionListContext.getChildCount(); final int size = tupleExpressionListContext.getChildCount();
final int estimatedSize = size >> 1; final int estimatedSize = size >> 1;
final Class<?> testExpressionJavaType = testExpression.getJavaType(); final String testExpressionJavaType = testExpression.getJavaTypeName();
final boolean isEnum = testExpressionJavaType != null && testExpressionJavaType.isEnum(); final boolean isEnum = testExpression.isEnum();
// Multivalued bindings are only allowed if there is a single list item, hence size 3 (LP, RP and param) // Multivalued bindings are only allowed if there is a single list item, hence size 3 (LP, RP and param)
parameterDeclarationContextStack.push( () -> size == 3 ); parameterDeclarationContextStack.push( () -> size == 3 );
try { try {
@ -2700,14 +2727,15 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
if ( parseTree instanceof HqlParser.ExpressionOrPredicateContext ) { if ( parseTree instanceof HqlParser.ExpressionOrPredicateContext ) {
final ParseTree child = parseTree.getChild( 0 ); final ParseTree child = parseTree.getChild( 0 );
final HqlParser.ExpressionContext expressionContext; final HqlParser.ExpressionContext expressionContext;
final Map<Class<?>, Enum<?>> possibleEnumValues; final Set<String> possibleEnumTypes;
if ( isEnum && child instanceof HqlParser.ExpressionContext if ( isEnum && child instanceof HqlParser.ExpressionContext
&& ( possibleEnumValues = getPossibleEnumValues( expressionContext = (HqlParser.ExpressionContext) child ) ) != null ) { && ( possibleEnumTypes = getPossibleEnumTypes( expressionContext = (HqlParser.ExpressionContext) child ) ) != null ) {
listExpressions.add( listExpressions.add(
resolveEnumShorthandLiteral( resolveEnumShorthandLiteral(
expressionContext, expressionContext,
possibleEnumValues, getPossibleEnumValue( expressionContext ),
testExpressionJavaType testExpressionJavaType,
possibleEnumTypes
) )
); );
} }
@ -3229,9 +3257,14 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
final HqlParser.SimpleCaseWhenContext simpleCaseWhenContext = ctx.simpleCaseWhen( i ); final HqlParser.SimpleCaseWhenContext simpleCaseWhenContext = ctx.simpleCaseWhen( i );
final HqlParser.ExpressionContext testExpression = simpleCaseWhenContext.expression(); final HqlParser.ExpressionContext testExpression = simpleCaseWhenContext.expression();
final SqmExpression<?> test; final SqmExpression<?> test;
final Map<Class<?>, Enum<?>> possibleEnumValues; final Set<String> possibleEnumTypes;
if ( ( possibleEnumValues = getPossibleEnumValues( testExpression ) ) != null ) { if ( ( possibleEnumTypes = getPossibleEnumTypes( testExpression ) ) != null ) {
test = resolveEnumShorthandLiteral( testExpression, possibleEnumValues, expression.getJavaType() ); test = resolveEnumShorthandLiteral(
testExpression,
getPossibleEnumValue( testExpression ),
expression.getJavaTypeName(),
possibleEnumTypes
);
} }
else { else {
test = (SqmExpression<?>) testExpression.accept( this ); 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") @Query("where type = org.hibernate.processor.test.data.eg.Type.Magazine")
List<Book> magazines(); List<Book> magazines();
@Query("where type = Journal")
List<Book> journals();
} }

View File

@ -846,12 +846,21 @@ public abstract class MockSessionFactory
return null; return null;
} }
@Override
public Set<String> getAllowedEnumLiteralTexts(String enumValue) {
return MockSessionFactory.this.getAllowedEnumLiteralTexts().get(enumValue);
}
@Override @Override
public JpaCompliance getJpaCompliance() { public JpaCompliance getJpaCompliance() {
return jpaCompliance; return jpaCompliance;
} }
} }
Map<String, Set<String>> getAllowedEnumLiteralTexts() {
return emptyMap();
}
class MockMappedDomainType<X> extends MappedSuperclassTypeImpl<X>{ class MockMappedDomainType<X> extends MappedSuperclassTypeImpl<X>{
public MockMappedDomainType(String typeName) { public MockMappedDomainType(String typeName) {
super(typeName, false, true, false, null, null, metamodel.getJpaMetamodel()); 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.beans.Introspector;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import static java.util.Arrays.stream; import static java.util.Arrays.stream;
import static org.hibernate.internal.util.StringHelper.qualify; import static org.hibernate.internal.util.StringHelper.qualify;
@ -194,6 +196,30 @@ public abstract class ProcessorSessionFactory extends MockSessionFactory {
: IntegerJdbcType.INSTANCE; : 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, private static Type elementCollectionElementType(TypeElement elementType,
String role, String path, String role, String path,
AccessType defaultAccessType) { AccessType defaultAccessType) {