make fully-qualified enum literals in @Query pass the validation

JD examples and TCK require this, though it's not really correct

Signed-off-by: Gavin King <gavin@hibernate.org>
This commit is contained in:
Gavin King 2024-03-28 16:04:44 +01:00
parent fe4a3fbaf3
commit f0c9d4ec4c
11 changed files with 107 additions and 52 deletions

View File

@ -19,6 +19,7 @@ import org.hibernate.graph.spi.RootGraphImplementor;
import org.hibernate.jpa.spi.JpaCompliance; import org.hibernate.jpa.spi.JpaCompliance;
import org.hibernate.metamodel.MappingMetamodel; import org.hibernate.metamodel.MappingMetamodel;
import org.hibernate.service.ServiceRegistry; import org.hibernate.service.ServiceRegistry;
import org.hibernate.type.descriptor.java.EnumJavaType;
import org.hibernate.type.spi.TypeConfiguration; import org.hibernate.type.spi.TypeConfiguration;
/** /**
@ -85,6 +86,10 @@ public interface JpaMetamodel extends Metamodel {
*/ */
Map<String, Map<Class<?>, Enum<?>>> getAllowedEnumLiteralTexts(); Map<String, Map<Class<?>, Enum<?>>> getAllowedEnumLiteralTexts();
EnumJavaType<?> getEnumType(String prefix);
<E extends Enum<E>> E enumValue(EnumJavaType<E> enumType, String terminal);
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Covariant returns // Covariant returns

View File

