Skip to content

Commit

Permalink
spec-compliant inference of entity type in @query
Browse files Browse the repository at this point in the history
Signed-off-by: Gavin King <gavin@hibernate.org>
  • Loading branch information
gavinking committed Mar 27, 2024
1 parent f5ddee4 commit 2955e0b
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -561,15 +561,15 @@ else if ( c == character ) {
return count;
}

public static boolean isNotEmpty(String string) {
public static boolean isNotEmpty(@Nullable String string) {
return string != null && !string.isEmpty();
}

public static boolean isEmpty(String string) {
public static boolean isEmpty(@Nullable String string) {
return string == null || string.isEmpty();
}

public static boolean isBlank(String string) {
public static boolean isBlank(@Nullable String string) {
return string == null || string.isBlank();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import jakarta.data.repository.CrudRepository;
import jakarta.data.repository.Find;
import jakarta.data.repository.Query;
import jakarta.data.repository.Repository;
import jakarta.transaction.Transactional;

Expand All @@ -12,4 +13,7 @@ public interface Bookshop extends CrudRepository<Book,String> {
@Find
@Transactional
List<Book> byPublisher(String publisher_name);

@Query("select isbn where title like ?1 order by isbn")
String[] ssns(String title);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@
*/
package org.hibernate.processor.annotation;

import org.antlr.v4.runtime.Token;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.hibernate.AssertionFailure;
import org.hibernate.grammars.hql.HqlLexer;
import org.hibernate.processor.Context;
import org.hibernate.processor.ImportContextImpl;
import org.hibernate.processor.ProcessLaterException;
Expand All @@ -23,6 +25,7 @@
import org.hibernate.query.criteria.JpaEntityJoin;
import org.hibernate.query.criteria.JpaRoot;
import org.hibernate.query.criteria.JpaSelection;
import org.hibernate.query.hql.internal.HqlParseTreeBuilder;
import org.hibernate.query.sql.internal.ParameterParser;
import org.hibernate.query.sql.spi.ParameterRecognizer;
import org.hibernate.query.sqm.SqmExpressible;
Expand Down Expand Up @@ -66,6 +69,12 @@
import static java.util.stream.Collectors.toList;
import static javax.lang.model.util.ElementFilter.fieldsIn;
import static javax.lang.model.util.ElementFilter.methodsIn;
import static org.hibernate.grammars.hql.HqlLexer.FROM;
import static org.hibernate.grammars.hql.HqlLexer.GROUP;
import static org.hibernate.grammars.hql.HqlLexer.HAVING;
import static org.hibernate.grammars.hql.HqlLexer.ORDER;
import static org.hibernate.grammars.hql.HqlLexer.WHERE;
import static org.hibernate.internal.util.StringHelper.isNotEmpty;
import static org.hibernate.internal.util.StringHelper.qualify;
import static org.hibernate.processor.annotation.AbstractQueryMethod.isSessionParameter;
import static org.hibernate.processor.annotation.AbstractQueryMethod.isSpecialParam;
Expand Down Expand Up @@ -2077,13 +2086,6 @@ private void addQueryMethod(
final List<String> paramNames = parameterNames( method );
final List<String> paramTypes = parameterTypes( method );

if ( isNative ) {
validateSql( method, mirror, queryString, paramNames, value );
}
else {
validateHql( method, returnType, mirror, value, queryString, paramNames, paramTypes );
}

// now check that the query has a parameter for every method parameter
checkParameters( method, returnType, paramNames, paramTypes, mirror, value, queryString );

Expand All @@ -2093,11 +2095,21 @@ private void addQueryMethod(
? emptyList()
: orderByList( method, (TypeElement) resultType.asElement() );

final String processedQuery;
if ( isNative ) {
processedQuery = queryString;
validateSql( method, mirror, processedQuery, paramNames, value );
}
else {
processedQuery = addFromClauseIfNecessary( queryString, implicitEntityName(resultType) );
validateHql( method, returnType, mirror, value, processedQuery, paramNames, paramTypes );
}

final QueryMethod attribute =
new QueryMethod(
this, method,
method.getSimpleName().toString(),
queryString,
processedQuery,
returnType == null ? null : returnType.toString(),
containerTypeName,
paramNames,
Expand All @@ -2116,6 +2128,51 @@ private void addQueryMethod(
}
}

private @Nullable String implicitEntityName(@Nullable DeclaredType resultType) {
if ( resultType != null && hasAnnotation(resultType.asElement(), ENTITY) ) {
final AnnotationMirror annotation =
getAnnotationMirror(resultType.asElement(), ENTITY);
if ( annotation == null ) {
throw new AssertionFailure("@Entity annotation should not be missing");
}
final String name = (String) getAnnotationValue(annotation, "name");
return isNotEmpty(name) ? resultType.asElement().getSimpleName().toString() : name;
}
else if ( primaryEntity != null ) {
return primaryEntity.getSimpleName().toString();
}
else {
return null;
}
}

private static String addFromClauseIfNecessary(String hql, @Nullable String entityType) {
if ( entityType == null ) {
return hql;
}
else if ( isInsertUpdateDelete(hql) ) {
return hql;
}
else {
final HqlLexer hqlLexer = HqlParseTreeBuilder.INSTANCE.buildHqlLexer( hql );
final List<? extends Token> allTokens = hqlLexer.getAllTokens();
for (Token token : allTokens) {
switch ( token.getType() ) {
case FROM:
return hql;
case WHERE:
case HAVING:
case GROUP:
case ORDER:
return new StringBuilder(hql)
.insert(token.getStartIndex(), "from " + entityType + " ")
.toString();
}
}
return hql + " from " + entityType;
}
}

private @Nullable DeclaredType resultType(
ExecutableElement method, @Nullable TypeMirror returnType, AnnotationMirror mirror, AnnotationValue value) {
if ( returnType != null && returnType.getKind() == TypeKind.DECLARED ) {
Expand Down

0 comments on commit 2955e0b

Please sign in to comment.