Skip to content

Commit

Permalink
experiment with allowing single-item auto-instantiation
Browse files Browse the repository at this point in the history
  • Loading branch information
gavinking committed Feb 21, 2024
1 parent bb477c1 commit 77f7d68
Show file tree
Hide file tree
Showing 19 changed files with 367 additions and 110 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
import org.hibernate.HibernateException;
import org.hibernate.LockMode;
import org.hibernate.LockOptions;
import org.hibernate.TypeMismatchException;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.graph.GraphSemantic;
import org.hibernate.internal.EntityManagerMessageLogger;
Expand Down Expand Up @@ -654,9 +653,6 @@ public int executeUpdate() throws HibernateException {
catch (IllegalQueryOperationException e) {
throw new IllegalStateException( e );
}
catch (TypeMismatchException e) {
throw new IllegalArgumentException( e );
}
catch (HibernateException e) {
throw getSession().getExceptionConverter().convert( e );
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@
import org.hibernate.LockOptions;
import org.hibernate.NonUniqueResultException;
import org.hibernate.ScrollMode;
import org.hibernate.TypeMismatchException;
import org.hibernate.UnknownProfileException;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
Expand Down Expand Up @@ -89,6 +88,8 @@
import static org.hibernate.jpa.QueryHints.HINT_FETCH_SIZE;
import static org.hibernate.jpa.QueryHints.HINT_FOLLOW_ON_LOCKING;
import static org.hibernate.jpa.QueryHints.HINT_READONLY;
import static org.hibernate.query.sqm.internal.SqmUtil.isHqlTuple;
import static org.hibernate.query.sqm.internal.SqmUtil.isSelectionAssignableToResultType;

/**
* @author Steve Ebersole
Expand All @@ -108,26 +109,26 @@ public AbstractSelectionQuery(SharedSessionContractImplementor session) {
}

protected TupleMetadata buildTupleMetadata(SqmStatement<?> statement, Class<R> resultType) {
if ( isInstantiableWithoutMetadata( resultType ) ) {
// no need to build metadata for instantiating tuples
return null;
}
else {
if ( statement instanceof SqmSelectStatement<?> ) {
final SqmSelectStatement<?> select = (SqmSelectStatement<?>) statement;
final List<SqmSelection<?>> selections =
select.getQueryPart().getFirstQuerySpec().getSelectClause()
.getSelections();
if ( Tuple.class.equals( resultType ) || selections.size() > 1 ) {
return getTupleMetadata( selections );
}
else {
// only one element in select list,
// we don't support instantiation
return null;
}
return isTupleMetadataRequired( resultType, selections.get(0) )
? getTupleMetadata( selections )
: null;
}
else {
return null;
}
}

private static <R> boolean isTupleMetadataRequired(Class<R> resultType, SqmSelection<?> selection) {
return isHqlTuple( selection )
|| !isInstantiableWithoutMetadata( resultType )
&& !isSelectionAssignableToResultType( selection, resultType );
}

private TupleMetadata getTupleMetadata(List<SqmSelection<?>> selections) {
if ( getQueryOptions().getTupleTransformer() == null ) {
return new TupleMetadata( buildTupleElementArray( selections ), buildTupleAliasArray( selections ) );
Expand Down Expand Up @@ -431,9 +432,6 @@ public List<R> list() {
catch (IllegalQueryOperationException e) {
throw new IllegalStateException( e );
}
catch (TypeMismatchException e) {
throw new IllegalArgumentException( e );
}
catch (HibernateException he) {
throw getSession().getExceptionConverter().convert( he, getQueryOptions().getLockOptions() );
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,13 @@
import java.util.Map;

import jakarta.persistence.Tuple;
import org.hibernate.AssertionFailure;
import org.hibernate.InstantiationException;
import org.hibernate.ScrollMode;
import org.hibernate.engine.spi.EntityHolder;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.engine.spi.SubselectFetch;
import org.hibernate.internal.EmptyScrollableResults;
import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.metamodel.mapping.MappingModelExpressible;
import org.hibernate.query.Query;
import org.hibernate.query.TupleTransformer;
Expand All @@ -43,6 +41,7 @@
import org.hibernate.sql.exec.spi.JdbcParametersList;
import org.hibernate.sql.exec.spi.JdbcSelectExecutor;
import org.hibernate.sql.results.internal.RowTransformerArrayImpl;
import org.hibernate.sql.results.internal.RowTransformerCheckingImpl;
import org.hibernate.sql.results.internal.RowTransformerConstructorImpl;
import org.hibernate.sql.results.internal.RowTransformerJpaTupleImpl;
import org.hibernate.sql.results.internal.RowTransformerListImpl;
Expand All @@ -56,7 +55,9 @@
import org.hibernate.sql.results.spi.RowTransformer;

import static org.hibernate.internal.util.ReflectHelper.isClass;
import static org.hibernate.internal.util.collections.ArrayHelper.toStringArray;
import static org.hibernate.query.sqm.internal.QuerySqmImpl.CRITERIA_HQL_STRING;
import static org.hibernate.query.sqm.internal.SqmUtil.isSelectionAssignableToResultType;

/**
* Standard Hibernate implementation of SelectQueryPlan for SQM-backed
Expand Down Expand Up @@ -203,12 +204,6 @@ private static SqmSelection<?> singleSelection(SqmSelectStatement<?> sqm) {
return selections.size() == 1 ? selections.get( 0 ) : null;
}

private static Class<?> selectionType(SqmSelection<?> selection) {
return selection != null && !selection.getSelectableNode().isCompoundSelection() ?
selection.getNodeJavaType().getJavaTypeClass()
: null;
}

@SuppressWarnings("unchecked")
protected static <T> RowTransformer<T> determineRowTransformer(
SqmSelectStatement<?> sqm,
Expand All @@ -223,40 +218,32 @@ else if ( resultType == null ) {
}
else {
final SqmSelection<?> selection = singleSelection( sqm );
if ( resultType.isArray() && resultType != selectionType( selection ) ) {
if ( isSelectionAssignableToResultType( selection, resultType ) ) {
return RowTransformerSingularReturnImpl.instance();
}
else if ( resultType.isArray() ) {
return (RowTransformer<T>) RowTransformerArrayImpl.instance();
}
else if ( resultType == List.class && resultType != selectionType( selection ) ) {
else if ( List.class.equals( resultType ) ) {
return (RowTransformer<T>) RowTransformerListImpl.instance();
}
else {
// NOTE : if we get here :
// 1) there is no TupleTransformer specified
// 2) an explicit result-type, other than an array or List, was specified

if ( tupleMetadata == null ) {
if ( selection != null ) {
return RowTransformerSingularReturnImpl.instance();
}
else {
throw new AssertionFailure( "Query defined multiple selections, should have had TupleMetadata" );
}
else if ( Tuple.class.equals( resultType ) ) {
return (RowTransformer<T>) new RowTransformerJpaTupleImpl( tupleMetadata );
}
else if ( Map.class.equals( resultType ) ) {
return (RowTransformer<T>) new RowTransformerMapImpl( tupleMetadata );
}
else if ( isClass( resultType ) ) {
try {
return new RowTransformerConstructorImpl<>( resultType, tupleMetadata );
}
else {
if ( Tuple.class.equals( resultType ) ) {
return (RowTransformer<T>) new RowTransformerJpaTupleImpl( tupleMetadata );
}
else if ( Map.class.equals( resultType ) ) {
return (RowTransformer<T>) new RowTransformerMapImpl( tupleMetadata );
}
else if ( isClass( resultType ) ) {
return new RowTransformerConstructorImpl<>( resultType, tupleMetadata );
}
else {
throw new InstantiationException( "Query result type is not instantiable", resultType );
}
catch (InstantiationException ie) {
return new RowTransformerCheckingImpl<>( resultType );
}
}
else {
return new RowTransformerCheckingImpl<>( resultType );
}
}
}

Expand All @@ -279,10 +266,7 @@ private static <T> RowTransformer<T> makeRowTransformerTupleTransformerAdapter(

@SuppressWarnings("unchecked")
TupleTransformer<T> tupleTransformer = (TupleTransformer<T>) queryOptions.getTupleTransformer();
return new RowTransformerTupleTransformerAdapter<T>(
ArrayHelper.toStringArray( aliases ),
tupleTransformer
);
return new RowTransformerTupleTransformerAdapter<>( toStringArray( aliases ), tupleTransformer );
}

@Override
Expand Down Expand Up @@ -424,10 +408,7 @@ private static CacheableSqmInterpretation buildCacheableSqmInterpretation(

final SqlAstTranslator<JdbcOperationQuerySelect> selectTranslator =
sessionFactory.getJdbcServices().getJdbcEnvironment().getSqlAstTranslatorFactory()
.buildSelectTranslator(
sessionFactory,
sqmInterpretation.getSqlAst()
);
.buildSelectTranslator( sessionFactory, sqmInterpretation.getSqlAst() );

final Map<QueryParameterImplementor<?>, Map<SqmParameter<?>, List<JdbcParametersList>>> jdbcParamsXref
= SqmUtil.generateJdbcParamsXref( domainParameterXref, sqmInterpretation::getJdbcParamsBySqmParam );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
import org.hibernate.LockMode;
import org.hibernate.LockOptions;
import org.hibernate.ScrollMode;
import org.hibernate.TypeMismatchException;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.engine.query.spi.EntityGraphQueryHint;
import org.hibernate.engine.spi.LoadQueryInfluencers;
Expand Down Expand Up @@ -692,9 +691,6 @@ public int executeUpdate() {
catch (IllegalQueryOperationException e) {
throw new IllegalStateException( e );
}
catch (TypeMismatchException e) {
throw new IllegalArgumentException( e );
}
catch (HibernateException e) {
throw getSession().getExceptionConverter().convert( e );
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@
import org.hibernate.sql.results.internal.TupleMetadata;
import org.hibernate.sql.results.spi.ResultsConsumer;
import org.hibernate.sql.results.spi.SingleResultConsumer;
import org.hibernate.type.descriptor.java.JavaType;

import static java.util.stream.Collectors.toList;
import static org.hibernate.jpa.HibernateHints.HINT_CACHEABLE;
Expand All @@ -82,6 +81,7 @@
import static org.hibernate.jpa.SpecHints.HINT_SPEC_CACHE_STORE_MODE;
import static org.hibernate.query.spi.SqlOmittingQueryOptions.omitSqlQueryOptions;
import static org.hibernate.query.sqm.internal.SqmInterpretationsKey.createInterpretationsKey;
import static org.hibernate.query.sqm.internal.SqmUtil.isSelectionAssignableToResultType;
import static org.hibernate.query.sqm.internal.SqmUtil.sortSpecification;

/**
Expand Down Expand Up @@ -131,17 +131,14 @@ private Class<?> determineResultType(SqmSelectStatement<?> sqm) {
}
else {
final SqmSelection<?> selection = selections.get(0);
if ( selection!=null ) {
final JavaType<?> javaType = selection.getNodeJavaType();
if ( javaType != null) {
return javaType.getJavaTypeClass();
}
if ( isSelectionAssignableToResultType( selection, expectedResultType ) ) {
return selection.getNodeJavaType().getJavaTypeClass();
}
else {
// let's assume there's some
// way to instantiate it
return expectedResultType;
}
// due to some error in the query,
// we don't have any information,
// so just let it through so the
// user sees the real error
return expectedResultType;
}
}
else if ( expectedResultType != null ) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,14 @@
import org.hibernate.query.sqm.tree.expression.SqmAliasedNodeRef;
import org.hibernate.query.sqm.tree.expression.SqmJpaCriteriaParameterWrapper;
import org.hibernate.query.sqm.tree.expression.SqmParameter;
import org.hibernate.query.sqm.tree.expression.SqmTuple;
import org.hibernate.query.sqm.tree.from.SqmFrom;
import org.hibernate.query.sqm.tree.from.SqmJoin;
import org.hibernate.query.sqm.tree.from.SqmQualifiedJoin;
import org.hibernate.query.sqm.tree.from.SqmRoot;
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
import org.hibernate.query.sqm.tree.select.SqmSelectableNode;
import org.hibernate.query.sqm.tree.select.SqmSelection;
import org.hibernate.query.sqm.tree.select.SqmSortSpecification;
import org.hibernate.spi.NavigablePath;
import org.hibernate.sql.ast.SqlTreeCreationException;
Expand All @@ -69,6 +71,7 @@
import org.hibernate.sql.exec.spi.JdbcParametersList;
import org.hibernate.type.JavaObjectType;
import org.hibernate.type.descriptor.converter.spi.BasicValueConverter;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.internal.BasicTypeImpl;
import org.hibernate.type.internal.ConvertedBasicTypeImpl;
import org.hibernate.type.spi.TypeConfiguration;
Expand Down Expand Up @@ -601,6 +604,26 @@ static <T> JpaOrder sortSpecification(SqmSelectStatement<T> sqm, Order<? super T
}
}

public static boolean isSelectionAssignableToResultType(SqmSelection<?> selection, Class<?> expectedResultType) {
if ( expectedResultType == null
|| selection != null && selection.getSelectableNode() instanceof SqmParameter ) {
return true;
}
else if ( selection == null
|| !isHqlTuple( selection ) && selection.getSelectableNode().isCompoundSelection() ) {
return false;
}
else {
final JavaType<?> nodeJavaType = selection.getNodeJavaType();
return nodeJavaType != null
&& expectedResultType.isAssignableFrom( nodeJavaType.getJavaTypeClass() );
}
}

public static boolean isHqlTuple(SqmSelection<?> selection) {
return selection != null && selection.getSelectableNode() instanceof SqmTuple;
}

private static class CriteriaParameterCollector {
private Set<SqmParameter<?>> sqmParameters;
private Map<JpaCriteriaParameter<?>, List<SqmJpaCriteriaParameterWrapper<?>>> jpaCriteriaParamResolutions;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* 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.TypeMismatchException;
import org.hibernate.sql.results.spi.RowTransformer;

import java.util.Map;

/**
* @author Gavin King
*/
public class RowTransformerCheckingImpl<R> implements RowTransformer<R> {

private final Class<R> type;
private final Class<?> objectType;

private static final Map<Class<?>,Class<?>> WRAPPERS
= Map.of(
boolean.class, Boolean.class,
int.class, Integer.class,
long.class, Long.class,
short.class, Short.class,
byte.class, Byte.class,
float.class, Float.class,
double.class, Double.class,
char.class, Character.class
);

public RowTransformerCheckingImpl(Class<R> type) {
this.type = type;
objectType = WRAPPERS.getOrDefault(type, type);
}

@Override
@SuppressWarnings("unchecked")
public R transformRow(Object[] row) {
final Object result = row[0];
if ( result == null || objectType.isInstance( result ) ) {
return (R) result;
}
else {
throw new TypeMismatchException( "Result type is '" + type.getSimpleName()
+ "' but the query returned a '" + result.getClass().getSimpleName() + "'" );
}
}

@Override
public int determineNumberOfResultElements(int rawElementCount) {
return 1;
}
}
Loading

0 comments on commit 77f7d68

Please sign in to comment.