handle unqualified enum values in @Query
Signed-off-by: Gavin King <gavin@hibernate.org>
This commit is contained in:
parent
f0c9d4ec4c
commit
ce317960fc
|
@ -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);
|
||||
|
||||
|
|
|
@ -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() );
|
||||
}
|
||||
}
|
||||
} );
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in New Issue