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

View File

@ -243,6 +243,28 @@ public class JpaMetamodelImpl implements JpaMetamodelImplementor, Serializable {
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
public <T> void addNamedEntityGraph(String graphName, RootGraphImplementor<T> entityGraph) {
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.ComponentType;
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.spi.JavaTypeRegistry;
import org.hibernate.type.descriptor.jdbc.JdbcType;
@ -523,6 +524,16 @@ public class MappingMetamodelImpl extends QueryParameterBindingTypeResolverImpl
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
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

View File

@ -10,12 +10,14 @@ import java.lang.reflect.Field;
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
import org.hibernate.metamodel.model.domain.EntityDomainType;
import org.hibernate.metamodel.model.domain.spi.JpaMetamodelImplementor;
import org.hibernate.query.SemanticException;
import org.hibernate.query.hql.HqlLogging;
import org.hibernate.query.hql.spi.DotIdentifierConsumer;
import org.hibernate.query.hql.spi.SemanticPathPart;
import org.hibernate.query.hql.spi.SqmCreationState;
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.spi.SqmCreationContext;
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.type.descriptor.java.EnumJavaType;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.java.spi.JavaTypeRegistry;
/**
* @asciidoc
@ -177,17 +178,20 @@ public class BasicDotIdentifierConsumer implements DotIdentifierConsumer {
}
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 ) {
final EntityDomainType<?> entityDomainType = creationContext.getJpaMetamodel().entity( importableName );
final EntityDomainType<?> entityDomainType = jpaMetamodel.entity( importableName );
if ( entityDomainType != null ) {
return new SqmLiteralEntityType( entityDomainType, creationContext.getNodeBuilder() );
return new SqmLiteralEntityType( entityDomainType, nodeBuilder );
}
}
final SqmFunctionDescriptor functionDescriptor = creationContext.getQueryEngine()
.getSqmFunctionRegistry()
.findFunctionDescriptor( path );
final SqmFunctionDescriptor functionDescriptor =
creationContext.getQueryEngine()
.getSqmFunctionRegistry()
.findFunctionDescriptor( path );
if ( functionDescriptor != null ) {
return functionDescriptor.generateSqmExpression(
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
final int splitPosition = path.lastIndexOf( '.' );
if ( splitPosition > 0 ) {
final String prefix = path.substring( 0, splitPosition );
final String terminal = path.substring( splitPosition + 1 );
//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 {
final Class<?> namedClass = creationContext
.getServiceRegistry()
.requireService( ClassLoaderService.class )
.classForName( prefix );
final Class<?> namedClass =
creationContext.getServiceRegistry()
.requireService( ClassLoaderService.class )
.classForName( prefix );
if ( namedClass != null ) {
final JavaTypeRegistry javaTypeRegistry = creationContext.getJpaMetamodel()
.getTypeConfiguration()
.getJavaTypeRegistry();
if ( namedClass.isEnum() ) {
return new SqmEnumLiteral(
Enum.valueOf( (Class) namedClass, terminal ),
(EnumJavaType) javaTypeRegistry.resolveDescriptor( namedClass ),
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) {
final Field referencedField = namedClass.getDeclaredField( terminal );
if ( referencedField != null ) {
final JavaType<?> fieldJtd =
jpaMetamodel
.getTypeConfiguration()
.getJavaTypeRegistry()
.getDescriptor( referencedField.getType() );
return new SqmFieldLiteral( referencedField, fieldJtd, nodeBuilder);
}
}
}

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.Id;

View File

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

View File

@ -4,7 +4,7 @@
* 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>.
*/
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.WithClasses;

View File

@ -102,4 +102,7 @@ public interface Library {
@Find
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.SqlTypes;
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.spi.UnknownBasicJavaType;
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
@ -283,6 +284,8 @@ public abstract class MockSessionFactory
abstract boolean isClassDefined(String qualifiedName);
abstract boolean isEnum(String className);
abstract boolean isFieldDefined(String qualifiedClassName, String fieldName);
abstract boolean isConstructorDefined(String qualifiedClassName, List<Type> argumentTypes);
@ -823,6 +826,26 @@ public abstract class MockSessionFactory
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
public JpaCompliance getJpaCompliance() {
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) {
return hasAnnotation(type, "Embeddable");
}