Projections Failing - TupleTransformer not Used : Problem and Fix

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 );

Thanks for the analysis, but we will still need a reproducer for this issue and a bug ticket in our issue tracker. Since you seem to have a hunch about what needs fixing, please open a pull request with the test and fix against the Hibernate ORM repository.