HHH-16747 nail down query result types and actually document the semantics

This commit is contained in:
Gavin King 2023-06-05 11:40:18 +02:00
parent c5ecf5d41c
commit 4eee30550c
13 changed files with 366 additions and 107 deletions

View File

@ -861,7 +861,7 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
query.setTupleTransformer( NativeQueryListTransformer.INSTANCE ); query.setTupleTransformer( NativeQueryListTransformer.INSTANCE );
} }
else if ( getFactory().getMappingMetamodel().isEntityClass( resultClass ) ) { else if ( getFactory().getMappingMetamodel().isEntityClass( resultClass ) ) {
query.addEntity( "alias1", resultClass.getName(), LockMode.READ ); query.addEntity( resultClass, LockMode.READ );
} }
else if ( resultClass != Object.class && resultClass != Object[].class ) { else if ( resultClass != Object.class && resultClass != Object[].class ) {
if ( isClass( resultClass ) if ( isClass( resultClass )

View File

@ -80,8 +80,8 @@ public interface JpaMetamodel extends Metamodel {
/** /**
* Returns a map that gives access to the enum literal expressions that can be used in queries. * Returns a map that gives access to the enum literal expressions that can be used in queries.
* The key is the short-hand enum literal. The value is a map, from enum class to the actual enum value. * The key is the shorthand enum literal. The value is a map, from enum class to the actual enum value.
* This is needed for parsing short-hand enum literals that don't use FQNs. * This is needed for parsing shorthand enum literals that don't use FQNs.
*/ */
Map<String, Map<Class<?>, Enum<?>>> getAllowedEnumLiteralTexts(); Map<String, Map<Class<?>, Enum<?>>> getAllowedEnumLiteralTexts();

View File

@ -29,11 +29,28 @@ public interface QueryProducer {
/** /**
* Create a {@link Query} instance for the given HQL query, or * Create a {@link Query} instance for the given HQL query, or
* HQL insert, update, or delete statement. * HQL insert, update, or delete statement.
* <p>
* If a query has no explicit {@code select} list, the select list
* is inferred:
* <ul>
* <li>if there is exactly one root entity in the {@code from}
* clause, then that root entity is the only element of the
* select list, or
* <li>otherwise, if there are multiple root entities in the
* {@code from} clause, then the select list contains every
* root entity and every non-{@code fetch} joined entity.
* </ul>
* *
* @apiNote Returns a raw {@code Query} type instead of a wildcard * @apiNote Returns a raw {@code Query} type instead of a wildcard
* type {@code Query<?>}, to match the signature of the JPA method * type {@code Query<?>}, to match the signature of the JPA method
* {@link jakarta.persistence.EntityManager#createQuery(String)}. * {@link jakarta.persistence.EntityManager#createQuery(String)}.
* *
* @implNote This method interprets some queries with an implicit
* {@code select} list in a quite unintuitive way. In some future
* release, this method will be modified to throw an exception
* when passed a query with a missing {@code select}. For now, use
* {@link #createQuery(String, Class)} to avoid ambiguity.
*
* @param queryString The HQL query * @param queryString The HQL query
* *
* @return The {@link Query} instance for manipulation and execution * @return The {@link Query} instance for manipulation and execution
@ -41,21 +58,47 @@ public interface QueryProducer {
* @see jakarta.persistence.EntityManager#createQuery(String) * @see jakarta.persistence.EntityManager#createQuery(String)
* *
* @deprecated use {@link #createQuery(String, Class)}, * @deprecated use {@link #createQuery(String, Class)},
* {@link #createSelectionQuery} or {@link #createMutationQuery(String)} * {@link #createSelectionQuery(String, Class)}, or
* depending on intention * {@link #createMutationQuery(String)} depending on intention
*/ */
@Deprecated(since = "6.0") @SuppressWarnings("rawtypes") @Deprecated(since = "6.0") @SuppressWarnings("rawtypes")
Query createQuery(String queryString); Query createQuery(String queryString);
/** /**
* Create a typed {@link Query} instance for the given HQL query string. * Create a typed {@link Query} instance for the given HQL query
* string and given query result type.
* <ul>
* <li>If the query has a single item in the {@code select} list,
* then the select item must be assignable to the given result
* type.
* <li>Otherwise, if there are multiple select items, then the
* select items will be packaged into an instance of the
* result type. The result type must have an appropriate
* constructor with parameter types matching the select items,
* or it must be one of the types {@code Object[]},
* {@link java.util.List}, {@link java.util.Map}, or
* {@link jakarta.persistence.Tuple}.
* </ul>
* <p>
* If a query has no explicit {@code select} list, the select list
* is inferred from the given query result type:
* <ul>
* <li>if the result type is an entity type, the query must have
* exactly one root entity in the {@code from} clause, it must
* be assignable to the result type, and the inferred select
* list will contain just that entity, or
* <li>otherwise, the select list contains every root entity and
* every non-{@code fetch} joined entity, and each query result
* will be packaged into an instance of the result type, just
* as specified above.
* </ul>
* <p> * <p>
* The returned {@code Query} may be executed by calling * The returned {@code Query} may be executed by calling
* {@link Query#getResultList()} or {@link Query#getSingleResult()}. * {@link Query#getResultList()} or {@link Query#getSingleResult()}.
* *
* @param queryString The HQL query * @param queryString The HQL query
* @param resultClass The type of the query result * @param resultClass The type of the query result
* @return The Query instance for manipulation and execution * @return The {@link Query} instance for manipulation and execution
* *
* @see jakarta.persistence.EntityManager#createQuery(String,Class) * @see jakarta.persistence.EntityManager#createQuery(String,Class)
*/ */
@ -98,15 +141,25 @@ public interface QueryProducer {
/** /**
* Create a {@link NativeQuery} instance for the given native SQL query * Create a {@link NativeQuery} instance for the given native SQL query
* using implicit mapping to the specified Java type. * using an implicit mapping to the specified Java type.
* <p> * <ul>
* If the given class is an entity class, this method is equivalent to * <li>If the given class is an entity class, this method is equivalent
* {@code createNativeQuery(sqlString).addEntity("alias1", resultClass)}. * to {@code createNativeQuery(sqlString).addEntity(resultClass)}.
* <li>If the given class has a registered
* {@link org.hibernate.type.descriptor.java.JavaType}, then the
* query must return a result set with a single column whose
* {@code JdbcType} is compatible with that {@code JavaType}.
* <li>Otherwise, the select items will be packaged into an instance of
* the result type. The result type must have an appropriate
* constructor with parameter types matching the select items, or it
* must be one of the types {@code Object[]}, {@link java.util.List},
* {@link java.util.Map}, or {@link jakarta.persistence.Tuple}.
* </ul>
* *
* @param sqlString The native (SQL) query string * @param sqlString The native (SQL) query string
* @param resultClass The Java entity type to map results to * @param resultClass The Java type to map results to
* *
* @return The NativeQuery instance for manipulation and execution * @return The {@link NativeQuery} instance for manipulation and execution
* *
* @see jakarta.persistence.EntityManager#createNativeQuery(String,Class) * @see jakarta.persistence.EntityManager#createNativeQuery(String,Class)
*/ */
@ -114,13 +167,13 @@ public interface QueryProducer {
/** /**
* Create a {@link NativeQuery} instance for the given native SQL query * Create a {@link NativeQuery} instance for the given native SQL query
* using implicit mapping to the specified Java type. * using an implicit mapping to the specified Java entity type.
* <p> * <p>
* If the given class is an entity class, this method is equivalent to * The given class must be an entity class. This method is equivalent to
* {@code createNativeQuery(sqlString).addEntity(tableAlias, resultClass)}. * {@code createNativeQuery(sqlString).addEntity(tableAlias, resultClass)}.
* *
* @param sqlString Native (SQL) query string * @param sqlString Native (SQL) query string
* @param resultClass The Java entity type to map results to * @param resultClass The Java entity class to map results to
* @param tableAlias The table alias for columns in the result set * @param tableAlias The table alias for columns in the result set
* *
* @return The {@link NativeQuery} instance for manipulation and execution * @return The {@link NativeQuery} instance for manipulation and execution
@ -133,13 +186,13 @@ public interface QueryProducer {
* Create a {@link NativeQuery} instance for the given native SQL query * Create a {@link NativeQuery} instance for the given native SQL query
* using an explicit mapping to the specified Java type. * using an explicit mapping to the specified Java type.
* <p> * <p>
* The given result set mapping name must identify a mapping defined by a * The given result set mapping name must identify a mapping defined by
* {@link jakarta.persistence.SqlResultSetMapping} annotation. * a {@link jakarta.persistence.SqlResultSetMapping} annotation.
* *
* @param sqlString The native (SQL) query string * @param sqlString The native (SQL) query string
* @param resultSetMappingName The explicit result mapping name * @param resultSetMappingName The explicit result mapping name
* *
* @return The NativeQuery instance for manipulation and execution * @return The {@link NativeQuery} instance for manipulation and execution
* *
* @see jakarta.persistence.EntityManager#createNativeQuery(String,Class) * @see jakarta.persistence.EntityManager#createNativeQuery(String,Class)
* @see jakarta.persistence.SqlResultSetMapping * @see jakarta.persistence.SqlResultSetMapping
@ -153,8 +206,8 @@ public interface QueryProducer {
* Create a {@link NativeQuery} instance for the given native SQL query * Create a {@link NativeQuery} instance for the given native SQL query
* using an explicit mapping to the specified Java type. * using an explicit mapping to the specified Java type.
* <p> * <p>
* The given result set mapping name must identify a mapping defined by a * The given result set mapping name must identify a mapping defined by
* {@link jakarta.persistence.SqlResultSetMapping} annotation. * a {@link jakarta.persistence.SqlResultSetMapping} annotation.
* *
* @param sqlString The native (SQL) query string * @param sqlString The native (SQL) query string
* @param resultSetMappingName The explicit result mapping name * @param resultSetMappingName The explicit result mapping name
@ -167,21 +220,69 @@ public interface QueryProducer {
<R> NativeQuery<R> createNativeQuery(String sqlString, String resultSetMappingName, Class<R> resultClass); <R> NativeQuery<R> createNativeQuery(String sqlString, String resultSetMappingName, Class<R> resultClass);
/** /**
* Create a {@link SelectionQuery} reference for the given HQL. * Create a {@link SelectionQuery} reference for the given HQL
* {@code select} statement.
* <p>
* If the statement has no explicit {@code select} list, the
* select list is inferred:
* <ul>
* <li>if there is exactly one root entity in the {@code from}
* clause, then that root entity is the only element of the
* select list, or
* <li>otherwise, if there are multiple root entities in the
* {@code from} clause, then the select list contains every
* root entity and every non-{@code fetch} joined entity.
* </ul>
* *
* Only valid for select queries * @implNote This method interprets some queries with an implicit
* * {@code select} list in a quite unintuitive way. In some future
* @see jakarta.persistence.EntityManager#createQuery(String) * release, this method will be modified to throw an exception
* when passed a query with a missing {@code select}. For now, use
* {@link #createSelectionQuery(String, Class)} to avoid ambiguity.
* *
* @throws IllegalSelectQueryException if the given HQL query * @throws IllegalSelectQueryException if the given HQL query
* is an insert, update or delete query * is an insert, update or delete query
*
* @deprecated Use {@link #createSelectionQuery(String, Class)}
*/ */
@Deprecated(since = "6.3")
SelectionQuery<?> createSelectionQuery(String hqlString); SelectionQuery<?> createSelectionQuery(String hqlString);
/** /**
* Create a {@link SelectionQuery} reference for the given HQL. * Create a {@link SelectionQuery} instance for the given HQL query
* * string and given query result type.
* Only valid for select queries * <ul>
* <li>If the query has a single item in the {@code select} list,
* then the select item must be assignable to the given result
* type.
* <li>Otherwise, if there are multiple select items, then the
* select items will be packaged into an instance of the
* result type. The result type must have an appropriate
* constructor with parameter types matching the select items,
* or it must be one of the types {@code Object[]},
* {@link java.util.List}, {@link java.util.Map}, or
* {@link jakarta.persistence.Tuple}.
* </ul>
* <p>
* If a query has no explicit {@code select} list, the select list
* is inferred from the given query result type:
* <ul>
* <li>if the result type is an entity type, the query must have
* exactly one root entity in the {@code from} clause, it must
* be assignable to the result type, and the inferred select
* list will contain just that entity, or
* <li>otherwise, the select list contains every root entity and
* every non-{@code fetch} joined entity, and each query result
* will be packaged into an instance of the result type, just
* as specified above.
* </ul>
* <p>
* The returned {@code Query} may be executed by calling
* {@link Query#getResultList()} or {@link Query#getSingleResult()}.
* @param hqlString The HQL query as a string
* @param resultType The {@link Class} object representing the
* query result type
* *
* @see jakarta.persistence.EntityManager#createQuery(String) * @see jakarta.persistence.EntityManager#createQuery(String)
* *
@ -191,14 +292,15 @@ public interface QueryProducer {
<R> SelectionQuery<R> createSelectionQuery(String hqlString, Class<R> resultType); <R> SelectionQuery<R> createSelectionQuery(String hqlString, Class<R> resultType);
/** /**
* Create a {@link SelectionQuery} reference for the given Criteria. * Create a {@link SelectionQuery} reference for the given
* {@link CriteriaQuery}.
* *
* @see jakarta.persistence.EntityManager#createQuery(CriteriaQuery) * @see jakarta.persistence.EntityManager#createQuery(CriteriaQuery)
*/ */
<R> SelectionQuery<R> createSelectionQuery(CriteriaQuery<R> criteria); <R> SelectionQuery<R> createSelectionQuery(CriteriaQuery<R> criteria);
/** /**
* Create a MutationQuery reference for the given HQL insert, * Create a {@link MutationQuery} reference for the given HQL insert,
* update, or delete statement. * update, or delete statement.
* *
* @throws IllegalMutationQueryException if the given HQL query * @throws IllegalMutationQueryException if the given HQL query
@ -234,7 +336,7 @@ public interface QueryProducer {
* Create a typed {@link Query} instance for the given named query. * Create a typed {@link Query} instance for the given named query.
* The named query might be defined in HQL or in native SQL. * The named query might be defined in HQL or in native SQL.
* *
* @param name the name of a pre-defined, named query * @param name the name of a predefined named query
* *
* @return The {@link Query} instance for manipulation and execution * @return The {@link Query} instance for manipulation and execution
* *
@ -244,8 +346,7 @@ public interface QueryProducer {
* *
* @see jakarta.persistence.EntityManager#createNamedQuery(String) * @see jakarta.persistence.EntityManager#createNamedQuery(String)
* *
* @deprecated use {@link #createNamedQuery(String, Class)} or * @deprecated use {@link #createNamedQuery(String, Class)}
* {@link #createNamedMutationQuery(String)}
*/ */
@Deprecated(since = "6.0") @SuppressWarnings("rawtypes") @Deprecated(since = "6.0") @SuppressWarnings("rawtypes")
Query createNamedQuery(String name); Query createNamedQuery(String name);
@ -269,20 +370,29 @@ public interface QueryProducer {
<R> Query<R> createNamedQuery(String name, Class<R> resultClass); <R> Query<R> createNamedQuery(String name, Class<R> resultClass);
/** /**
* Create a {@link SelectionQuery} instance for the * Create a {@link SelectionQuery} instance for the named
* named {@link jakarta.persistence.NamedQuery} * {@link jakarta.persistence.NamedQuery}.
* *
* @throws IllegalSelectQueryException if the given HQL query is a select query * @implNote This method interprets some queries with an implicit
* {@code select} list in a quite unintuitive way. In some future
* release, this method will be modified to throw an exception
* when passed a query with a missing {@code select}. For now, use
* {@link #createNamedSelectionQuery(String, Class)} to avoid
* ambiguity.
*
* @throws IllegalSelectQueryException if the given HQL query is not a select query
* @throws UnknownNamedQueryException if no query has been defined with the given name * @throws UnknownNamedQueryException if no query has been defined with the given name
*
* @deprecated use {@link #createNamedSelectionQuery(String, Class)}
*/ */
@Deprecated(since = "6.3")
SelectionQuery<?> createNamedSelectionQuery(String name); SelectionQuery<?> createNamedSelectionQuery(String name);
/** /**
* Create a {@link SelectionQuery} instance for the * Create a {@link SelectionQuery} instance for the named
* named {@link jakarta.persistence.NamedQuery} with the expected * {@link jakarta.persistence.NamedQuery} with the given result type.
* result-type
* *
* @throws IllegalSelectQueryException if the given HQL query is a select query * @throws IllegalSelectQueryException if the given HQL query is not a select query
* @throws UnknownNamedQueryException if no query has been defined with the given name * @throws UnknownNamedQueryException if no query has been defined with the given name
*/ */
<R> SelectionQuery<R> createNamedSelectionQuery(String name, Class<R> resultType); <R> SelectionQuery<R> createNamedSelectionQuery(String name, Class<R> resultType);
@ -301,7 +411,7 @@ public interface QueryProducer {
/** /**
* Create a {@link Query} instance for the named query. * Create a {@link Query} instance for the named query.
* *
* @param queryName the name of a pre-defined, named query * @param queryName the name of a predefined named query
* *
* @return The {@link Query} instance for manipulation and execution * @return The {@link Query} instance for manipulation and execution
* *
@ -317,7 +427,7 @@ public interface QueryProducer {
/** /**
* Get a {@link NativeQuery} instance for a named native SQL query * Get a {@link NativeQuery} instance for a named native SQL query
* *
* @param name The name of the pre-defined query * @param name The name of the predefined query
* *
* @return The {@link NativeQuery} instance for manipulation and execution * @return The {@link NativeQuery} instance for manipulation and execution
* *
@ -329,7 +439,7 @@ public interface QueryProducer {
/** /**
* Get a {@link NativeQuery} instance for a named native SQL query * Get a {@link NativeQuery} instance for a named native SQL query
* *
* @param name The name of the pre-defined query * @param name The name of the predefined query
* *
* @return The {@link NativeQuery} instance for manipulation and execution * @return The {@link NativeQuery} instance for manipulation and execution
* *

View File

@ -31,6 +31,7 @@ import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import org.hibernate.AssertionFailure;
import org.hibernate.QueryException; import org.hibernate.QueryException;
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
import org.hibernate.boot.registry.classloading.spi.ClassLoadingException; import org.hibernate.boot.registry.classloading.spi.ClassLoadingException;
@ -1157,31 +1158,56 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
} }
protected SqmSelectClause buildInferredSelectClause(SqmFromClause fromClause) { protected SqmSelectClause buildInferredSelectClause(SqmFromClause fromClause) {
// for now, this is slightly different to the legacy behavior where if ( fromClause.getNumberOfRoots() == 0 ) {
// the root and each non-fetched-join was selected. For now, here, we simply // should be impossible to get here
// select the root throw new AssertionFailure( "query has no 'select' clause, and no root entities");
final SqmSelectClause selectClause; }
final boolean expectingArray = expectedResultType != null && expectedResultType.isArray(); final NodeBuilder nodeBuilder = creationContext.getNodeBuilder();
if ( expectingArray ) {
// triggers legacy interpretation of returning all roots final SqmSelectClause selectClause;
// and non-fetched joins final boolean singleEntityResult;
selectClause = new SqmSelectClause( if ( expectedResultType == null ) {
false, // no result type was specified
creationContext.getNodeBuilder() // - if there is a single root entity return the entity,
); // even if it has non-fetch joins (ugh!)
// - otherwise, return all entities in an Object[] array,
// including non-fetch joins
selectClause = new SqmSelectClause( false, nodeBuilder );
singleEntityResult = fromClause.getNumberOfRoots() == 1;
} }
else { else {
selectClause = new SqmSelectClause( singleEntityResult = creationContext.getJpaMetamodel().findEntityType( expectedResultType ) != null;
false, if ( singleEntityResult ) {
fromClause.getNumberOfRoots(), // the result type is an entity class
creationContext.getNodeBuilder() if ( fromClause.getNumberOfRoots() > 1 ) {
); // multiple root entities
throw new SemanticException( "query has no 'select' clause, and multiple root entities, but query result type is an entity class"
+ " (specify an explicit 'select' list, or a different result type, for example, 'Object[].class')");
}
else {
final SqmRoot<?> sqmRoot = fromClause.getRoots().get(0);
if ( sqmRoot instanceof SqmCteRoot ) {
throw new SemanticException( "query has no 'select' clause, and the 'from' clause refers to a CTE, but query result type is an entity class"
+ " (specify an explicit 'select' list)");
}
else {
// exactly one root entity, return it
// (joined entities are not returned)
selectClause = new SqmSelectClause( false, 1, nodeBuilder );
}
}
}
else {
// the result type is not an entity class
// return all root entities and non-fetch joins
selectClause = new SqmSelectClause( false, nodeBuilder );
}
} }
fromClause.visitRoots( (sqmRoot) -> { fromClause.visitRoots( (sqmRoot) -> {
selectClause.addSelection( new SqmSelection<>( sqmRoot, sqmRoot.getAlias(), creationContext.getNodeBuilder() ) ); selectClause.addSelection( new SqmSelection<>( sqmRoot, sqmRoot.getAlias(), nodeBuilder) );
if ( expectingArray ) { if ( !singleEntityResult ) {
applyJoinsToInferredSelectClause( sqmRoot, selectClause ); applyJoinsToInferredSelectClause( sqmRoot, selectClause );
} }
} ); } );

View File

@ -63,7 +63,7 @@ public class QueryInterpretationCacheDisabledImpl implements QueryInterpretation
public HqlInterpretation resolveHqlInterpretation(String queryString, Class<?> expectedResultType, Function<String, SqmStatement<?>> creator) { public HqlInterpretation resolveHqlInterpretation(String queryString, Class<?> expectedResultType, Function<String, SqmStatement<?>> creator) {
final StatisticsImplementor statistics = statisticsSupplier.get(); final StatisticsImplementor statistics = statisticsSupplier.get();
final boolean stats = statistics.isStatisticsEnabled(); final boolean stats = statistics.isStatisticsEnabled();
final long startTime = ( stats ) ? System.nanoTime() : 0L; final long startTime = stats ? System.nanoTime() : 0L;
final SqmStatement<?> sqmStatement = creator.apply( queryString ); final SqmStatement<?> sqmStatement = creator.apply( queryString );
final DomainParameterXref domainParameterXref; final DomainParameterXref domainParameterXref;

View File

@ -9,7 +9,6 @@ package org.hibernate.query.internal;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Supplier; import java.util.function.Supplier;
import jakarta.persistence.Tuple;
import org.hibernate.internal.util.collections.BoundedConcurrentHashMap; import org.hibernate.internal.util.collections.BoundedConcurrentHashMap;
import org.hibernate.query.QueryLogging; import org.hibernate.query.QueryLogging;
@ -23,6 +22,7 @@ import org.hibernate.query.spi.SimpleHqlInterpretationImpl;
import org.hibernate.query.sql.spi.ParameterInterpretation; import org.hibernate.query.sql.spi.ParameterInterpretation;
import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.query.sqm.internal.DomainParameterXref;
import org.hibernate.query.sqm.tree.SqmStatement; import org.hibernate.query.sqm.tree.SqmStatement;
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
import org.hibernate.stat.spi.StatisticsImplementor; import org.hibernate.stat.spi.StatisticsImplementor;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
@ -99,6 +99,45 @@ public class QueryInterpretationCacheStandardImpl implements QueryInterpretation
log.tracef( "QueryPlan#cacheNonSelectQueryPlan(%s)", key ); log.tracef( "QueryPlan#cacheNonSelectQueryPlan(%s)", key );
} }
private static String typedKey(String queryString, Class<?> expectedResultType) {
return expectedResultType.getName() + ":" + queryString;
}
private void putInCache(String queryString, Class<?> expectedResultType, HqlInterpretation hqlInterpretation) {
final SqmStatement<?> statement = hqlInterpretation.getSqmStatement();
if ( statement instanceof SqmSelectStatement && expectedResultType != null ) {
hqlInterpretationCache.put( typedKey( queryString, expectedResultType ), hqlInterpretation);
}
else {
hqlInterpretationCache.put( queryString, hqlInterpretation );
}
}
private HqlInterpretation getFromCache(String queryString, Class<?> expectedResultType) {
if ( expectedResultType != null ) {
final HqlInterpretation typedHqlInterpretation =
hqlInterpretationCache.get( typedKey( queryString, expectedResultType ) );
if ( typedHqlInterpretation != null ) {
return typedHqlInterpretation;
}
}
final HqlInterpretation hqlInterpretation = hqlInterpretationCache.get( queryString );
if ( hqlInterpretation != null ) {
final SqmStatement<?> statement = hqlInterpretation.getSqmStatement();
if ( statement instanceof SqmSelectStatement && expectedResultType != null ) {
final Class<?> resultType = ((SqmSelectStatement<?>) statement).getSelection().getJavaType();
return expectedResultType.equals( resultType ) ? hqlInterpretation : null;
}
else {
return hqlInterpretation;
}
}
else {
return null;
}
}
@Override @Override
public HqlInterpretation resolveHqlInterpretation( public HqlInterpretation resolveHqlInterpretation(
String queryString, String queryString,
@ -106,17 +145,7 @@ public class QueryInterpretationCacheStandardImpl implements QueryInterpretation
Function<String, SqmStatement<?>> creator) { Function<String, SqmStatement<?>> creator) {
log.tracef( "QueryPlan#resolveHqlInterpretation( `%s` )", queryString ); log.tracef( "QueryPlan#resolveHqlInterpretation( `%s` )", queryString );
final String cacheKey; final HqlInterpretation existing = getFromCache( queryString, expectedResultType );
if ( expectedResultType != null
&& ( expectedResultType.isArray() || Tuple.class.isAssignableFrom( expectedResultType ) ) ) {
cacheKey = "multi_" + queryString;
}
else {
cacheKey = queryString;
}
final HqlInterpretation existing = hqlInterpretationCache.get( cacheKey );
if ( existing != null ) { if ( existing != null ) {
final StatisticsImplementor statistics = statisticsSupplier.get(); final StatisticsImplementor statistics = statisticsSupplier.get();
if ( statistics.isStatisticsEnabled() ) { if ( statistics.isStatisticsEnabled() ) {
@ -124,10 +153,12 @@ public class QueryInterpretationCacheStandardImpl implements QueryInterpretation
} }
return existing; return existing;
} }
else {
final HqlInterpretation hqlInterpretation = createHqlInterpretation( queryString, creator, statisticsSupplier ); final HqlInterpretation hqlInterpretation =
hqlInterpretationCache.put( cacheKey, hqlInterpretation ); createHqlInterpretation( queryString, creator, statisticsSupplier );
return hqlInterpretation; putInCache( queryString, expectedResultType, hqlInterpretation );
return hqlInterpretation;
}
} }
protected static HqlInterpretation createHqlInterpretation( protected static HqlInterpretation createHqlInterpretation(
@ -136,7 +167,7 @@ public class QueryInterpretationCacheStandardImpl implements QueryInterpretation
Supplier<StatisticsImplementor> statisticsSupplier) { Supplier<StatisticsImplementor> statisticsSupplier) {
final StatisticsImplementor statistics = statisticsSupplier.get(); final StatisticsImplementor statistics = statisticsSupplier.get();
final boolean stats = statistics.isStatisticsEnabled(); final boolean stats = statistics.isStatisticsEnabled();
final long startTime = ( stats ) ? System.nanoTime() : 0L; final long startTime = stats ? System.nanoTime() : 0L;
final SqmStatement<?> sqmStatement = creator.apply( queryString ); final SqmStatement<?> sqmStatement = creator.apply( queryString );
final ParameterMetadataImplementor parameterMetadata; final ParameterMetadataImplementor parameterMetadata;

View File

@ -106,7 +106,7 @@ public class Builders {
Class<R> jdbcJavaType, Class<R> jdbcJavaType,
AttributeConverter<O, R> converter, AttributeConverter<O, R> converter,
SessionFactoryImplementor sessionFactory) { SessionFactoryImplementor sessionFactory) {
return new DynamicResultBuilderBasicConverted( columnAlias, domainJavaType, jdbcJavaType, converter, sessionFactory ); return new DynamicResultBuilderBasicConverted<>( columnAlias, domainJavaType, jdbcJavaType, converter, sessionFactory );
} }
public static <R> ResultBuilder converted( public static <R> ResultBuilder converted(
@ -123,7 +123,7 @@ public class Builders {
Class<R> jdbcJavaType, Class<R> jdbcJavaType,
Class<? extends AttributeConverter<O,R>> converterJavaType, Class<? extends AttributeConverter<O,R>> converterJavaType,
SessionFactoryImplementor sessionFactory) { SessionFactoryImplementor sessionFactory) {
return new DynamicResultBuilderBasicConverted( columnAlias, domainJavaType, jdbcJavaType, converterJavaType, sessionFactory ); return new DynamicResultBuilderBasicConverted<>( columnAlias, domainJavaType, jdbcJavaType, converterJavaType, sessionFactory );
} }
public static ResultBuilderBasicValued scalar(int position) { public static ResultBuilderBasicValued scalar(int position) {
@ -233,10 +233,8 @@ public class Builders {
String entityName, String entityName,
LockMode explicitLockMode, LockMode explicitLockMode,
SessionFactoryImplementor sessionFactory) { SessionFactoryImplementor sessionFactory) {
final RuntimeMetamodels runtimeMetamodels = sessionFactory.getRuntimeMetamodels(); final EntityMappingType entityMapping = sessionFactory.getRuntimeMetamodels().getEntityMappingType( entityName );
final EntityMappingType entityMapping = runtimeMetamodels.getEntityMappingType( entityName ); return new DynamicResultBuilderEntityCalculated( entityMapping, tableAlias, explicitLockMode );
return new DynamicResultBuilderEntityCalculated( entityMapping, tableAlias, explicitLockMode, sessionFactory );
} }
public static DynamicFetchBuilderLegacy fetch(String tableAlias, String ownerTableAlias, String joinPropertyName) { public static DynamicFetchBuilderLegacy fetch(String tableAlias, String ownerTableAlias, String joinPropertyName) {
@ -252,9 +250,9 @@ public class Builders {
public static ResultBuilder resultClassBuilder( public static ResultBuilder resultClassBuilder(
Class<?> resultMappingClass, Class<?> resultMappingClass,
SessionFactoryImplementor sessionFactory) { SessionFactoryImplementor sessionFactory) {
final MappingMetamodelImplementor mappingMetamodel = sessionFactory final MappingMetamodelImplementor mappingMetamodel =
.getRuntimeMetamodels() sessionFactory.getRuntimeMetamodels()
.getMappingMetamodel(); .getMappingMetamodel();
final EntityMappingType entityMappingType = mappingMetamodel.findEntityDescriptor( resultMappingClass ); final EntityMappingType entityMappingType = mappingMetamodel.findEntityDescriptor( resultMappingClass );
if ( entityMappingType != null ) { if ( entityMappingType != null ) {
// the resultClass is an entity // the resultClass is an entity

View File

@ -9,7 +9,6 @@ package org.hibernate.query.results.dynamic;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import org.hibernate.LockMode; import org.hibernate.LockMode;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.query.NativeQuery; import org.hibernate.query.NativeQuery;
import org.hibernate.query.results.DomainResultCreationStateImpl; import org.hibernate.query.results.DomainResultCreationStateImpl;
@ -35,18 +34,14 @@ public class DynamicResultBuilderEntityCalculated implements DynamicResultBuilde
private final String tableAlias; private final String tableAlias;
private final LockMode explicitLockMode; private final LockMode explicitLockMode;
private final SessionFactoryImplementor sessionFactory;
public DynamicResultBuilderEntityCalculated( public DynamicResultBuilderEntityCalculated(
EntityMappingType entityMapping, EntityMappingType entityMapping,
String tableAlias, String tableAlias,
LockMode explicitLockMode, LockMode explicitLockMode) {
SessionFactoryImplementor sessionFactory) {
this.entityMapping = entityMapping; this.entityMapping = entityMapping;
this.navigablePath = new NavigablePath( entityMapping.getEntityName() ); this.navigablePath = new NavigablePath( entityMapping.getEntityName() );
this.tableAlias = tableAlias; this.tableAlias = tableAlias;
this.explicitLockMode = explicitLockMode; this.explicitLockMode = explicitLockMode;
this.sessionFactory = sessionFactory;
} }
@Override @Override

View File

@ -45,11 +45,13 @@ public class ImplicitResultClassBuilder implements ResultBuilder {
DomainResultCreationState domainResultCreationState) { DomainResultCreationState domainResultCreationState) {
assert resultPosition == 0; assert resultPosition == 0;
final SessionFactoryImplementor sessionFactory = domainResultCreationState.getSqlAstCreationState() final SessionFactoryImplementor sessionFactory =
.getCreationContext() domainResultCreationState.getSqlAstCreationState()
.getSessionFactory(); .getCreationContext()
.getSessionFactory();
final TypeConfiguration typeConfiguration = sessionFactory.getTypeConfiguration(); final TypeConfiguration typeConfiguration = sessionFactory.getTypeConfiguration();
final SqlExpressionResolver sqlExpressionResolver = domainResultCreationState.getSqlAstCreationState().getSqlExpressionResolver(); final SqlExpressionResolver sqlExpressionResolver =
domainResultCreationState.getSqlAstCreationState().getSqlExpressionResolver();
final int jdbcResultPosition = 1; final int jdbcResultPosition = 1;

View File

@ -1014,6 +1014,11 @@ public class NativeQueryImpl<R>
return addEntity( entityType.getName() ); return addEntity( entityType.getName() );
} }
@Override
public NativeQueryImplementor<R> addEntity(Class<R> entityType, LockMode lockMode) {
return addEntity( StringHelper.unqualify( entityType.getName() ), entityType.getName(), lockMode);
}
@Override @Override
public NativeQueryImplementor<R> addEntity(String tableAlias, @SuppressWarnings("rawtypes") Class entityClass) { public NativeQueryImplementor<R> addEntity(String tableAlias, @SuppressWarnings("rawtypes") Class entityClass) {
return addEntity( tableAlias, entityClass.getName() ); return addEntity( tableAlias, entityClass.getName() );

View File

@ -112,6 +112,8 @@ public interface NativeQueryImplementor<R> extends QueryImplementor<R>, NativeQu
@Override @Override
NativeQueryImplementor<R> addEntity(@SuppressWarnings("rawtypes") Class entityType); NativeQueryImplementor<R> addEntity(@SuppressWarnings("rawtypes") Class entityType);
NativeQueryImplementor<R> addEntity(Class<R> entityType, LockMode lockMode);
@Override @Override
NativeQueryImplementor<R> addEntity(String tableAlias, @SuppressWarnings("rawtypes") Class entityType); NativeQueryImplementor<R> addEntity(String tableAlias, @SuppressWarnings("rawtypes") Class entityType);

View File

@ -238,7 +238,7 @@ public abstract class AbstractSqmFrom<O,T> extends AbstractSqmPath<T> implements
@Override @Override
public boolean hasJoins() { public boolean hasJoins() {
return !( joins == null || joins.isEmpty() ); return joins != null && !joins.isEmpty();
} }
@Override @Override
@ -330,7 +330,6 @@ public abstract class AbstractSqmFrom<O,T> extends AbstractSqmPath<T> implements
} }
@Override @Override
@SuppressWarnings("unchecked")
public <A> SqmSingularJoin<T, A> join(SingularAttribute<? super T, A> attribute, JoinType jt) { public <A> SqmSingularJoin<T, A> join(SingularAttribute<? super T, A> attribute, JoinType jt) {
final SqmSingularJoin<T, A> join = buildSingularJoin( (SingularPersistentAttribute<? super T, A>) attribute, SqmJoinType.from( jt ), false ); final SqmSingularJoin<T, A> join = buildSingularJoin( (SingularPersistentAttribute<? super T, A>) attribute, SqmJoinType.from( jt ), false );
addSqmJoin( join ); addSqmJoin( join );
@ -646,7 +645,6 @@ public abstract class AbstractSqmFrom<O,T> extends AbstractSqmPath<T> implements
} }
@Override @Override
@SuppressWarnings("unchecked")
public <A> SqmSingularJoin<T, A> fetch(SingularAttribute<? super T, A> attribute, JoinType jt) { public <A> SqmSingularJoin<T, A> fetch(SingularAttribute<? super T, A> attribute, JoinType jt) {
final SqmSingularJoin<T, A> join = buildSingularJoin( final SqmSingularJoin<T, A> join = buildSingularJoin(
(SingularPersistentAttribute<? super T, A>) attribute, (SingularPersistentAttribute<? super T, A>) attribute,
@ -663,7 +661,6 @@ public abstract class AbstractSqmFrom<O,T> extends AbstractSqmPath<T> implements
} }
@Override @Override
@SuppressWarnings("unchecked")
public <A> SqmAttributeJoin<T, A> fetch(PluralAttribute<? super T, ?, A> attribute, JoinType jt) { public <A> SqmAttributeJoin<T, A> fetch(PluralAttribute<? super T, ?, A> attribute, JoinType jt) {
return buildJoin( return buildJoin(
(PluralPersistentAttribute<? super T, ?, A>) attribute, (PluralPersistentAttribute<? super T, ?, A>) attribute,

View File

@ -24,6 +24,7 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
/** /**
* @author Steve Ebersole * @author Steve Ebersole
@ -34,6 +35,7 @@ import static org.assertj.core.api.Assertions.assertThat;
public class ImplicitSelectWithJoinTests { public class ImplicitSelectWithJoinTests {
private static final String HQL = "from Product p join p.vendor v where v.name like '%Steve%'"; private static final String HQL = "from Product p join p.vendor v where v.name like '%Steve%'";
private static final String HQL2 = "select p " + HQL; private static final String HQL2 = "select p " + HQL;
private static final String HQL3 = "from Product q join q.vendor w, Product p join p.vendor v where v.name like '%Steve%' and w.name like '%Gavin%'";
@Test @Test
public void testNoExpectedType(SessionFactoryScope scope) { public void testNoExpectedType(SessionFactoryScope scope) {
@ -79,8 +81,93 @@ public class ImplicitSelectWithJoinTests {
} ); } );
} }
@Test
public void testArrayResultNoResultType(SessionFactoryScope scope) {
scope.inTransaction( (session) -> {
final SelectionQuery<?> query = session.createSelectionQuery( HQL3 );
{
final List<?> results = query.list();
assertThat( results ).hasSize( 1 );
final Object result = results.get( 0 );
assertThat( result ).isNotNull();
assertInstanceOf( Object[].class, result );
assertThat( (Object[]) result ).hasSize(4);
assertThat( (Object[]) result ).hasExactlyElementsOfTypes(Product.class, Vendor.class, Product.class, Vendor.class);
}
{
final ScrollableResults<?> results = query.scroll();
assertThat( results.next() ).isTrue();
final Object result = results.get();
assertThat( result ).isNotNull();
assertInstanceOf( Object[].class, result );
assertThat( (Object[]) result ).hasSize(4);
assertThat( (Object[]) result ).hasExactlyElementsOfTypes(Product.class, Vendor.class, Product.class, Vendor.class);
assertThat( results.next() ).isFalse();
}
} );
// frankly, this would be more consistent and more backward-compatible
// scope.inTransaction( (session) -> {
// final SelectionQuery<?> query = session.createSelectionQuery( HQL );
//
// {
// final List<?> results = query.list();
// assertThat( results ).hasSize( 1 );
// final Object result = results.get( 0 );
// assertThat( result ).isNotNull();
// assertInstanceOf( Object[].class, result );
// assertThat( (Object[]) result ).hasSize(2);
// assertThat( (Object[]) result ).hasExactlyElementsOfTypes(Product.class, Vendor.class);
// }
//
// {
// final ScrollableResults<?> results = query.scroll();
// assertThat( results.next() ).isTrue();
// final Object result = results.get();
// assertThat( result ).isNotNull();
// assertInstanceOf( Object[].class, result );
// assertThat( (Object[]) result ).hasSize(2);
// assertThat( (Object[]) result ).hasExactlyElementsOfTypes(Product.class, Vendor.class);
// assertThat( results.next() ).isFalse();
// }
// } );
}
@Test @Test
public void testArrayResult(SessionFactoryScope scope) { public void testArrayResult(SessionFactoryScope scope) {
scope.inTransaction( (session) -> {
final SelectionQuery<Object[]> query = session.createSelectionQuery( HQL3, Object[].class );
{
final List<Object[]> results = query.list();
assertThat( results ).hasSize( 1 );
final Object[] result = results.get( 0 );
assertThat( result ).isNotNull();
assertThat( result ).hasSize( 4 );
assertThat( result[ 0 ] ).isNotNull();
assertThat( result[ 1 ] ).isNotNull();
assertThat( result[ 2 ] ).isNotNull();
assertThat( result[ 3 ] ).isNotNull();
assertThat( result ).hasExactlyElementsOfTypes(Product.class, Vendor.class, Product.class, Vendor.class);
}
{
final ScrollableResults<Object[]> results = query.scroll();
assertThat( results.next() ).isTrue();
final Object[] result = results.get();
assertThat( results.next() ).isFalse();
assertThat( result ).isNotNull();
assertThat( result ).hasSize( 4 );
assertThat( result[ 0 ] ).isNotNull();
assertThat( result[ 1 ] ).isNotNull();
assertThat( result[ 2 ] ).isNotNull();
assertThat( result[ 3 ] ).isNotNull();
assertThat( result ).hasExactlyElementsOfTypes(Product.class, Vendor.class, Product.class, Vendor.class);
}
} );
scope.inTransaction( (session) -> { scope.inTransaction( (session) -> {
final SelectionQuery<Object[]> query = session.createSelectionQuery( HQL, Object[].class ); final SelectionQuery<Object[]> query = session.createSelectionQuery( HQL, Object[].class );
@ -92,6 +179,7 @@ public class ImplicitSelectWithJoinTests {
assertThat( result ).hasSize( 2 ); assertThat( result ).hasSize( 2 );
assertThat( result[ 0 ] ).isNotNull(); assertThat( result[ 0 ] ).isNotNull();
assertThat( result[ 1 ] ).isNotNull(); assertThat( result[ 1 ] ).isNotNull();
assertThat( result ).hasExactlyElementsOfTypes(Product.class, Vendor.class);
} }
{ {
@ -103,6 +191,7 @@ public class ImplicitSelectWithJoinTests {
assertThat( result ).hasSize( 2 ); assertThat( result ).hasSize( 2 );
assertThat( result[ 0 ] ).isNotNull(); assertThat( result[ 0 ] ).isNotNull();
assertThat( result[ 1 ] ).isNotNull(); assertThat( result[ 1 ] ).isNotNull();
assertThat( result ).hasExactlyElementsOfTypes(Product.class, Vendor.class);
} }
} ); } );
} }
@ -161,10 +250,14 @@ public class ImplicitSelectWithJoinTests {
@BeforeEach @BeforeEach
public void prepareTestData(SessionFactoryScope scope) { public void prepareTestData(SessionFactoryScope scope) {
scope.inTransaction( (session) -> { scope.inTransaction( (session) -> {
final Vendor vendor = new Vendor( 1, "Steve's Curios", "Acme Corp." ); final Vendor steve = new Vendor( 1, "Steve's Curios", "Acme Corp." );
final Product product = new Product( 10, UUID.fromString( "53886a8a-7082-4879-b430-25cb94415be8" ), vendor ); final Product product1 = new Product( 10, UUID.fromString( "53886a8a-7082-4879-b430-25cb94415be8" ), steve );
session.persist( vendor ); final Vendor gavin = new Vendor( 2, "Gavin & Associates", "Acme Corp." );
session.persist( product ); final Product product2 = new Product( 11, UUID.fromString( "53886a8b-3083-4879-b431-25cb95515be9" ), gavin );
session.persist( steve );
session.persist( product1 );
session.persist( gavin );
session.persist( product2 );
} ); } );
} }