Skip to content

Commit

Permalink
HHH-17956 Criteria multiselect ignores type of the criteria query and…
Browse files Browse the repository at this point in the history
… always returns list of Object[]
  • Loading branch information
dreab8 authored and beikov committed Apr 25, 2024
1 parent 3a995eb commit ac4cae6
Show file tree
Hide file tree
Showing 5 changed files with 209 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,8 @@ default boolean hasCallbackActions() {
* The underlying session
*/
SharedSessionContractImplementor getSession();

default Class<?> getResultType() {
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -136,11 +136,13 @@ public ConcreteSqmSelectQueryPlan(
jdbcParameterBindings
);
session.autoFlushIfRequired( jdbcSelect.getAffectedTableNames(), true );
//noinspection unchecked
return session.getFactory().getJdbcServices().getJdbcSelectExecutor().list(
jdbcSelect,
jdbcParameterBindings,
listInterpreterExecutionContext( hql, executionContext, jdbcSelect, subSelectFetchKeyHandler ),
rowTransformer,
(Class<R>) executionContext.getResultType(),
uniqueSemantic
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/
package org.hibernate.sql.results.internal;

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;

Expand All @@ -32,6 +33,8 @@ public class StandardRowReader<T> implements RowReader<T> {
private final Class<T> domainResultJavaType;

private final int assemblerCount;
private final ComponentType componentType;
private final Class<?> resultElementClass;

private static final Logger LOGGER = LoadingLogger.LOGGER;

Expand All @@ -45,6 +48,19 @@ public StandardRowReader(
this.rowTransformer = rowTransformer;
this.assemblerCount = resultAssemblers.size();
this.domainResultJavaType = domainResultJavaType;
if ( domainResultJavaType == null
|| domainResultJavaType == Object[].class
|| domainResultJavaType == Object.class
|| !domainResultJavaType.isArray()
|| resultAssemblers.size() == 1
&& domainResultJavaType == resultAssemblers.get( 0 ).getAssembledJavaType().getJavaTypeClass() ) {
this.resultElementClass = Object.class;
this.componentType = ComponentType.OBJECT;
}
else {
this.resultElementClass = domainResultJavaType.getComponentType();
this.componentType = ComponentType.determineComponentType( domainResultJavaType );
}
}

@Override
Expand Down Expand Up @@ -86,20 +102,139 @@ public T readRow(RowProcessingState rowProcessingState, JdbcValuesSourceProcessi
LOGGER.trace( "StandardRowReader#readRow" );
coordinateInitializers( rowProcessingState );

final Object[] resultRow = new Object[ assemblerCount ];
final boolean debugEnabled = LOGGER.isDebugEnabled();
// The following is ugly, but unfortunately necessary to not hurt performance.
// This implementation was micro-benchmarked and discussed with Francesco Nigro,
// who hinted that using this style instead of the reflective Array.getLength(), Array.set()
// is easier for the JVM to optimize
switch ( componentType ) {
case BOOLEAN:
final boolean[] resultBooleanRow = new boolean[assemblerCount];

for ( int i = 0; i < assemblerCount; i++ ) {
final DomainResultAssembler assembler = resultAssemblers.get( i );
if ( debugEnabled ) {
LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler );
}
resultRow[i] = assembler.assemble( rowProcessingState, options );
}
for ( int i = 0; i < assemblerCount; i++ ) {
final DomainResultAssembler assembler = resultAssemblers.get( i );
if ( debugEnabled ) {
LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler );
}
resultBooleanRow[i] = (boolean) assembler.assemble( rowProcessingState, options );
}

afterRow( rowProcessingState );

return (T) resultBooleanRow;
case BYTE:
final byte[] resultByteRow = new byte[assemblerCount];

for ( int i = 0; i < assemblerCount; i++ ) {
final DomainResultAssembler assembler = resultAssemblers.get( i );
if ( debugEnabled ) {
LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler );
}
resultByteRow[i] = (byte) assembler.assemble( rowProcessingState, options );
}

afterRow( rowProcessingState );

return (T) resultByteRow;
case CHAR:
final char[] resultCharRow = new char[assemblerCount];

for ( int i = 0; i < assemblerCount; i++ ) {
final DomainResultAssembler assembler = resultAssemblers.get( i );
if ( debugEnabled ) {
LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler );
}
resultCharRow[i] = (char) assembler.assemble( rowProcessingState, options );
}

afterRow( rowProcessingState );

return (T) resultCharRow;
case SHORT:
final short[] resultShortRow = new short[assemblerCount];

for ( int i = 0; i < assemblerCount; i++ ) {
final DomainResultAssembler assembler = resultAssemblers.get( i );
if ( debugEnabled ) {
LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler );
}
resultShortRow[i] = (short) assembler.assemble( rowProcessingState, options );
}

afterRow( rowProcessingState );

return (T) resultShortRow;
case INT:
final int[] resultIntRow = new int[assemblerCount];

for ( int i = 0; i < assemblerCount; i++ ) {
final DomainResultAssembler assembler = resultAssemblers.get( i );
if ( debugEnabled ) {
LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler );
}
resultIntRow[i] = (int) assembler.assemble( rowProcessingState, options );
}

