support optional 'from' in @HQL query method generation

(as desired by Stef)
This commit is contained in:
Gavin King 2023-07-23 15:15:29 +02:00
parent 8794f86ad2
commit 6c435b02c9
6 changed files with 79 additions and 16 deletions

View File

@ -54,6 +54,7 @@ import org.hibernate.metamodel.model.domain.PluralPersistentAttribute;
import org.hibernate.metamodel.model.domain.SingularPersistentAttribute;
import org.hibernate.metamodel.model.domain.internal.AnyDiscriminatorSqmPath;
import org.hibernate.metamodel.model.domain.internal.EntitySqmPathSource;
import org.hibernate.metamodel.model.domain.spi.JpaMetamodelImplementor;
import org.hibernate.query.NullPrecedence;
import org.hibernate.query.ParameterLabelException;
import org.hibernate.query.PathException;
@ -310,6 +311,7 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
}
private final Class<R> expectedResultType;
private final String expectedResultEntity;
private final SqmCreationOptions creationOptions;
private final SqmCreationContext creationContext;
@ -336,7 +338,23 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
Class<R> expectedResultType,
SqmCreationOptions creationOptions,
SqmCreationContext creationContext) {
this( expectedResultType, null, creationOptions, creationContext );
}
public SemanticQueryBuilder(
String expectedResultEntity,
SqmCreationOptions creationOptions,
SqmCreationContext creationContext) {
this( null, expectedResultEntity, creationOptions, creationContext );
}
private SemanticQueryBuilder(
Class<R> expectedResultType,
String expectedResultEntity,
SqmCreationOptions creationOptions,
SqmCreationContext creationContext) {
this.expectedResultType = expectedResultType;
this.expectedResultEntity = expectedResultEntity;
this.creationOptions = creationOptions;
this.creationContext = creationContext;
this.dotIdentifierConsumerStack = new StandardStack<>(
@ -1153,8 +1171,8 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
}
private SqmFromClause buildInferredFromClause(HqlParser.SelectClauseContext selectClauseContext) {
if ( selectClauseContext != null ) {
// when there's an explicit 'select', we never infer the 'from'
if ( selectClauseContext != null || processingStateStack.depth() > 1 ) {
// when there's an explicit 'select', or in a subquery, we never infer the 'from'
return new SqmFromClause();
}
else {
@ -1166,13 +1184,8 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
}
final SqmFromClause fromClause = new SqmFromClause();
if ( expectedResultType != null && processingStateStack.depth() <= 1 ) {
final EntityDomainType<R> entityDescriptor =
creationContext.getJpaMetamodel().findEntityType( expectedResultType );
if ( entityDescriptor == null ) {
throw new SemanticException( "Query has no 'from' clause, and the result type '"
+ expectedResultType.getName() + "' is not an entity type" );
}
final EntityDomainType<R> entityDescriptor = getResultEntity();
if ( entityDescriptor != null ) {
final SqmRoot<R> sqmRoot =
new SqmRoot<>( entityDescriptor, null, false, creationContext.getNodeBuilder() );
processingStateStack.getCurrent().getPathRegistry().register( sqmRoot );
@ -1182,6 +1195,29 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
}
}
private EntityDomainType<R> getResultEntity() {
final JpaMetamodelImplementor jpaMetamodel = creationContext.getJpaMetamodel();
if ( expectedResultEntity != null ) {
final EntityDomainType<R> entityDescriptor = jpaMetamodel.entity( expectedResultEntity );
if ( entityDescriptor == null ) {
throw new SemanticException("Query has no 'from' clause, and the result type '"
+ expectedResultEntity + "' is not an entity type");
}
return entityDescriptor;
}
else if ( expectedResultType != null ) {
final EntityDomainType<R> entityDescriptor = jpaMetamodel.findEntityType( expectedResultType );
if ( entityDescriptor == null ) {
throw new SemanticException("Query has no 'from' clause, and the result type '"
+ expectedResultType.getSimpleName() + "' is not an entity type");
}
return entityDescriptor;
}
else {
return null;
}
}
protected SqmSelectClause buildInferredSelectClause(SqmFromClause fromClause) {
if ( creationOptions.useStrictJpaCompliance() ) {
throw new StrictJpaComplianceViolation(

View File

@ -94,6 +94,7 @@ public abstract class AnnotationMeta implements Metamodel {
final SqmStatement<?> statement =
Validation.validate(
hql,
null,
true,
// If we are in the scope of @CheckHQL, semantic errors in the
// query result in compilation errors. Otherwise, they only

View File

@ -965,6 +965,7 @@ public class AnnotationMetaEntity extends AnnotationMeta {
final SqmStatement<?> statement =
Validation.validate(
hql,
returnType,
true,
new ErrorHandler( context, method, mirror, value, hql),
ProcessorSessionFactory.create( context.getProcessingEnvironment() )

View File

@ -515,13 +515,13 @@ public abstract class ProcessorSessionFactory extends MockSessionFactory {
return null;
}
private static boolean isMappedClass(TypeElement type) {
static boolean isMappedClass(TypeElement type) {
return hasAnnotation(type, "Entity")
|| hasAnnotation(type, "Embeddable")
|| hasAnnotation(type, "MappedSuperclass");
}
private static boolean isEntity(TypeElement member) {
static boolean isEntity(TypeElement member) {
return member.getKind() == ElementKind.CLASS
// && member.getAnnotation(entityAnnotation)!=null;
&& hasAnnotation(member, "Entity");
@ -668,7 +668,7 @@ public abstract class ProcessorSessionFactory extends MockSessionFactory {
}
}
private static String getEntityName(TypeElement type) {
static String getEntityName(TypeElement type) {
if ( type == null ) {
return null;
}

View File

@ -25,6 +25,14 @@ import org.hibernate.query.sqm.TerminalPathException;
import org.hibernate.query.sqm.tree.SqmStatement;
import org.hibernate.type.descriptor.java.spi.JdbcTypeRecommendationException;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import static org.hibernate.jpamodelgen.validation.ProcessorSessionFactory.getEntityName;
import static org.hibernate.jpamodelgen.validation.ProcessorSessionFactory.isEntity;
/**
* The entry point for HQL validation.
@ -42,14 +50,16 @@ public class Validation {
public static @Nullable SqmStatement<?> validate(
String hql,
@Nullable TypeMirror returnType,
boolean checkTyping,
Handler handler,
SessionFactoryImplementor factory) {
return validate( hql, checkTyping, handler, factory, 0 );
return validate( hql, returnType, checkTyping, handler, factory, 0 );
}
public static @Nullable SqmStatement<?> validate(
String hql,
@Nullable TypeMirror returnType,
boolean checkTyping,
Handler handler,
SessionFactoryImplementor factory,
@ -57,7 +67,7 @@ public class Validation {
try {
final HqlParser.StatementContext statementContext = parseAndCheckSyntax( hql, handler );
if ( checkTyping && handler.getErrorCount() == 0 ) {
return checkTyping( hql, handler, factory, errorOffset, statementContext );
return checkTyping( hql, returnType, handler, factory, errorOffset, statementContext );
}
}
catch (Exception e) {
@ -68,13 +78,13 @@ public class Validation {
private static @Nullable SqmStatement<?> checkTyping(
String hql,
@Nullable TypeMirror returnType,
Handler handler,
SessionFactoryImplementor factory,
int errorOffset,
HqlParser.StatementContext statementContext) {
try {
return new SemanticQueryBuilder<>( Object[].class, () -> false, factory )
.visitStatement( statementContext );
return createSemanticQueryBuilder( returnType, factory ).visitStatement( statementContext );
}
catch ( JdbcTypeRecommendationException ignored ) {
// just squash these for now
@ -89,6 +99,18 @@ public class Validation {
return null;
}
private static SemanticQueryBuilder<?> createSemanticQueryBuilder(
@Nullable TypeMirror returnType, SessionFactoryImplementor factory) {
if ( returnType != null && returnType.getKind() == TypeKind.DECLARED ) {
final DeclaredType declaredType = (DeclaredType) returnType;
final TypeElement typeElement = (TypeElement) declaredType.asElement();
if ( isEntity( typeElement ) ) {
return new SemanticQueryBuilder<>( getEntityName( typeElement ), () -> false, factory );
}
}
return new SemanticQueryBuilder<>( Object[].class, () -> false, factory );
}
private static HqlParser.StatementContext parseAndCheckSyntax(String hql, Handler handler) {
final HqlLexer hqlLexer = HqlParseTreeBuilder.INSTANCE.buildHqlLexer( hql );
final HqlParser hqlParser = HqlParseTreeBuilder.INSTANCE.buildHqlParser( hql, hqlLexer );

View File

@ -42,6 +42,9 @@ public interface Dao {
@Find
SelectionQuery<Book> createBooksSelectionQuery(String title);
@HQL("where title like ?1")
List<Book> findBooksByTitle(String title);
@HQL("from Book where title like ?1")
TypedQuery<Book> findByTitle(String title);