HHH-16710 implicit instantiation of Lists, Maps

This commit is contained in:
Gavin 2023-05-28 12:14:44 +02:00 committed by Gavin King
parent 4bdfb2da25
commit 87a2b967c7
12 changed files with 361 additions and 135 deletions

View File

@ -23,7 +23,7 @@ public class NonUniqueResultException extends HibernateException {
* @param resultCount The number of actual results. * @param resultCount The number of actual results.
*/ */
public NonUniqueResultException(int resultCount) { public NonUniqueResultException(int resultCount) {
super( "query did not return a unique result: " + resultCount ); super( "Query did not return a unique result: " + resultCount + " results were returned" );
} }
} }

View File

@ -742,7 +742,7 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
} }
} }
protected static <R> void checkResultType(Class<R> expectedResultType, SqmSelectionQueryImpl<?> query) { protected static <R> void checkResultType(Class<R> expectedResultType, SqmSelectionQueryImpl<R> query) {
final Class<?> resultType = query.getResultType(); final Class<?> resultType = query.getResultType();
if ( !expectedResultType.isAssignableFrom( resultType ) ) { if ( !expectedResultType.isAssignableFrom( resultType ) ) {
throw new QueryTypeMismatchException( throw new QueryTypeMismatchException(
@ -849,6 +849,7 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
if ( Tuple.class.equals( resultClass ) ) { if ( Tuple.class.equals( resultClass ) ) {
query.setTupleTransformer( new NativeQueryTupleTransformer() ); query.setTupleTransformer( new NativeQueryTupleTransformer() );
} }
// TODO: handle Map, List as well
else if ( getFactory().getMappingMetamodel().isEntityClass( resultClass ) ) { else if ( getFactory().getMappingMetamodel().isEntityClass( resultClass ) ) {
query.addEntity( "alias1", resultClass.getName(), LockMode.READ ); query.addEntity( "alias1", resultClass.getName(), LockMode.READ );
} }

View File

@ -104,40 +104,56 @@ public abstract class AbstractSelectionQuery<R>
super( session ); super( session );
} }
protected TupleMetadata buildTupleMetadata(SqmStatement<?> statement, Class<R> resultType) { public static boolean isTupleResultClass(Class<?> resultType) {
if ( resultType != null && Tuple.class.isAssignableFrom( resultType ) ) { return Tuple.class.isAssignableFrom( resultType )
final List<SqmSelection<?>> selections = ( (SqmSelectStatement<?>) statement ).getQueryPart() || Map.class.isAssignableFrom( resultType );
.getFirstQuerySpec() }
.getSelectClause()
.getSelections();
// resultType is Tuple..
if ( getQueryOptions().getTupleTransformer() == null ) {
final Map<TupleElement<?>, Integer> tupleElementMap;
if ( selections.size() == 1 && selections.get( 0 ).getSelectableNode() instanceof CompoundSelection<?> ) {
final List<? extends JpaSelection<?>> selectionItems = selections.get( 0 )
.getSelectableNode()
.getSelectionItems();
tupleElementMap = new IdentityHashMap<>( selectionItems.size() );
for ( int i = 0; i < selectionItems.size(); i++ ) {
tupleElementMap.put( selectionItems.get( i ), i );
}
}
else {
tupleElementMap = new IdentityHashMap<>( selections.size() );
for ( int i = 0; i < selections.size(); i++ ) {
final SqmSelection<?> selection = selections.get( i );
tupleElementMap.put( selection.getSelectableNode(), i );
}
}
return new TupleMetadata( tupleElementMap );
}
throw new IllegalArgumentException( protected TupleMetadata buildTupleMetadata(SqmStatement<?> statement, Class<R> resultType) {
"Illegal combination of Tuple resultType and (non-JpaTupleBuilder) TupleTransformer : " + if ( resultType == null ) {
getQueryOptions().getTupleTransformer() return null;
);
} }
return null; else if ( isTupleResultClass( resultType ) ) {
final List<SqmSelection<?>> selections =
( (SqmSelectStatement<?>) statement ).getQueryPart()
.getFirstQuerySpec()
.getSelectClause()
.getSelections();
if ( getQueryOptions().getTupleTransformer() == null ) {
return new TupleMetadata( buildTupleElementMap( selections ) );
}
else {
throw new IllegalArgumentException(
"Illegal combination of Tuple resultType and (non-JpaTupleBuilder) TupleTransformer : " +
getQueryOptions().getTupleTransformer()
);
}
}
else {
return null;
}
}
private static Map<TupleElement<?>, Integer> buildTupleElementMap(List<SqmSelection<?>> selections) {
final Map<TupleElement<?>, Integer> tupleElementMap;
if ( selections.size() == 1
&& selections.get( 0 ).getSelectableNode() instanceof CompoundSelection<?> ) {
final List<? extends JpaSelection<?>> selectionItems =
selections.get( 0 ).getSelectableNode()
.getSelectionItems();
tupleElementMap = new IdentityHashMap<>( selectionItems.size() );
for ( int i = 0; i < selectionItems.size(); i++ ) {
tupleElementMap.put( selectionItems.get( i ), i );
}
}
else {
tupleElementMap = new IdentityHashMap<>( selections.size() );
for (int i = 0; i < selections.size(); i++ ) {
final SqmSelection<?> selection = selections.get( i );
tupleElementMap.put( selection.getSelectableNode(), i );
}
}
return tupleElementMap;
} }
protected void applyOptions(NamedSqmQueryMemento memento) { protected void applyOptions(NamedSqmQueryMemento memento) {
@ -207,7 +223,7 @@ public abstract class AbstractSelectionQuery<R>
*/ */
protected void visitQueryReturnType( protected void visitQueryReturnType(
SqmQueryPart<R> queryPart, SqmQueryPart<R> queryPart,
Class<R> resultType, Class<R> expectedResultType,
SessionFactoryImplementor factory) { SessionFactoryImplementor factory) {
assert getQueryString().equals( CRITERIA_HQL_STRING ); assert getQueryString().equals( CRITERIA_HQL_STRING );
@ -231,33 +247,35 @@ public abstract class AbstractSelectionQuery<R>
} }
} }
if ( resultType != null ) { if ( expectedResultType != null ) {
checkQueryReturnType( sqmQuerySpec, resultType, factory ); checkQueryReturnType( sqmQuerySpec, expectedResultType, factory );
} }
} }
else { else {
final SqmQueryGroup<R> queryGroup = (SqmQueryGroup<R>) queryPart; final SqmQueryGroup<R> queryGroup = (SqmQueryGroup<R>) queryPart;
for ( SqmQueryPart<R> sqmQueryPart : queryGroup.getQueryParts() ) { for ( SqmQueryPart<R> sqmQueryPart : queryGroup.getQueryParts() ) {
visitQueryReturnType( sqmQueryPart, resultType, factory ); visitQueryReturnType( sqmQueryPart, expectedResultType, factory );
} }
} }
} }
protected static <T> void checkQueryReturnType( protected static <T> void checkQueryReturnType(
SqmQuerySpec<T> querySpec, SqmQuerySpec<T> querySpec,
Class<T> resultClass, Class<T> expectedResultClass,
SessionFactoryImplementor sessionFactory) { SessionFactoryImplementor sessionFactory) {
if ( resultClass == null || resultClass == Object.class ) { if ( expectedResultClass == null || expectedResultClass == Object.class ) {
// nothing to check // nothing to check
return; return;
} }
final List<SqmSelection<?>> selections = querySpec.getSelectClause().getSelections(); final List<SqmSelection<?>> selections = querySpec.getSelectClause().getSelections();
if ( resultClass.isArray() ) { if ( expectedResultClass.isArray() ) {
// todo (6.0) : implement // todo (6.0) : implement
} }
else if ( Tuple.class.isAssignableFrom( resultClass ) ) { else if ( Tuple.class.isAssignableFrom( expectedResultClass )
|| Map.class.isAssignableFrom( expectedResultClass )
|| List.class.isAssignableFrom( expectedResultClass ) ) {
// todo (6.0) : implement // todo (6.0) : implement
} }
else { else {
@ -290,21 +308,18 @@ public abstract class AbstractSelectionQuery<R>
if ( jpaQueryComplianceEnabled ) { if ( jpaQueryComplianceEnabled ) {
return; return;
} }
verifyResultType( resultClass, sqmSelection.getNodeType(), sessionFactory ); verifyResultType( expectedResultClass, sqmSelection.getNodeType() );
} }
} }
protected static <T> void verifyResultType( protected static <T> void verifyResultType(Class<T> resultClass, SqmExpressible<?> sqmExpressible) {
Class<T> resultClass,
SqmExpressible<?> sqmExpressible,
SessionFactoryImplementor sessionFactory) {
assert sqmExpressible != null; assert sqmExpressible != null;
final JavaType<?> expressibleJavaType = sqmExpressible.getExpressibleJavaType(); final JavaType<?> expressibleJavaType = sqmExpressible.getExpressibleJavaType();
assert expressibleJavaType != null; assert expressibleJavaType != null;
final Class<?> javaTypeClass = expressibleJavaType.getJavaTypeClass(); final Class<?> javaTypeClass = expressibleJavaType.getJavaTypeClass();
if ( !resultClass.isAssignableFrom( javaTypeClass ) ) { if ( !resultClass.isAssignableFrom( javaTypeClass ) ) {
if ( expressibleJavaType instanceof PrimitiveJavaType ) { if ( expressibleJavaType instanceof PrimitiveJavaType ) {
if ( ( (PrimitiveJavaType) expressibleJavaType ).getPrimitiveClass() == resultClass ) { if ( ( (PrimitiveJavaType<?>) expressibleJavaType ).getPrimitiveClass() == resultClass ) {
return; return;
} }
throwQueryTypeMismatchException( resultClass, sqmExpressible ); throwQueryTypeMismatchException( resultClass, sqmExpressible );
@ -887,7 +902,7 @@ public abstract class AbstractSelectionQuery<R>
} }
@Override @Override
public SelectionQuery<R> setParameterList(String name, Collection values) { public SelectionQuery<R> setParameterList(String name, @SuppressWarnings("rawtypes") Collection values) {
super.setParameterList( name, values ); super.setParameterList( name, values );
return this; return this;
} }
@ -995,7 +1010,7 @@ public abstract class AbstractSelectionQuery<R>
} }
@Override @Override
public SelectionQuery<R> setProperties(Map map) { public SelectionQuery<R> setProperties(@SuppressWarnings("rawtypes") Map map) {
super.setProperties( map ); super.setProperties( map );
return this; return this;
} }

View File

@ -149,7 +149,7 @@ public interface SqmQuery extends CommonQueryContract {
SqmQuery setProperties(Object bean); SqmQuery setProperties(Object bean);
@Override @Override
SqmQuery setProperties(Map bean); SqmQuery setProperties(@SuppressWarnings("rawtypes") Map bean);
@Override @Override
SqmQuery setHibernateFlushMode(FlushMode flushMode); SqmQuery setHibernateFlushMode(FlushMode flushMode);

View File

@ -82,7 +82,7 @@ public interface SqmSelectionQuery<R> extends SqmQuery, SelectionQuery<R> {
SqmSelectionQuery<R> setParameter(Parameter<Date> param, Date value, TemporalType temporalType); SqmSelectionQuery<R> setParameter(Parameter<Date> param, Date value, TemporalType temporalType);
@Override @Override
SqmSelectionQuery<R> setParameterList(String name, Collection values); SqmSelectionQuery<R> setParameterList(String name, @SuppressWarnings("rawtypes") Collection values);
@Override @Override
<P> SqmSelectionQuery<R> setParameterList(String name, Collection<? extends P> values, Class<P> javaType); <P> SqmSelectionQuery<R> setParameterList(String name, Collection<? extends P> values, Class<P> javaType);
@ -100,7 +100,7 @@ public interface SqmSelectionQuery<R> extends SqmQuery, SelectionQuery<R> {
<P> SqmSelectionQuery<R> setParameterList(String name, P[] values, BindableType<P> type); <P> SqmSelectionQuery<R> setParameterList(String name, P[] values, BindableType<P> type);
@Override @Override
SqmSelectionQuery<R> setParameterList(int position, Collection values); SqmSelectionQuery<R> setParameterList(int position, @SuppressWarnings("rawtypes") Collection values);
@Override @Override
<P> SqmSelectionQuery<R> setParameterList(int position, Collection<? extends P> values, Class<P> javaType); <P> SqmSelectionQuery<R> setParameterList(int position, Collection<? extends P> values, Class<P> javaType);
@ -139,7 +139,7 @@ public interface SqmSelectionQuery<R> extends SqmQuery, SelectionQuery<R> {
SqmSelectionQuery<R> setProperties(Object bean); SqmSelectionQuery<R> setProperties(Object bean);
@Override @Override
SqmSelectionQuery<R> setProperties(Map bean); SqmSelectionQuery<R> setProperties(@SuppressWarnings("rawtypes") Map bean);
@Override @Override
SqmSelectionQuery<R> setHibernateFlushMode(FlushMode flushMode); SqmSelectionQuery<R> setHibernateFlushMode(FlushMode flushMode);

View File

@ -11,6 +11,8 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import jakarta.persistence.Tuple;
import org.hibernate.AssertionFailure;
import org.hibernate.ScrollMode; import org.hibernate.ScrollMode;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.jdbc.spi.JdbcServices;
@ -41,7 +43,6 @@ import org.hibernate.query.sqm.tree.select.SqmSelection;
import org.hibernate.sql.ast.SqlAstTranslator; import org.hibernate.sql.ast.SqlAstTranslator;
import org.hibernate.sql.ast.SqlAstTranslatorFactory; import org.hibernate.sql.ast.SqlAstTranslatorFactory;
import org.hibernate.sql.ast.spi.FromClauseAccess; import org.hibernate.sql.ast.spi.FromClauseAccess;
import org.hibernate.sql.ast.tree.expression.JdbcParameter;
import org.hibernate.sql.ast.tree.select.SelectStatement; import org.hibernate.sql.ast.tree.select.SelectStatement;
import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect;
import org.hibernate.sql.exec.spi.JdbcParameterBindings; import org.hibernate.sql.exec.spi.JdbcParameterBindings;
@ -50,6 +51,8 @@ import org.hibernate.sql.exec.spi.JdbcSelectExecutor;
import org.hibernate.sql.results.graph.entity.LoadingEntityEntry; import org.hibernate.sql.results.graph.entity.LoadingEntityEntry;
import org.hibernate.sql.results.internal.RowTransformerArrayImpl; import org.hibernate.sql.results.internal.RowTransformerArrayImpl;
import org.hibernate.sql.results.internal.RowTransformerJpaTupleImpl; import org.hibernate.sql.results.internal.RowTransformerJpaTupleImpl;
import org.hibernate.sql.results.internal.RowTransformerListImpl;
import org.hibernate.sql.results.internal.RowTransformerMapImpl;
import org.hibernate.sql.results.internal.RowTransformerSingularReturnImpl; import org.hibernate.sql.results.internal.RowTransformerSingularReturnImpl;
import org.hibernate.sql.results.internal.RowTransformerStandardImpl; import org.hibernate.sql.results.internal.RowTransformerStandardImpl;
import org.hibernate.sql.results.internal.RowTransformerTupleTransformerAdapter; import org.hibernate.sql.results.internal.RowTransformerTupleTransformerAdapter;
@ -183,22 +186,26 @@ public class ConcreteSqmSelectQueryPlan<R> implements SelectQueryPlan<R> {
if ( resultType == Object[].class ) { if ( resultType == Object[].class ) {
return (RowTransformer<T>) RowTransformerArrayImpl.instance(); return (RowTransformer<T>) RowTransformerArrayImpl.instance();
} }
else if ( List.class.equals( resultType ) ) {
return (RowTransformer<T>) RowTransformerListImpl.instance();
}
// NOTE : if we get here : // NOTE : if we get here :
// 1) there is no TupleTransformer specified // 1) there is no TupleTransformer specified
// 2) an explicit result-type, other than an array, was specified // 2) an explicit result-type, other than an array, was specified
final List<SqmSelection<?>> selections = sqm.getQueryPart().getFirstQuerySpec().getSelectClause().getSelections(); final List<SqmSelection<?>> selections =
sqm.getQueryPart().getFirstQuerySpec().getSelectClause().getSelections();
if ( tupleMetadata != null ) { if ( tupleMetadata != null ) {
// resultType is Tuple.. if ( Tuple.class.equals( resultType ) ) {
if ( queryOptions.getTupleTransformer() == null ) {
return (RowTransformer<T>) new RowTransformerJpaTupleImpl( tupleMetadata ); return (RowTransformer<T>) new RowTransformerJpaTupleImpl( tupleMetadata );
} }
else if ( Map.class.equals( resultType ) ) {
throw new IllegalArgumentException( return (RowTransformer<T>) new RowTransformerMapImpl( tupleMetadata );
"Illegal combination of Tuple resultType and (non-JpaTupleBuilder) TupleTransformer : " + }
queryOptions.getTupleTransformer() else {
); throw new AssertionFailure( "Wrong result type for tuple handling: " + resultType );
}
} }
// NOTE : if we get here we have a resultType of some kind // NOTE : if we get here we have a resultType of some kind

View File

@ -218,7 +218,7 @@ public class QuerySqmImpl<R>
*/ */
public QuerySqmImpl( public QuerySqmImpl(
SqmStatement<R> criteria, SqmStatement<R> criteria,
Class<R> resultType, Class<R> expectedResultType,
SharedSessionContractImplementor producer) { SharedSessionContractImplementor producer) {
super( producer ); super( producer );
this.hql = CRITERIA_HQL_STRING; this.hql = CRITERIA_HQL_STRING;
@ -262,12 +262,12 @@ public class QuerySqmImpl<R>
queryPart.validateQueryStructureAndFetchOwners(); queryPart.validateQueryStructureAndFetchOwners();
visitQueryReturnType( visitQueryReturnType(
queryPart, queryPart,
resultType, expectedResultType,
producer.getFactory() producer.getFactory()
); );
} }
else { else {
if ( resultType != null ) { if ( expectedResultType != null ) {
throw new IllegalQueryOperationException( throw new IllegalQueryOperationException(
"Result type given for a non-SELECT Query", "Result type given for a non-SELECT Query",
hql, hql,
@ -288,8 +288,8 @@ public class QuerySqmImpl<R>
} }
} }
this.resultType = resultType; this.resultType = expectedResultType;
this.tupleMetadata = buildTupleMetadata( criteria, resultType ); this.tupleMetadata = buildTupleMetadata( criteria, expectedResultType );
} }
private void validateStatement(SqmStatement<R> sqmStatement, Class<R> resultType) { private void validateStatement(SqmStatement<R> sqmStatement, Class<R> resultType) {

View File

@ -21,7 +21,6 @@ import jakarta.persistence.FlushModeType;
import jakarta.persistence.LockModeType; import jakarta.persistence.LockModeType;
import jakarta.persistence.Parameter; import jakarta.persistence.Parameter;
import jakarta.persistence.TemporalType; import jakarta.persistence.TemporalType;
import jakarta.persistence.Tuple;
import org.hibernate.CacheMode; import org.hibernate.CacheMode;
import org.hibernate.FlushMode; import org.hibernate.FlushMode;
@ -55,7 +54,6 @@ import org.hibernate.query.spi.ScrollableResultsImplementor;
import org.hibernate.query.spi.SelectQueryPlan; import org.hibernate.query.spi.SelectQueryPlan;
import org.hibernate.query.sqm.SqmSelectionQuery; import org.hibernate.query.sqm.SqmSelectionQuery;
import org.hibernate.query.sqm.internal.SqmInterpretationsKey.InterpretationsKeySource; import org.hibernate.query.sqm.internal.SqmInterpretationsKey.InterpretationsKeySource;
import org.hibernate.query.sqm.tree.SqmCopyContext;
import org.hibernate.query.sqm.tree.expression.JpaCriteriaParameter; import org.hibernate.query.sqm.tree.expression.JpaCriteriaParameter;
import org.hibernate.query.sqm.tree.expression.SqmJpaCriteriaParameterWrapper; import org.hibernate.query.sqm.tree.expression.SqmJpaCriteriaParameterWrapper;
import org.hibernate.query.sqm.tree.expression.SqmParameter; import org.hibernate.query.sqm.tree.expression.SqmParameter;
@ -74,6 +72,8 @@ import static org.hibernate.jpa.LegacySpecHints.HINT_JAVAEE_CACHE_STORE_MODE;
import static org.hibernate.jpa.SpecHints.HINT_SPEC_CACHE_RETRIEVE_MODE; import static org.hibernate.jpa.SpecHints.HINT_SPEC_CACHE_RETRIEVE_MODE;
import static org.hibernate.jpa.SpecHints.HINT_SPEC_CACHE_STORE_MODE; import static org.hibernate.jpa.SpecHints.HINT_SPEC_CACHE_STORE_MODE;
import static org.hibernate.query.spi.SqlOmittingQueryOptions.omitSqlQueryOptions; import static org.hibernate.query.spi.SqlOmittingQueryOptions.omitSqlQueryOptions;
import static org.hibernate.query.sqm.internal.SqmInterpretationsKey.createInterpretationsKey;
import static org.hibernate.query.sqm.tree.SqmCopyContext.simpleContext;
/** /**
* @author Steve Ebersole * @author Steve Ebersole
@ -86,7 +86,8 @@ public class SqmSelectionQueryImpl<R> extends AbstractSelectionQuery<R> implemen
private final DomainParameterXref domainParameterXref; private final DomainParameterXref domainParameterXref;
private final QueryParameterBindingsImpl parameterBindings; private final QueryParameterBindingsImpl parameterBindings;
private final Class<R> resultType; private final Class<R> expectedResultType;
private final Class<?> resultType;
private final TupleMetadata tupleMetadata; private final TupleMetadata tupleMetadata;
public SqmSelectionQueryImpl( public SqmSelectionQueryImpl(
@ -104,30 +105,35 @@ public class SqmSelectionQueryImpl<R> extends AbstractSelectionQuery<R> implemen
this.domainParameterXref = hqlInterpretation.getDomainParameterXref(); this.domainParameterXref = hqlInterpretation.getDomainParameterXref();
this.parameterBindings = QueryParameterBindingsImpl.from( parameterMetadata, session.getFactory() ); this.parameterBindings = QueryParameterBindingsImpl.from( parameterMetadata, session.getFactory() );
this.expectedResultType = expectedResultType;
// visitQueryReturnType( sqm.getQueryPart(), expectedResultType, getSessionFactory() ); // visitQueryReturnType( sqm.getQueryPart(), expectedResultType, getSessionFactory() );
this.resultType = determineResultType( sqm, expectedResultType ); this.resultType = determineResultType( sqm );
setComment( hql ); setComment( hql );
this.tupleMetadata = buildTupleMetadata( sqm, expectedResultType ); this.tupleMetadata = buildTupleMetadata( sqm, expectedResultType );
} }
private static <T> Class<T> determineResultType(SqmSelectStatement<?> sqm, Class<?> expectedResultType) { private Class<?> determineResultType(SqmSelectStatement<?> sqm) {
if ( expectedResultType != null && expectedResultType.equals( Tuple.class ) ) { if ( expectedResultType != null ) {
//noinspection unchecked if ( expectedResultType.isArray() ) {
return (Class<T>) Tuple.class; return Object[].class;
} }
else if ( List.class.isAssignableFrom( expectedResultType ) ) {
if ( expectedResultType == null || ! expectedResultType.isArray() ) { return expectedResultType;
final List<SqmSelection<?>> selections = sqm.getQuerySpec().getSelectClause().getSelections(); }
if ( selections.size() == 1 ) { else if ( isTupleResultClass( expectedResultType ) ) {
final SqmSelection<?> sqmSelection = selections.get( 0 ); return expectedResultType;
//noinspection unchecked }
return (Class<T>) sqmSelection.getNodeJavaType().getJavaTypeClass(); else {
return Object[].class;
} }
} }
else {
//noinspection unchecked final List<SqmSelection<?>> selections = sqm.getQuerySpec().getSelectClause().getSelections();
return (Class<T>) Object[].class; return selections.size() == 1
? selections.get(0).getNodeJavaType().getJavaTypeClass()
: Object[].class;
}
} }
public SqmSelectionQueryImpl( public SqmSelectionQueryImpl(
@ -136,6 +142,7 @@ public class SqmSelectionQueryImpl<R> extends AbstractSelectionQuery<R> implemen
SharedSessionContractImplementor session) { SharedSessionContractImplementor session) {
super( session ); super( session );
this.hql = memento.getHqlString(); this.hql = memento.getHqlString();
this.expectedResultType = resultType;
this.resultType = resultType; this.resultType = resultType;
final SessionFactoryImplementor factory = session.getFactory(); final SessionFactoryImplementor factory = session.getFactory();
@ -164,10 +171,10 @@ public class SqmSelectionQueryImpl<R> extends AbstractSelectionQuery<R> implemen
public SqmSelectionQueryImpl( public SqmSelectionQueryImpl(
NamedCriteriaQueryMementoImpl memento, NamedCriteriaQueryMementoImpl memento,
Class<R> resultType, Class<R> expectedResultType,
SharedSessionContractImplementor session) { SharedSessionContractImplementor session) {
//noinspection unchecked //noinspection unchecked
this( (SqmSelectStatement<R>) memento.getSqmStatement(), resultType, session); this( (SqmSelectStatement<R>) memento.getSqmStatement(), expectedResultType, session );
applyOptions( memento ); applyOptions( memento );
} }
@ -177,27 +184,20 @@ public class SqmSelectionQueryImpl<R> extends AbstractSelectionQuery<R> implemen
SharedSessionContractImplementor session) { SharedSessionContractImplementor session) {
super( session ); super( session );
this.hql = CRITERIA_HQL_STRING; this.hql = CRITERIA_HQL_STRING;
if ( session.isCriteriaCopyTreeEnabled() ) { this.sqm = session.isCriteriaCopyTreeEnabled() ? criteria.copy( simpleContext() ) : criteria;
this.sqm = criteria.copy( SqmCopyContext.simpleContext() );
}
else {
this.sqm = criteria;
}
this.domainParameterXref = DomainParameterXref.from( sqm ); this.domainParameterXref = DomainParameterXref.from( sqm );
if ( ! domainParameterXref.hasParameters() ) { this.parameterMetadata = domainParameterXref.hasParameters()
this.parameterMetadata = ParameterMetadataImpl.EMPTY; ? new ParameterMetadataImpl( domainParameterXref.getQueryParameters() )
} : ParameterMetadataImpl.EMPTY;
else {
this.parameterMetadata = new ParameterMetadataImpl( domainParameterXref.getQueryParameters() );
}
this.parameterBindings = QueryParameterBindingsImpl.from( parameterMetadata, session.getFactory() ); this.parameterBindings = QueryParameterBindingsImpl.from( parameterMetadata, session.getFactory() );
// Parameters might be created through HibernateCriteriaBuilder.value which we need to bind here // Parameters might be created through HibernateCriteriaBuilder.value which we need to bind here
for ( SqmParameter<?> sqmParameter : this.domainParameterXref.getParameterResolutions().getSqmParameters() ) { for ( SqmParameter<?> sqmParameter : domainParameterXref.getParameterResolutions().getSqmParameters() ) {
if ( sqmParameter instanceof SqmJpaCriteriaParameterWrapper<?> ) { if ( sqmParameter instanceof SqmJpaCriteriaParameterWrapper<?> ) {
final JpaCriteriaParameter<Object> jpaCriteriaParameter = ( (SqmJpaCriteriaParameterWrapper<Object>) sqmParameter ).getJpaCriteriaParameter(); final JpaCriteriaParameter<Object> jpaCriteriaParameter =
( (SqmJpaCriteriaParameterWrapper<Object>) sqmParameter ).getJpaCriteriaParameter();
final Object value = jpaCriteriaParameter.getValue(); final Object value = jpaCriteriaParameter.getValue();
// We don't set a null value, unless the type is also null which is the case when using HibernateCriteriaBuilder.value // We don't set a null value, unless the type is also null which is the case when using HibernateCriteriaBuilder.value
if ( value != null || jpaCriteriaParameter.getNodeType() == null ) { if ( value != null || jpaCriteriaParameter.getNodeType() == null ) {
@ -209,7 +209,8 @@ public class SqmSelectionQueryImpl<R> extends AbstractSelectionQuery<R> implemen
} }
} }
this.resultType = determineResultType( sqm, expectedResultType ); this.expectedResultType = expectedResultType;
this.resultType = determineResultType( sqm );
visitQueryReturnType( sqm.getQueryPart(), expectedResultType, getSessionFactory() ); visitQueryReturnType( sqm.getQueryPart(), expectedResultType, getSessionFactory() );
setComment( hql ); setComment( hql );
@ -336,12 +337,10 @@ public class SqmSelectionQueryImpl<R> extends AbstractSelectionQuery<R> implemen
// Query plan // Query plan
private SelectQueryPlan<R> resolveQueryPlan() { private SelectQueryPlan<R> resolveQueryPlan() {
final QueryInterpretationCache.Key cacheKey = SqmInterpretationsKey.createInterpretationsKey( this ); final QueryInterpretationCache.Key cacheKey = createInterpretationsKey( this );
if ( cacheKey != null ) { if ( cacheKey != null ) {
return getSession().getFactory().getQueryEngine().getInterpretationCache().resolveSelectQueryPlan( return getSession().getFactory().getQueryEngine().getInterpretationCache()
cacheKey, .resolveSelectQueryPlan( cacheKey, this::buildQueryPlan );
this::buildQueryPlan
);
} }
else { else {
return buildQueryPlan(); return buildQueryPlan();
@ -354,40 +353,29 @@ public class SqmSelectionQueryImpl<R> extends AbstractSelectionQuery<R> implemen
getSession().getFactory() getSession().getFactory()
); );
if ( concreteSqmStatements.length > 1 ) { return concreteSqmStatements.length > 1
return buildAggregatedQueryPlan( concreteSqmStatements ); ? buildAggregatedQueryPlan( concreteSqmStatements )
} : buildConcreteQueryPlan( concreteSqmStatements[0], getQueryOptions() );
else {
return buildConcreteQueryPlan( concreteSqmStatements[0], getResultType(), getQueryOptions() );
}
} }
private SelectQueryPlan<R> buildAggregatedQueryPlan(SqmSelectStatement<?>[] concreteSqmStatements) { private SelectQueryPlan<R> buildAggregatedQueryPlan(SqmSelectStatement<?>[] concreteSqmStatements) {
//noinspection unchecked //noinspection unchecked
final SelectQueryPlan<R>[] aggregatedQueryPlans = new SelectQueryPlan[ concreteSqmStatements.length ]; final SelectQueryPlan<R>[] aggregatedQueryPlans = new SelectQueryPlan[ concreteSqmStatements.length ];
// todo (6.0) : we want to make sure that certain thing (ResultListTransformer, etc) only get applied at the aggregator-level // todo (6.0) : we want to make sure that certain thing (ResultListTransformer, etc) only get applied at the aggregator-level
for ( int i = 0, x = concreteSqmStatements.length; i < x; i++ ) { for ( int i = 0, x = concreteSqmStatements.length; i < x; i++ ) {
aggregatedQueryPlans[i] = buildConcreteQueryPlan( aggregatedQueryPlans[i] = buildConcreteQueryPlan( concreteSqmStatements[i], getQueryOptions() );
concreteSqmStatements[i],
getResultType(),
getQueryOptions()
);
} }
return new AggregatedSelectQueryPlanImpl<>( aggregatedQueryPlans ); return new AggregatedSelectQueryPlanImpl<>( aggregatedQueryPlans );
} }
private <T> SelectQueryPlan<T> buildConcreteQueryPlan( private SelectQueryPlan<R> buildConcreteQueryPlan(
SqmSelectStatement<?> concreteSqmStatement, SqmSelectStatement<?> concreteSqmStatement,
Class<T> resultType,
QueryOptions queryOptions) { QueryOptions queryOptions) {
return new ConcreteSqmSelectQueryPlan<>( return new ConcreteSqmSelectQueryPlan<>(
concreteSqmStatement, concreteSqmStatement,
getQueryString(), getQueryString(),
getDomainParameterXref(), getDomainParameterXref(),
resultType, expectedResultType,
tupleMetadata, tupleMetadata,
queryOptions queryOptions
); );
@ -399,7 +387,7 @@ public class SqmSelectionQueryImpl<R> extends AbstractSelectionQuery<R> implemen
// InterpretationsKeySource // InterpretationsKeySource
@Override @Override
public Class<R> getResultType() { public Class<?> getResultType() {
return resultType; return resultType;
} }
@ -684,7 +672,7 @@ public class SqmSelectionQueryImpl<R> extends AbstractSelectionQuery<R> implemen
} }
@Override @Override
public SqmSelectionQuery<R> setParameterList(String name, Collection values) { public SqmSelectionQuery<R> setParameterList(String name, @SuppressWarnings("rawtypes") Collection values) {
super.setParameterList( name, values ); super.setParameterList( name, values );
return this; return this;
} }
@ -720,7 +708,7 @@ public class SqmSelectionQueryImpl<R> extends AbstractSelectionQuery<R> implemen
} }
@Override @Override
public SqmSelectionQuery<R> setParameterList(int position, Collection values) { public SqmSelectionQuery<R> setParameterList(int position, @SuppressWarnings("rawtypes") Collection values) {
super.setParameterList( position, values ); super.setParameterList( position, values );
return this; return this;
} }
@ -792,7 +780,7 @@ public class SqmSelectionQueryImpl<R> extends AbstractSelectionQuery<R> implemen
} }
@Override @Override
public SqmSelectionQuery<R> setProperties(Map map) { public SqmSelectionQuery<R> setProperties(@SuppressWarnings("rawtypes") Map map) {
super.setProperties( map ); super.setProperties( map );
return this; return this;
} }

View File

@ -0,0 +1,32 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.sql.results.internal;
import org.hibernate.sql.results.spi.RowTransformer;
import java.util.List;
/**
* RowTransformer used when an array is explicitly specified as the return type
*
* @author Steve Ebersole
*/
public class RowTransformerListImpl<T> implements RowTransformer<List<Object>> {
/**
* Singleton access
*/
public static final RowTransformerListImpl INSTANCE = new RowTransformerListImpl();
public static RowTransformerListImpl instance() {
return INSTANCE;
}
@Override
public List<Object> transformRow(Object[] row) {
return List.of( row );
}
}

View File

@ -0,0 +1,48 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.sql.results.internal;
import jakarta.persistence.Tuple;
import jakarta.persistence.TupleElement;
import org.hibernate.sql.results.spi.RowTransformer;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* RowTransformer generating a JPA {@link Tuple}
*
* @author Steve Ebersole
*/
public class RowTransformerMapImpl implements RowTransformer<Map<String,Object>> {
private final TupleMetadata tupleMetadata;
public RowTransformerMapImpl(TupleMetadata tupleMetadata) {
this.tupleMetadata = tupleMetadata;
}
@Override
public Map<String,Object> transformRow(Object[] row) {
Map<String,Object> map = new HashMap<>( row.length );
List<TupleElement<?>> list = tupleMetadata.getList();
for ( int i = 0; i < row.length; i++ ) {
String alias = list.get(i).getAlias();
if ( alias == null ) {
alias = Integer.toString(i);
}
map.put( alias, row[i] );
}
return map;
}
@Override
public int determineNumberOfResultElements(int rawElementCount) {
return 1;
}
}

View File

@ -10,9 +10,10 @@ import java.util.Arrays;
import java.util.List; import java.util.List;
import jakarta.persistence.TupleElement; import jakarta.persistence.TupleElement;
import org.hibernate.internal.util.type.PrimitiveWrapperHelper;
import org.hibernate.query.JpaTuple; import org.hibernate.query.JpaTuple;
import static org.hibernate.internal.util.type.PrimitiveWrapperHelper.getDescriptorByPrimitiveType;
/** /**
* Implementation of the JPA Tuple contract * Implementation of the JPA Tuple contract
* *
@ -45,7 +46,7 @@ public class TupleImpl implements JpaTuple {
public <X> X get(String alias, Class<X> type) { public <X> X get(String alias, Class<X> type) {
final Object untyped = get( alias ); final Object untyped = get( alias );
if ( untyped != null ) { if ( untyped != null ) {
if (!elementTypeMatches(type, untyped)) { if ( !elementTypeMatches( type, untyped ) ) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
String.format( String.format(
"Requested tuple value [alias=%s, value=%s] cannot be assigned to requested type [%s]", "Requested tuple value [alias=%s, value=%s] cannot be assigned to requested type [%s]",
@ -99,9 +100,8 @@ public class TupleImpl implements JpaTuple {
} }
private <X> boolean elementTypeMatches(Class<X> type, Object untyped) { private <X> boolean elementTypeMatches(Class<X> type, Object untyped) {
return type.isInstance(untyped) return type.isInstance( untyped )
|| type.isPrimitive() || type.isPrimitive() && getDescriptorByPrimitiveType( type ).getWrapperClass().isInstance( untyped );
&& PrimitiveWrapperHelper.getDescriptorByPrimitiveType( type).getWrapperClass().isInstance( untyped);
} }
@Override @Override

View File

@ -0,0 +1,135 @@
package org.hibernate.orm.test.query.hql;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.persistence.Tuple;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.Test;
import java.util.List;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
@DomainModel(
annotatedClasses = {
ImplicitInstantiationTest.Thing.class
}
)
@SessionFactory
public class ImplicitInstantiationTest {
@Test
public void testTupleInstantiationWithAlias(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
session.persist(new Thing(1L, "thing"));
Tuple result = session.createQuery("select id as id, upper(name) as name from Thing", Tuple.class).getSingleResult();
assertEquals( result.get("id"), 1L );
assertEquals( result.get("name"), "THING" );
session.getTransaction().setRollbackOnly();
}
);
}
@Test
public void testTupleInstantiationWithoutAlias(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
session.persist(new Thing(1L, "thing"));
Tuple result = session.createSelectionQuery("select id, upper(name) from Thing", Tuple.class).getSingleResult();
assertEquals( result.get(0), 1L );
assertEquals( result.get(1), "THING" );
session.getTransaction().setRollbackOnly();
}
);
}
@Test
public void testMapInstantiationWithoutAlias(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
session.persist(new Thing(1L, "thing"));
Map result = session.createSelectionQuery("select id, upper(name) from Thing", Map.class).getSingleResult();
assertEquals( result.get("0"), 1L );
assertEquals( result.get("1"), "THING" );
session.getTransaction().setRollbackOnly();
}
);
}
@Test
public void testMapInstantiationWithAlias(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
session.persist(new Thing(1L, "thing"));
Map result = session.createQuery("select id as id, upper(name) as name from Thing", Map.class).getSingleResult();
assertEquals( result.get("id"), 1L );
assertEquals( result.get("name"), "THING" );
session.getTransaction().setRollbackOnly();
}
);
}
@Test
public void testListInstantiationWithoutAlias(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
session.persist(new Thing(1L, "thing"));
List result = session.createSelectionQuery("select id, upper(name) from Thing", List.class).getSingleResult();
assertEquals( result.get(0), 1L );
assertEquals( result.get(1), "THING" );
session.getTransaction().setRollbackOnly();
}
);
}
@Test
public void testListInstantiationWithAlias(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
session.persist(new Thing(1L, "thing"));
List result = session.createQuery("select id as id, upper(name) as name from Thing", List.class).getSingleResult();
assertEquals( result.get(0), 1L );
assertEquals( result.get(1), "THING" );
session.getTransaction().setRollbackOnly();
}
);
}
@Entity(name = "Thing")
@Table(name = "thingy_table")
public class Thing {
private Long id;
private String name;
public Thing(Long id, String name) {
this.id = id;
this.name = name;
}
Thing() {
}
@Id
public Long getId() {
return this.id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}