afterRow( rowProcessingState );

return (T) resultIntRow;
case LONG:
final long[] resultLongRow = new long[assemblerCount];

for ( int i = 0; i < assemblerCount; i++ ) {
final DomainResultAssembler assembler = resultAssemblers.get( i );
if ( debugEnabled ) {
LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler );
}
resultLongRow[i] = (long) assembler.assemble( rowProcessingState, options );
}

afterRow( rowProcessingState );

return (T) resultLongRow;
case FLOAT:
final float[] resultFloatRow = new float[assemblerCount];

for ( int i = 0; i < assemblerCount; i++ ) {
final DomainResultAssembler assembler = resultAssemblers.get( i );
if ( debugEnabled ) {
LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler );
}
resultFloatRow[i] = (float) assembler.assemble( rowProcessingState, options );
}

afterRow( rowProcessingState );

afterRow( rowProcessingState );
return (T) resultFloatRow;
case DOUBLE:
final double[] resultDoubleRow = new double[assemblerCount];

return rowTransformer.transformRow( resultRow );
for ( int i = 0; i < assemblerCount; i++ ) {
final DomainResultAssembler assembler = resultAssemblers.get( i );
if ( debugEnabled ) {
LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler );
}
resultDoubleRow[i] = (double) assembler.assemble( rowProcessingState, options );
}

afterRow( rowProcessingState );

return (T) resultDoubleRow;
default:
final Object[] resultRow = (Object[]) Array.newInstance( resultElementClass, assemblerCount );

for ( int i = 0; i < assemblerCount; i++ ) {
final DomainResultAssembler assembler = resultAssemblers.get( i );
if ( debugEnabled ) {
LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler );
}
resultRow[i] = assembler.assemble( rowProcessingState, options );
}

afterRow( rowProcessingState );

return rowTransformer.transformRow( resultRow );
}
}

private void afterRow(RowProcessingState rowProcessingState) {
Expand All @@ -118,4 +253,54 @@ public void finishUp(JdbcValuesSourceProcessingState processingState) {
initializers.endLoading( processingState.getExecutionContext() );
}

enum ComponentType {
BOOLEAN(boolean.class),
BYTE(byte.class),
SHORT(short.class),
CHAR(char.class),
INT(int.class),
LONG(long.class),
FLOAT(float.class),
DOUBLE(double.class),
OBJECT(Object.class);

private final Class<?> componentType;

ComponentType(Class<?> componentType) {
this.componentType = componentType;
}

public static ComponentType determineComponentType(Class<?> resultType) {
if ( resultType == boolean[].class) {
return BOOLEAN;
}
else if ( resultType == byte[].class) {
return BYTE;
}
else if ( resultType == short[].class) {
return SHORT;
}
else if ( resultType == char[].class) {
return CHAR;
}
else if ( resultType == int[].class) {
return INT;
}
else if ( resultType == long[].class) {
return LONG;
}
else if ( resultType == float[].class) {
return FLOAT;
}
else if ( resultType == double[].class) {
return DOUBLE;
}
return OBJECT;
}

public Class<?> getComponentType() {
return componentType;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,10 @@ public interface RowReader<R> {
* @apiNote along with {@link #getResultJavaTypes()}, describes the "raw"
* values as determined from the {@link org.hibernate.sql.results.graph.DomainResult}
* references associated with the JdbcValues being processed
*
* @deprecated Not used anymore
*/
@Deprecated(forRemoval = true)
Class<?> getResultJavaType();

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ public void testResultOfMultiSelect(EntityManagerFactoryScope scope) {
List<Integer[]> idPairs = entityManager.createQuery( q ).getResultList();
assertThat( idPairs.size() ).isEqualTo( 1 );
Integer[] ids = idPairs.get( 0 );
assertThat( ids[0] ).isEqualTo( 1 );
}
);
}
Expand All @@ -60,6 +61,7 @@ public void testResultOfMultiSelectPrimitive(EntityManagerFactoryScope scope) {
List<int[]> idPairs = entityManager.createQuery( q ).getResultList();
assertThat( idPairs.size() ).isEqualTo( 1 );
int[] ids = idPairs.get( 0 );
assertThat( ids[0] ).isEqualTo( 1 );
}
);
}
Expand All @@ -75,8 +77,8 @@ public void testResultOfMultiSelect2(EntityManagerFactoryScope scope) {
List<Object[]> values = entityManager.createQuery( q ).getResultList();
assertThat( values.size() ).isEqualTo( 1 );
Object[] value = values.get( 0 );
Integer id = (Integer) value[0];
String name = (String) value[1];
assertThat( value[0] ).isEqualTo( 1 );
assertThat( value[1] ).isEqualTo( "a" );
}
);
}
Expand All @@ -91,7 +93,7 @@ public void testResultOfSelect(EntityManagerFactoryScope scope) {
q.select( r.get( "id" ) );
List<Integer> idPairs = entityManager.createQuery( q ).getResultList();
assertThat( idPairs.size() ).isEqualTo( 1 );
Integer id = idPairs.get( 0 );
assertThat( idPairs.get( 0 ) ).isEqualTo( 1 );
}
);
}
Expand Down

0 comments on commit ac4cae6

Please sign in to comment.