Regarding the earlier bug:
On debugging, The issue is that the set TupleTransformer is not used: Previously (version 7.3.0.Final) the class org.hibernate.query.sqm.internal.ConcreteSqmSelectQueryPlan had this code:
/**
* If the result type of the query is {@link Tuple}, {@link Map}, {@link List},
* or any record or class type with an appropriate constructor, then we attempt
* to repackage the result tuple as an instance of the result type using an
* appropriate {@link RowTransformer}.
*
* @param resultClass The requested result type of the query
* @return A {@link RowTransformer} responsible for repackaging the result type
*/
protected static <T> RowTransformer<T> determineRowTransformer(
SqmSelectStatement<?> sqm,
Class<T> resultClass,
TupleMetadata tupleMetadata,
QueryOptions queryOptions) {
if ( queryOptions.getTupleTransformer() != null ) {
return makeRowTransformerTupleTransformerAdapter( sqm, queryOptions );
}
else if ( resultClass == null || resultClass == Object.class ) {
return RowTransformerStandardImpl.instance();
}
else {
final var selections = selections( sqm );
if ( selections == null ) {
throw new AssertionFailure( "No selections" );
}
else {
final var resultType = primitiveToWrapper( resultClass );
return switch ( selections.size() ) {
case 0 -> throw new AssertionFailure( "No selections" );
case 1 -> singleItemRowTransformer( sqm, tupleMetadata, selections.get( 0 ), resultType );
default -> multipleItemRowTransformer( sqm, tupleMetadata, resultType );
};
}
}
}
This method is called in the constructor of this class, and when a TupleTransformer is set, It branches off (as expected) at the first if statement…
The versions after 7.3.0.Final break this method into 2 as follows:
/**
* If the result type of the query is {@link Tuple}, {@link Map}, {@link List},
* or any record or class type with an appropriate constructor, then we attempt
* to repackage the result tuple as an instance of the result type using an
* appropriate {@link RowTransformer}.
*
* @param resultClass The requested result type of the query
* @return A {@link RowTransformer} responsible for repackaging the result type
*/
protected static <T> RowTransformer<T> determineRowTransformer(
SqmSelectStatement<?> sqm,
Class<T> resultClass,
TupleMetadata tupleMetadata,
QueryOptions queryOptions) {
if ( queryOptions.getTupleTransformer() != null ) {
return makeRowTransformerTupleTransformerAdapter( sqm, queryOptions );
}
else {
return determineRowTransformer( sqm, resultClass, tupleMetadata );
}
}
/**
* If the result type of the query is {@link Tuple}, {@link Map}, {@link List},
* or any record or class type with an appropriate constructor, then we attempt
* to repackage the result tuple as an instance of the result type using an
* appropriate {@link RowTransformer}.
*
* @param resultClass The requested result type of the query
* @return A {@link RowTransformer} responsible for repackaging the result type
*/
protected static <T> RowTransformer<T> determineRowTransformer(
SqmSelectStatement<?> sqm,
Class<T> resultClass,
TupleMetadata tupleMetadata) {
if ( resultClass == null || resultClass == Object.class ) {
return RowTransformerStandardImpl.instance();
}
else {
final var selections = selections( sqm );
final var resultType = primitiveToWrapper( resultClass );
return switch ( selections.size() ) {
case 0 -> throw new AssertionFailure( "No selections" );
case 1 -> singleItemRowTransformer( sqm, tupleMetadata, selections.get( 0 ), resultType );
default -> multipleItemRowTransformer( sqm, tupleMetadata, resultType );
};
}
}
Problem:
Now the problem is that in the constructor of the class, The call is made to the second method, Implying if you set a TupleTransformer, Its completely skipped as seen here:
final var rowTransformer = determineRowTransformer( sqm, resultType, tupleMetadata );
Fix:
Is to call the first method (overload) which actually inturn calls the second method if no TupleTransformer is set…
final var rowTransformer = determineRowTransformer( sqm, resultType, tupleMetadata, queryOptions );