allow @Query methods to return array

This commit is contained in:
Gavin King 2024-03-18 15:13:12 +01:00
parent 813ae8ed9d
commit 4bb5bc60e9
5 changed files with 119 additions and 61 deletions

View File

@ -107,8 +107,14 @@ public interface BookAuthorRepository {
@Query("from Book where title like :title") @Query("from Book where title like :title")
List<Book> books3(String title, Limit limit, Sort<Book>... order); List<Book> books3(String title, Limit limit, Sort<Book>... order);
@Query("from Book where title like :title")
Book[] books4(String title, Sort<Book> sort);
@Query("select title from Book where title like :title order by isbn") @Query("select title from Book where title like :title order by isbn")
Stream<String> titles(String title); Stream<String> titles0(String title);
@Query("select title from Book where title like :title order by isbn")
String[] titles1(String title);
@Query("from Book") @Query("from Book")
List<Book> everyBook1(PageRequest<? super Book> pageRequest); List<Book> everyBook1(PageRequest<? super Book> pageRequest);

View File

@ -139,7 +139,7 @@ public class AnnotationMetaEntity extends AnnotationMeta {
/** /**
* The type of the "session getter" method of a DAO-style repository. * The type of the "session getter" method of a DAO-style repository.
*/ */
private String sessionType = Constants.ENTITY_MANAGER; private String sessionType = ENTITY_MANAGER;
/** /**
* The field or method call to obtain the session * The field or method call to obtain the session
@ -456,35 +456,42 @@ public class AnnotationMetaEntity extends AnnotationMeta {
} }
private boolean isPanacheType(TypeElement type) { private boolean isPanacheType(TypeElement type) {
return isOrmPanacheType( type ) || isReactivePanacheType( type ); return isOrmPanacheType( type )
|| isReactivePanacheType( type );
} }
private boolean isOrmPanacheType(TypeElement type) { private boolean isOrmPanacheType(TypeElement type) {
ProcessingEnvironment processingEnvironment = this.context.getProcessingEnvironment(); final ProcessingEnvironment processingEnvironment = context.getProcessingEnvironment();
TypeElement panacheRepositorySuperType = processingEnvironment.getElementUtils().getTypeElement( Constants.PANACHE_ORM_REPOSITORY_BASE ); final Elements elements = processingEnvironment.getElementUtils();
TypeElement panacheEntitySuperType = processingEnvironment.getElementUtils().getTypeElement( Constants.PANACHE_ORM_ENTITY_BASE ); final TypeElement panacheRepositorySuperType = elements.getTypeElement( PANACHE_ORM_REPOSITORY_BASE );
final TypeElement panacheEntitySuperType = elements.getTypeElement( PANACHE_ORM_ENTITY_BASE );
if ( panacheRepositorySuperType == null || panacheEntitySuperType == null ) { if ( panacheRepositorySuperType == null || panacheEntitySuperType == null ) {
return false; return false;
} }
Types types = processingEnvironment.getTypeUtils(); else {
// check against a raw supertype of PanacheRepositoryBase, which .asType() is not final Types types = processingEnvironment.getTypeUtils();
return processingEnvironment.getTypeUtils().isSubtype( type.asType(), types.getDeclaredType( panacheRepositorySuperType ) )
|| processingEnvironment.getTypeUtils().isSubtype( type.asType(), panacheEntitySuperType.asType() );
}
private boolean isReactivePanacheType(TypeElement type) {
ProcessingEnvironment processingEnvironment = this.context.getProcessingEnvironment();
TypeElement panacheRepositorySuperType = processingEnvironment.getElementUtils().getTypeElement( Constants.PANACHE_REACTIVE_REPOSITORY_BASE );
TypeElement panacheEntitySuperType = processingEnvironment.getElementUtils().getTypeElement( Constants.PANACHE_REACTIVE_ENTITY_BASE );
if ( panacheRepositorySuperType == null || panacheEntitySuperType == null ) {
return false;
}
Types types = processingEnvironment.getTypeUtils();
// check against a raw supertype of PanacheRepositoryBase, which .asType() is not // check against a raw supertype of PanacheRepositoryBase, which .asType() is not
return types.isSubtype( type.asType(), types.getDeclaredType( panacheRepositorySuperType ) ) return types.isSubtype( type.asType(), types.getDeclaredType( panacheRepositorySuperType ) )
|| types.isSubtype( type.asType(), panacheEntitySuperType.asType() ); || types.isSubtype( type.asType(), panacheEntitySuperType.asType() );
} }
}
private boolean isReactivePanacheType(TypeElement type) {
final ProcessingEnvironment processingEnvironment = context.getProcessingEnvironment();
final Elements elements = processingEnvironment.getElementUtils();
final TypeElement panacheRepositorySuperType = elements.getTypeElement( PANACHE_REACTIVE_REPOSITORY_BASE );
final TypeElement panacheEntitySuperType = elements.getTypeElement( PANACHE_REACTIVE_ENTITY_BASE );
if ( panacheRepositorySuperType == null || panacheEntitySuperType == null ) {
return false;
}
else {
final Types types = processingEnvironment.getTypeUtils();
// check against a raw supertype of PanacheRepositoryBase, which .asType() is not
return types.isSubtype( type.asType(), types.getDeclaredType( panacheRepositorySuperType ) )
|| types.isSubtype( type.asType(), panacheEntitySuperType.asType() );
}
}
/** /**
* If there is a session getter method, we generate an instance * If there is a session getter method, we generate an instance
@ -546,7 +553,7 @@ public class AnnotationMetaEntity extends AnnotationMeta {
true true
) )
); );
return Constants.ENTITY_MANAGER; return ENTITY_MANAGER;
} }
else { else {
importType( Constants.QUARKUS_SESSION_OPERATIONS ); importType( Constants.QUARKUS_SESSION_OPERATIONS );
@ -570,10 +577,10 @@ public class AnnotationMetaEntity extends AnnotationMeta {
if ( element.getKind() == ElementKind.INTERFACE ) { if ( element.getKind() == ElementKind.INTERFACE ) {
final TypeElement typeElement = (TypeElement) element; final TypeElement typeElement = (TypeElement) element;
final Name name = typeElement.getQualifiedName(); final Name name = typeElement.getQualifiedName();
return name.contentEquals(Constants.HIB_SESSION) return name.contentEquals(HIB_SESSION)
|| name.contentEquals(Constants.HIB_STATELESS_SESSION) || name.contentEquals(HIB_STATELESS_SESSION)
|| name.contentEquals(Constants.MUTINY_SESSION) || name.contentEquals(MUTINY_SESSION)
|| name.contentEquals(Constants.ENTITY_MANAGER); || name.contentEquals(ENTITY_MANAGER);
} }
} }
} }
@ -655,7 +662,7 @@ public class AnnotationMetaEntity extends AnnotationMeta {
private boolean isPersistent(Element memberOfClass, AccessType membersKind) { private boolean isPersistent(Element memberOfClass, AccessType membersKind) {
return ( entityAccessTypeInfo.getAccessType() == membersKind return ( entityAccessTypeInfo.getAccessType() == membersKind
|| determineAnnotationSpecifiedAccessType( memberOfClass ) != null ) || determineAnnotationSpecifiedAccessType( memberOfClass ) != null )
&& !containsAnnotation( memberOfClass, Constants.TRANSIENT ) && !containsAnnotation( memberOfClass, TRANSIENT )
&& !memberOfClass.getModifiers().contains( Modifier.TRANSIENT ) && !memberOfClass.getModifiers().contains( Modifier.TRANSIENT )
&& !memberOfClass.getModifiers().contains( Modifier.STATIC ) && !memberOfClass.getModifiers().contains( Modifier.STATIC )
&& !( memberOfClass.getKind() == ElementKind.METHOD && !( memberOfClass.getKind() == ElementKind.METHOD
@ -1591,25 +1598,63 @@ public class AnnotationMetaEntity extends AnnotationMeta {
@Nullable TypeElement containerType, @Nullable TypeElement containerType,
AnnotationMirror mirror, AnnotationMirror mirror,
boolean isNative) { boolean isNative) {
final AnnotationValue value = getAnnotationValueRef( mirror, "value" ); final AnnotationValue value = getAnnotationValueRef( mirror, "value" );
if ( value != null ) { if ( value != null ) {
final Object query = value.getValue(); final Object query = value.getValue();
if ( query instanceof String ) { if ( query instanceof String ) {
final String queryString = (String) query; final String queryString = (String) query;
// The following is quite fragile!
final String containerTypeName;
if ( containerType == null ) {
if ( returnType != null && returnType.getKind() == TypeKind.ARRAY ) {
final ArrayType arrayType = (ArrayType) returnType;
final TypeMirror componentType = arrayType.getComponentType();
final TypeElement object = context.getElementUtils().getTypeElement(JAVA_OBJECT);
if ( !context.getTypeUtils().isSameType( object.asType(), componentType ) ) {
returnType = componentType;
containerTypeName = "[]";
}
else {
// assume it's returning a single tuple as Object[]
containerTypeName = null;
}
}
else {
containerTypeName = null;
}
}
else {
containerTypeName = containerType.getQualifiedName().toString();
}
final List<String> paramNames = parameterNames( method ); final List<String> paramNames = parameterNames( method );
final List<String> paramTypes = parameterTypes( 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 );
final String[] sessionType = sessionTypeFromParameters( paramNames, paramTypes ); final String[] sessionType = sessionTypeFromParameters( paramNames, paramTypes );
final DeclaredType resultType = resultType( method, returnType, mirror, value ); final DeclaredType resultType = resultType( method, returnType, mirror, value );
final List<OrderBy> orderBys = resultType == null final List<OrderBy> orderBys = resultType == null
? emptyList() ? emptyList()
: orderByList( method, (TypeElement) resultType.asElement() ); : orderByList( method, (TypeElement) resultType.asElement() );
final QueryMethod attribute = final QueryMethod attribute =
new QueryMethod( new QueryMethod(
this, this,
method.getSimpleName().toString(), method.getSimpleName().toString(),
queryString, queryString,
returnType == null ? null : returnType.toString(), returnType == null ? null : returnType.toString(),
containerType == null ? null : containerType.getQualifiedName().toString(), containerTypeName,
paramNames, paramNames,
paramTypes, paramTypes,
isInsertUpdateDelete( queryString ), isInsertUpdateDelete( queryString ),
@ -1622,16 +1667,6 @@ public class AnnotationMetaEntity extends AnnotationMeta {
jakartaDataRepository jakartaDataRepository
); );
putMember( attribute.getPropertyName() + paramTypes, attribute ); putMember( attribute.getPropertyName() + paramTypes, attribute );
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 );
} }
} }
} }
@ -1710,9 +1745,9 @@ public class AnnotationMetaEntity extends AnnotationMeta {
if ( reactive ) { if ( reactive ) {
// for reactive calls, don't use the returnType param, which has been ununi-ed, we want to check the full one // for reactive calls, don't use the returnType param, which has been ununi-ed, we want to check the full one
final String returnTypeName = method.getReturnType().toString(); final String returnTypeName = method.getReturnType().toString();
return returnTypeName.equals( Constants.UNI_VOID ) return returnTypeName.equals( UNI_VOID )
|| returnTypeName.equals( Constants.UNI_BOOLEAN ) || returnTypeName.equals( UNI_BOOLEAN )
|| returnTypeName.equals( Constants.UNI_INTEGER ); || returnTypeName.equals( UNI_INTEGER );
} }
else { else {
@ -1833,9 +1868,9 @@ public class AnnotationMetaEntity extends AnnotationMeta {
} }
final TypeElement typeElement = (TypeElement) returnType.asElement(); final TypeElement typeElement = (TypeElement) returnType.asElement();
final Name qualifiedName = typeElement.getQualifiedName(); final Name qualifiedName = typeElement.getQualifiedName();
if ( qualifiedName.contentEquals(Constants.TUPLE) if ( qualifiedName.contentEquals(TUPLE)
|| qualifiedName.contentEquals(Constants.LIST) || qualifiedName.contentEquals(LIST)
|| qualifiedName.contentEquals(Constants.MAP) ) { || qualifiedName.contentEquals(MAP) ) {
// these are exceptionally allowed // these are exceptionally allowed
return true; return true;
} }
@ -1903,7 +1938,7 @@ public class AnnotationMetaEntity extends AnnotationMeta {
if ( componentType.getKind() == TypeKind.DECLARED ) { if ( componentType.getKind() == TypeKind.DECLARED ) {
final DeclaredType declaredType = (DeclaredType) componentType; final DeclaredType declaredType = (DeclaredType) componentType;
final TypeElement typeElement = (TypeElement) declaredType.asElement(); final TypeElement typeElement = (TypeElement) declaredType.asElement();
return typeElement.getQualifiedName().contentEquals(Constants.JAVA_OBJECT); return typeElement.getQualifiedName().contentEquals(JAVA_OBJECT);
} }
else { else {
return false; return false;
@ -2114,11 +2149,11 @@ public class AnnotationMetaEntity extends AnnotationMeta {
} }
private boolean usingReactiveSession(String sessionType) { private boolean usingReactiveSession(String sessionType) {
return Constants.MUTINY_SESSION.equals(sessionType) return MUTINY_SESSION.equals(sessionType)
|| Constants.UNI_MUTINY_SESSION.equals(sessionType); || Constants.UNI_MUTINY_SESSION.equals(sessionType);
} }
private boolean usingStatelessSession(String sessionType) { private boolean usingStatelessSession(String sessionType) {
return Constants.HIB_STATELESS_SESSION.equals(sessionType); return HIB_STATELESS_SESSION.equals(sessionType);
} }
} }

View File

@ -7,6 +7,7 @@
package org.hibernate.processor.annotation; package org.hibernate.processor.annotation;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import org.hibernate.AssertionFailure;
import org.hibernate.processor.util.Constants; import org.hibernate.processor.util.Constants;
import java.util.List; import java.util.List;
@ -199,6 +200,7 @@ public class CriteriaFinderMethod extends AbstractFinderMethod {
.append(".in("); .append(".in(");
if ( paramType.endsWith("[]") ) { if ( paramType.endsWith("[]") ) {
declaration declaration
.append("(Object[]) ")
//TODO: only safe if we are binding literals as parameters!!! //TODO: only safe if we are binding literals as parameters!!!
.append(parameterName); .append(parameterName);
@ -247,7 +249,10 @@ public class CriteriaFinderMethod extends AbstractFinderMethod {
private StringBuilder returnType() { private StringBuilder returnType() {
final StringBuilder type = new StringBuilder(); final StringBuilder type = new StringBuilder();
if ( "[]".equals(containerType) ) { if ( "[]".equals(containerType) ) {
type.append(returnTypeName).append("[]"); if ( returnTypeName == null ) {
throw new AssertionFailure("array return type, but no type name");
}
type.append(annotationMetaEntity.importType(returnTypeName)).append("[]");
} }
else { else {
boolean returnsUni = isReactive() boolean returnsUni = isReactive()

View File

@ -7,6 +7,7 @@
package org.hibernate.processor.annotation; package org.hibernate.processor.annotation;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import org.hibernate.AssertionFailure;
import org.hibernate.internal.util.StringHelper; import org.hibernate.internal.util.StringHelper;
import org.hibernate.processor.util.Constants; import org.hibernate.processor.util.Constants;
@ -192,7 +193,14 @@ public class QueryMethod extends AbstractQueryMethod {
} }
private StringBuilder returnType() { private StringBuilder returnType() {
StringBuilder type = new StringBuilder(); final StringBuilder type = new StringBuilder();
if ( "[]".equals(containerType) ) {
if ( returnTypeName == null ) {
throw new AssertionFailure("array return type, but no type name");
}
type.append(annotationMetaEntity.importType(returnTypeName)).append("[]");
}
else {
boolean returnsUni = isReactive() boolean returnsUni = isReactive()
&& (containerType == null || LIST.equals(containerType)); && (containerType == null || LIST.equals(containerType));
if ( returnsUni ) { if ( returnsUni ) {
@ -210,6 +218,7 @@ public class QueryMethod extends AbstractQueryMethod {
if ( returnsUni ) { if ( returnsUni ) {
type.append('>'); type.append('>');
} }
}
return type; return type;
} }

View File

@ -43,6 +43,9 @@ public interface Dao {
@HQL("order by isbn asc, publicationDate desc") @HQL("order by isbn asc, publicationDate desc")
List<Book> allBooks(); List<Book> allBooks();
@HQL("order by isbn asc, publicationDate desc")
Book[] allBooksAsArray();
@SQL("select * from Book where isbn = :isbn") @SQL("select * from Book where isbn = :isbn")
Book findByIsbnNative(String isbn); Book findByIsbnNative(String isbn);