Skip to content

Commit

Permalink
HHH-18629 Fix inconsistent column alias generated while result class …
Browse files Browse the repository at this point in the history
…is used for placeholder
  • Loading branch information
beikov committed Nov 21, 2024
1 parent 7921473 commit e72c3b5
Show file tree
Hide file tree
Showing 4 changed files with 184 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import java.util.function.Function;

import jakarta.persistence.EntityGraph;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.hibernate.CacheMode;
import org.hibernate.EntityNameResolver;
import org.hibernate.Filter;
Expand Down Expand Up @@ -905,22 +906,8 @@ public <R> QueryImplementor<R> createQuery(TypedQueryReference<R> typedQueryRefe
// dynamic native (SQL) query handling

@Override @SuppressWarnings("rawtypes")
public NativeQueryImpl createNativeQuery(String sqlString) {
checkOpen();
pulseTransactionCoordinator();
delayedAfterCompletion();

try {
final NativeQueryImpl query = new NativeQueryImpl<>( sqlString, this );
if ( isEmpty( query.getComment() ) ) {
query.setComment( "dynamic native SQL query" );
}
applyQuerySettingsAndHints( query );
return query;
}
catch (RuntimeException he) {
throw getExceptionConverter().convert( he );
}
public NativeQueryImplementor createNativeQuery(String sqlString) {
return createNativeQuery( sqlString, (Class) null );
}

@Override @SuppressWarnings("rawtypes")
Expand Down Expand Up @@ -953,12 +940,28 @@ protected NamedResultSetMappingMemento getResultSetMappingMemento(String resultS
@Override @SuppressWarnings({"rawtypes", "unchecked"})
//note: we're doing something a bit funny here to work around
// the clashing signatures declared by the supertypes
public NativeQueryImplementor createNativeQuery(String sqlString, Class resultClass) {
final NativeQueryImpl query = createNativeQuery( sqlString );
addResultType( resultClass, query );
return query;
public NativeQueryImplementor createNativeQuery(String sqlString, @Nullable Class resultClass) {
checkOpen();
pulseTransactionCoordinator();
delayedAfterCompletion();

try {
final NativeQueryImpl query = new NativeQueryImpl<>( sqlString, resultClass, this );
if ( isEmpty( query.getComment() ) ) {
query.setComment( "dynamic native SQL query" );
}
applyQuerySettingsAndHints( query );
return query;
}
catch (RuntimeException he) {
throw getExceptionConverter().convert( he );
}
}

/**
* @deprecated Use {@link NativeQueryImpl#NativeQueryImpl(String, Class, SharedSessionContractImplementor)} instead
*/
@Deprecated(forRemoval = true)
protected <T> void addResultType(Class<T> resultClass, NativeQueryImplementor<T> query) {
if ( Tuple.class.equals( resultClass ) ) {
query.setTupleTransformer( NativeQueryTupleTransformer.INSTANCE );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,12 @@
import java.util.function.Consumer;
import java.util.function.Supplier;

import org.checkerframework.checker.nullness.qual.Nullable;
import org.hibernate.CacheMode;
import org.hibernate.FlushMode;
import org.hibernate.jpa.spi.NativeQueryConstructorTransformer;
import org.hibernate.jpa.spi.NativeQueryListTransformer;
import org.hibernate.jpa.spi.NativeQueryMapTransformer;
import org.hibernate.metamodel.spi.MappingMetamodelImplementor;
import org.hibernate.query.QueryFlushMode;
import org.hibernate.HibernateException;
Expand Down Expand Up @@ -105,17 +109,21 @@
import jakarta.persistence.TypedQuery;
import jakarta.persistence.metamodel.SingularAttribute;
import org.hibernate.type.BasicTypeRegistry;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.java.spi.UnknownBasicJavaType;
import org.hibernate.type.spi.TypeConfiguration;

import static java.lang.Character.isWhitespace;
import static java.util.Collections.addAll;
import static org.hibernate.internal.util.ReflectHelper.isClass;
import static org.hibernate.internal.util.StringHelper.unqualify;
import static org.hibernate.internal.util.collections.CollectionHelper.isEmpty;
import static org.hibernate.internal.util.collections.CollectionHelper.isNotEmpty;
import static org.hibernate.internal.util.collections.CollectionHelper.makeCopy;
import static org.hibernate.jpa.HibernateHints.HINT_NATIVE_LOCK_MODE;
import static org.hibernate.query.results.internal.Builders.resultClassBuilder;
import static org.hibernate.query.results.ResultSetMapping.resolveResultSetMapping;
import static org.hibernate.query.sqm.internal.SqmUtil.isResultTypeAlwaysAllowed;

/**
* @author Steve Ebersole
Expand All @@ -129,6 +137,7 @@ public class NativeQueryImpl<R>
private final List<ParameterOccurrence> parameterOccurrences;
private final QueryParameterBindings parameterBindings;

private final Class<R> resultType;
private final ResultSetMapping resultSetMapping;
private final boolean resultMappingSuppliedToCtor;

Expand Down Expand Up @@ -166,6 +175,7 @@ public NativeQueryImpl(
return false;
}
},
null,
session
);
}
Expand Down Expand Up @@ -218,26 +228,9 @@ public NativeQueryImpl(
return false;
}
},
resultJavaType,
session
);

if ( resultJavaType == Tuple.class ) {
setTupleTransformer( new NativeQueryTupleTransformer() );
}
else if ( resultJavaType != null && !resultJavaType.isArray() ) {
switch ( resultSetMapping.getNumberOfResultBuilders() ) {
case 0:
throw new IllegalArgumentException( "Named query exists, but did not specify a resultClass" );
case 1:
final Class<?> actualResultJavaType = resultSetMapping.getResultBuilders().get( 0 ).getJavaType();
if ( actualResultJavaType != null && !resultJavaType.isAssignableFrom( actualResultJavaType ) ) {
throw buildIncompatibleException( resultJavaType, actualResultJavaType );
}
break;
default:
throw new IllegalArgumentException( "Cannot create TypedQuery for query with more than one return" );
}
}
}

/**
Expand All @@ -258,6 +251,7 @@ public NativeQueryImpl(
mappingMemento.resolve( resultSetMapping, querySpaceConsumer, context );
return true;
},
null,
session
);

Expand All @@ -268,6 +262,15 @@ public NativeQueryImpl(
Supplier<ResultSetMapping> resultSetMappingCreator,
ResultSetMappingHandler resultSetMappingHandler,
SharedSessionContractImplementor session) {
this( memento, resultSetMappingCreator, resultSetMappingHandler, null, session );
}

public NativeQueryImpl(
NamedNativeQueryMemento<?> memento,
Supplier<ResultSetMapping> resultSetMappingCreator,
ResultSetMappingHandler resultSetMappingHandler,
@Nullable Class<R> resultType,
SharedSessionContractImplementor session) {
super( session );

this.originalSqlString = memento.getOriginalSqlString();
Expand All @@ -279,13 +282,35 @@ public NativeQueryImpl(
this.parameterMetadata = parameterInterpretation.toParameterMetadata( session );
this.parameterOccurrences = parameterInterpretation.getOrderedParameterOccurrences();
this.parameterBindings = parameterMetadata.createBindings( session.getFactory() );
this.resultType = resultType;
this.querySpaces = new HashSet<>();

this.resultSetMapping = resultSetMappingCreator.get();

this.resultMappingSuppliedToCtor =
resultSetMappingHandler.resolveResultSetMapping( resultSetMapping, querySpaces::add, this );

if ( resultType != null ) {
if ( !isResultTypeAlwaysAllowed( resultType ) ) {
switch ( resultSetMapping.getNumberOfResultBuilders() ) {
case 0:
throw new IllegalArgumentException( "Named query exists, but did not specify a resultClass" );
case 1:
final Class<?> actualResultJavaType = resultSetMapping.getResultBuilders().get( 0 )
.getJavaType();
if ( actualResultJavaType != null && !resultType.isAssignableFrom( actualResultJavaType ) ) {
throw buildIncompatibleException( resultType, actualResultJavaType );
}
break;
default:
throw new IllegalArgumentException(
"Cannot create TypedQuery for query with more than one return" );
}
}
else {
setTupleTransformerForResultType( resultType );
}
}
applyOptions( memento );
}

Expand All @@ -301,6 +326,7 @@ public NativeQueryImpl(
this.parameterMetadata = parameterInterpretation.toParameterMetadata( session );
this.parameterOccurrences = parameterInterpretation.getOrderedParameterOccurrences();
this.parameterBindings = parameterMetadata.createBindings( session.getFactory() );
this.resultType = null;
this.querySpaces = new HashSet<>();

this.resultSetMapping = buildResultSetMapping( resultSetMappingMemento.getName(), false, session );
Expand All @@ -310,6 +336,10 @@ public NativeQueryImpl(
}

public NativeQueryImpl(String sqlString, SharedSessionContractImplementor session) {
this( sqlString, null, session );
}

public NativeQueryImpl(String sqlString, @Nullable Class<R> resultType, SharedSessionContractImplementor session) {
super( session );

this.querySpaces = new HashSet<>();
Expand All @@ -320,11 +350,46 @@ public NativeQueryImpl(String sqlString, SharedSessionContractImplementor sessio
this.parameterMetadata = parameterInterpretation.toParameterMetadata( session );
this.parameterOccurrences = parameterInterpretation.getOrderedParameterOccurrences();
this.parameterBindings = parameterMetadata.createBindings( session.getFactory() );
this.resultType = resultType;
if ( resultType != null ) {
setTupleTransformerForResultType( resultType );
}

this.resultSetMapping = resolveResultSetMapping( sqlString, true, session.getFactory() );
this.resultMappingSuppliedToCtor = false;
}

protected <T> void setTupleTransformerForResultType(Class<T> resultClass) {
final TupleTransformer<?> tupleTransformer = determineTupleTransformerForResultType( resultClass );
if ( tupleTransformer != null ) {
setTupleTransformer( tupleTransformer );
}
}

protected @Nullable TupleTransformer<?> determineTupleTransformerForResultType(Class<?> resultClass) {
if ( Tuple.class.equals( resultClass ) ) {
return NativeQueryTupleTransformer.INSTANCE;
}
else if ( Map.class.equals( resultClass ) ) {
return NativeQueryMapTransformer.INSTANCE;
}
else if ( List.class.equals( resultClass ) ) {
return NativeQueryListTransformer.INSTANCE;
}
else if ( resultClass != Object.class && resultClass != Object[].class ) {
if ( isClass( resultClass ) && !hasJavaTypeDescriptor( resultClass ) ) {
// not a basic type
return new NativeQueryConstructorTransformer<>( resultClass );
}
}
return null;
}

private <T> boolean hasJavaTypeDescriptor(Class<T> resultClass) {
final JavaType<Object> descriptor = getTypeConfiguration().getJavaTypeRegistry().findDescriptor( resultClass );
return descriptor != null && descriptor.getClass() != UnknownBasicJavaType.class;
}

@FunctionalInterface
private interface ResultSetMappingHandler {
boolean resolveResultSetMapping(
Expand Down Expand Up @@ -436,11 +501,16 @@ public QueryParameterBindings getParameterBindings() {
return getQueryParameterBindings();
}

@Override
public Class<R> getResultType() {
return resultType;
}

@Override
public NamedNativeQueryMemento<?> toMemento(String name) {
return new NamedNativeQueryMementoImpl<>(
name,
extractResultClass( resultSetMapping ),
resultType != null ? resultType : extractResultClass( resultSetMapping ),
sqlString,
originalSqlString,
resultSetMapping.getMappingIdentifier(),
Expand All @@ -459,14 +529,14 @@ public NamedNativeQueryMemento<?> toMemento(String name) {
);
}

private Class<?> extractResultClass(ResultSetMapping resultSetMapping) {
private Class<R> extractResultClass(ResultSetMapping resultSetMapping) {
final List<ResultBuilder> resultBuilders = resultSetMapping.getResultBuilders();
if ( resultBuilders.size() == 1 ) {
final ResultBuilder resultBuilder = resultBuilders.get( 0 );
if ( resultBuilder instanceof ImplicitResultClassBuilder
|| resultBuilder instanceof ImplicitModelPartResultBuilderEntity
|| resultBuilder instanceof DynamicResultBuilderEntityCalculated ) {
return resultBuilder.getJavaType();
return (Class<R>) resultBuilder.getJavaType();
}
}
return null;
Expand Down Expand Up @@ -618,13 +688,28 @@ public KeyedResultList<R> getKeyedResultList(KeyedPage<R> page) {
}

protected SelectQueryPlan<R> resolveSelectQueryPlan() {
final ResultSetMapping mapping;
if ( resultType != null && resultSetMapping.isDynamic() && resultSetMapping.getNumberOfResultBuilders() == 0 ) {
mapping = ResultSetMapping.resolveResultSetMapping( originalSqlString, true, getSessionFactory() );

if ( getSessionFactory().getMappingMetamodel().isEntityClass( resultType ) ) {
mapping.addResultBuilder(
Builders.entityCalculated( unqualify( resultType.getName() ), resultType.getName(),
LockMode.READ, getSessionFactory() ) );
}
else if ( !isResultTypeAlwaysAllowed( resultType ) ) {
mapping.addResultBuilder( Builders.resultClassBuilder( resultType, getSessionFactory() ) );
}
}
else {
mapping = resultSetMapping;
}
return isCacheableQuery()
? getInterpretationCache()
.resolveSelectQueryPlan( selectInterpretationsKey(), this::createQueryPlan )
: createQueryPlan();
? getInterpretationCache().resolveSelectQueryPlan( selectInterpretationsKey( mapping ), () -> createQueryPlan( mapping ) )
: createQueryPlan( mapping );
}

private NativeSelectQueryPlan<R> createQueryPlan() {
private NativeSelectQueryPlan<R> createQueryPlan(ResultSetMapping resultSetMapping) {
final NativeSelectQueryDefinition<R> queryDefinition = new NativeSelectQueryDefinition<>() {
final String sqlString = expandParameterLists();

Expand Down Expand Up @@ -834,7 +919,7 @@ public static int determineBindValueMaxCount(boolean paddingEnabled, int inExprL
return bindValueMaxCount;
}

private SelectInterpretationsKey selectInterpretationsKey() {
private SelectInterpretationsKey selectInterpretationsKey(ResultSetMapping resultSetMapping) {
return new SelectInterpretationsKey(
getQueryString(),
resultSetMapping,
Expand Down
Loading

0 comments on commit e72c3b5

Please sign in to comment.