@ -243,6 +243,28 @@ public class JpaMetamodelImpl implements JpaMetamodelImplementor, Serializable {
return allowedEnumLiteralTexts; return allowedEnumLiteralTexts;
} }
@Override
public EnumJavaType<?> getEnumType(String prefix) {
try {
final Class<?> namedClass =
getServiceRegistry().requireService( ClassLoaderService.class )
.classForName( prefix );
if ( namedClass != null && namedClass.isEnum() ) {
return (EnumJavaType) getTypeConfiguration()
.getJavaTypeRegistry()
.resolveDescriptor(namedClass);
}
}
catch (Exception ignore) {
}
return null;
}
@Override
public <E extends Enum<E>> E enumValue(EnumJavaType<E> enumType, String terminal) {
return Enum.valueOf( enumType.getJavaTypeClass(), terminal );
}
@Override @Override
public <T> void addNamedEntityGraph(String graphName, RootGraphImplementor<T> entityGraph) { public <T> void addNamedEntityGraph(String graphName, RootGraphImplementor<T> entityGraph) {
final EntityGraph<?> old = entityGraphMap.put( final EntityGraph<?> old = entityGraphMap.put(

View File

@ -72,6 +72,7 @@ import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.type.BasicType; import org.hibernate.type.BasicType;
import org.hibernate.type.ComponentType; import org.hibernate.type.ComponentType;
import org.hibernate.type.Type; import org.hibernate.type.Type;
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.spi.JavaTypeRegistry; import org.hibernate.type.descriptor.java.spi.JavaTypeRegistry;
import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.JdbcType;
@ -523,6 +524,16 @@ public class MappingMetamodelImpl extends QueryParameterBindingTypeResolverImpl
return jpaMetamodel.getAllowedEnumLiteralTexts(); return jpaMetamodel.getAllowedEnumLiteralTexts();
} }
@Override
public EnumJavaType<?> getEnumType(String className) {
return jpaMetamodel.getEnumType(className);
}
@Override
public <E extends Enum<E>> E enumValue(EnumJavaType<E> enumType, String terminal) {
return jpaMetamodel.enumValue(enumType, terminal);
}
@Override @Override
public String[] getImplementors(String className) throws MappingException { public String[] getImplementors(String className) throws MappingException {
// computeIfAbsent() can be a contention point and we expect all the values to be in the map at some point so // computeIfAbsent() can be a contention point and we expect all the values to be in the map at some point so

View File

@ -10,12 +10,14 @@ import java.lang.reflect.Field;
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
import org.hibernate.metamodel.model.domain.EntityDomainType; import org.hibernate.metamodel.model.domain.EntityDomainType;
import org.hibernate.metamodel.model.domain.spi.JpaMetamodelImplementor;
import org.hibernate.query.SemanticException; import org.hibernate.query.SemanticException;
import org.hibernate.query.hql.HqlLogging; import org.hibernate.query.hql.HqlLogging;
import org.hibernate.query.hql.spi.DotIdentifierConsumer; import org.hibernate.query.hql.spi.DotIdentifierConsumer;
import org.hibernate.query.hql.spi.SemanticPathPart; import org.hibernate.query.hql.spi.SemanticPathPart;
import org.hibernate.query.hql.spi.SqmCreationState; import org.hibernate.query.hql.spi.SqmCreationState;
import org.hibernate.query.hql.spi.SqmPathRegistry; import org.hibernate.query.hql.spi.SqmPathRegistry;
import org.hibernate.query.sqm.NodeBuilder;
import org.hibernate.query.sqm.function.SqmFunctionDescriptor; import org.hibernate.query.sqm.function.SqmFunctionDescriptor;
import org.hibernate.query.sqm.spi.SqmCreationContext; import org.hibernate.query.sqm.spi.SqmCreationContext;
import org.hibernate.query.sqm.tree.domain.SqmPath; import org.hibernate.query.sqm.tree.domain.SqmPath;
@ -26,7 +28,6 @@ import org.hibernate.query.sqm.tree.expression.SqmLiteralEntityType;
import org.hibernate.query.sqm.tree.from.SqmFrom; import org.hibernate.query.sqm.tree.from.SqmFrom;
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.spi.JavaTypeRegistry;
/** /**
* @asciidoc * @asciidoc
@ -177,17 +178,20 @@ public class BasicDotIdentifierConsumer implements DotIdentifierConsumer {
} }
final String path = pathSoFar.toString(); final String path = pathSoFar.toString();
final String importableName = creationContext.getJpaMetamodel().qualifyImportableName( path ); final JpaMetamodelImplementor jpaMetamodel = creationContext.getJpaMetamodel();
final String importableName = jpaMetamodel.qualifyImportableName( path );
final NodeBuilder nodeBuilder = creationContext.getNodeBuilder();
if ( importableName != null ) { if ( importableName != null ) {
final EntityDomainType<?> entityDomainType = creationContext.getJpaMetamodel().entity( importableName ); final EntityDomainType<?> entityDomainType = jpaMetamodel.entity( importableName );
if ( entityDomainType != null ) { if ( entityDomainType != null ) {
return new SqmLiteralEntityType( entityDomainType, creationContext.getNodeBuilder() ); return new SqmLiteralEntityType( entityDomainType, nodeBuilder );
} }
} }
final SqmFunctionDescriptor functionDescriptor = creationContext.getQueryEngine() final SqmFunctionDescriptor functionDescriptor =
.getSqmFunctionRegistry() creationContext.getQueryEngine()
.findFunctionDescriptor( path ); .getSqmFunctionRegistry()
.findFunctionDescriptor( path );
if ( functionDescriptor != null ) { if ( functionDescriptor != null ) {
return functionDescriptor.generateSqmExpression( return functionDescriptor.generateSqmExpression(
null, null,
@ -195,55 +199,37 @@ public class BasicDotIdentifierConsumer implements DotIdentifierConsumer {
); );
} }
// // see if it is a Class name...
// try {
// final Class<?> namedClass = creationState.getCreationContext()
// .getServiceRegistry()
// .getService( ClassLoaderService.class )
// .classForName( pathSoFar );
// if ( namedClass != null ) {
// return new
// }
// }
// catch (Exception ignore) {
// }
// see if it is a named field/enum reference // see if it is a named field/enum reference
final int splitPosition = path.lastIndexOf( '.' ); final int splitPosition = path.lastIndexOf( '.' );
if ( splitPosition > 0 ) { if ( splitPosition > 0 ) {
final String prefix = path.substring( 0, splitPosition ); final String prefix = path.substring( 0, splitPosition );
final String terminal = path.substring( splitPosition + 1 ); final String terminal = path.substring( splitPosition + 1 );
//TODO: try interpreting paths of form foo.bar.Foo.Bar as foo.bar.Foo$Bar //TODO: try interpreting paths of form foo.bar.Foo.Bar as foo.bar.Foo$Bar
final EnumJavaType<?> enumType = jpaMetamodel.getEnumType(prefix);
if ( enumType != null ) {
return new SqmEnumLiteral(
jpaMetamodel.enumValue(enumType, terminal),
enumType,
terminal,
nodeBuilder
);
}
try { try {
final Class<?> namedClass = creationContext final Class<?> namedClass =
.getServiceRegistry() creationContext.getServiceRegistry()
.requireService( ClassLoaderService.class ) .requireService( ClassLoaderService.class )
.classForName( prefix ); .classForName( prefix );
if ( namedClass != null ) { if ( namedClass != null ) {
final JavaTypeRegistry javaTypeRegistry = creationContext.getJpaMetamodel() final Field referencedField = namedClass.getDeclaredField( terminal );
.getTypeConfiguration() if ( referencedField != null ) {
.getJavaTypeRegistry(); final JavaType<?> fieldJtd =
jpaMetamodel
if ( namedClass.isEnum() ) { .getTypeConfiguration()
return new SqmEnumLiteral( .getJavaTypeRegistry()
Enum.valueOf( (Class) namedClass, terminal ), .getDescriptor( referencedField.getType() );
(EnumJavaType) javaTypeRegistry.resolveDescriptor( namedClass ), return new SqmFieldLiteral( referencedField, fieldJtd, nodeBuilder);
terminal,
creationContext.getNodeBuilder()
);
}
try {
final Field referencedField = namedClass.getDeclaredField( terminal );
if ( referencedField != null ) {
final JavaType<?> fieldJtd = javaTypeRegistry
.getDescriptor( referencedField.getType() );
//noinspection unchecked
return new SqmFieldLiteral( referencedField, fieldJtd, creationContext.getNodeBuilder() );
}
}
catch (Exception ignore) {
} }
} }
} }

View File

@ -1,4 +1,4 @@
package org.hibernate.processor.test.data; package org.hibernate.processor.test.data.basic;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
import jakarta.persistence.Id; import jakarta.persistence.Id;

View File

@ -1,4 +1,4 @@
package org.hibernate.processor.test.data; package org.hibernate.processor.test.data.basic;
import jakarta.persistence.*; import jakarta.persistence.*;
import org.hibernate.annotations.NaturalId; import org.hibernate.annotations.NaturalId;

View File

@ -1,4 +1,4 @@
package org.hibernate.processor.test.data; package org.hibernate.processor.test.data.basic;
import jakarta.data.Limit; import jakarta.data.Limit;
import jakarta.data.Order; import jakarta.data.Order;
@ -22,7 +22,6 @@ import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream; import java.util.stream.Stream;
@Repository(dataStore = "myds") @Repository(dataStore = "myds")

View File

@ -4,7 +4,7 @@
* License: GNU Lesser General Public License (LGPL), version 2.1 or later. * License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>. * See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/ */
package org.hibernate.processor.test.data; package org.hibernate.processor.test.data.basic;
import org.hibernate.processor.test.util.CompilationTest; import org.hibernate.processor.test.util.CompilationTest;
import org.hibernate.processor.test.util.WithClasses; import org.hibernate.processor.test.util.WithClasses;

View File

@ -102,4 +102,7 @@ public interface Library {
@Find @Find
List<Author> authorsByCityAndPostcode(String address_city, String address_postcode); List<Author> authorsByCityAndPostcode(String address_city, String address_postcode);
@Query("where type = org.hibernate.processor.test.data.eg.Type.Magazine")
List<Book> magazines();
} }

View File

@ -108,6 +108,7 @@ import org.hibernate.type.MapType;
import org.hibernate.type.SetType; import org.hibernate.type.SetType;
import org.hibernate.type.SqlTypes; import org.hibernate.type.SqlTypes;
import org.hibernate.type.Type; import org.hibernate.type.Type;
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.spi.UnknownBasicJavaType; import org.hibernate.type.descriptor.java.spi.UnknownBasicJavaType;
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators; import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
@ -283,6 +284,8 @@ public abstract class MockSessionFactory
abstract boolean isClassDefined(String qualifiedName); abstract boolean isClassDefined(String qualifiedName);
abstract boolean isEnum(String className);
abstract boolean isFieldDefined(String qualifiedClassName, String fieldName); abstract boolean isFieldDefined(String qualifiedClassName, String fieldName);
abstract boolean isConstructorDefined(String qualifiedClassName, List<Type> argumentTypes); abstract boolean isConstructorDefined(String qualifiedClassName, List<Type> argumentTypes);
@ -823,6 +826,26 @@ public abstract class MockSessionFactory
throw new UnsupportedOperationException("operation not supported"); throw new UnsupportedOperationException("operation not supported");
} }
@Override
public EnumJavaType<?> getEnumType(String className) {
if ( isEnum(className) ) {
return new EnumJavaType( Enum.class ) {
@Override
public String getTypeName() {
return className;
}
};
}
else {
return null;
}
}
@Override
public <E extends Enum<E>> E enumValue(EnumJavaType<E> enumType, String terminal) {
return null;
}
@Override @Override
public JpaCompliance getJpaCompliance() { public JpaCompliance getJpaCompliance() {
return jpaCompliance; return jpaCompliance;

View File

@ -563,6 +563,12 @@ public abstract class ProcessorSessionFactory extends MockSessionFactory {
} }
} }
@Override
boolean isEnum(String className) {
final TypeElement typeElement = elementUtil.getTypeElement(className);
return typeElement != null && typeElement.getKind() == ElementKind.ENUM;
}
private static boolean isEmbeddableType(TypeElement type) { private static boolean isEmbeddableType(TypeElement type) {
return hasAnnotation(type, "Embeddable"); return hasAnnotation(type, "Embeddable");
} }