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 );
}
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 ) {
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.
* The key is the short-hand 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.
* 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 shorthand enum literals that don't use FQNs.
*/
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
* 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
* type {@code Query<?>}, to match the signature of the JPA method
* {@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
*
* @return The {@link Query} instance for manipulation and execution
@ -41,21 +58,47 @@ public interface QueryProducer {
* @see jakarta.persistence.EntityManager#createQuery(String)
*
* @deprecated use {@link #createQuery(String, Class)},
* {@link #createSelectionQuery} or {@link #createMutationQuery(String)}
* depending on intention
* {@link #createSelectionQuery(String, Class)}, or
* {@link #createMutationQuery(String)} depending on intention
*/
@Deprecated(since = "6.0") @SuppressWarnings("rawtypes")
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>
* The returned {@code Query} may be executed by calling
* {@link Query#getResultList()} or {@link Query#getSingleResult()}.
*
* @param queryString The HQL query
* @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)
*/
@ -98,15 +141,25 @@ public interface QueryProducer {
/**
* Create a {@link NativeQuery} instance for the given native SQL query
* using implicit mapping to the specified Java type.
* <p>
* If the given class is an entity class, this method is equivalent to
* {@code createNativeQuery(sqlString).addEntity("alias1", resultClass)}.
* using an implicit mapping to the specified Java type.
* <ul>
* <li>If the given class is an entity class, this method is equivalent
* 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 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)
*/
@ -114,13 +167,13 @@ public interface QueryProducer {
/**
* 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>
* 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)}.
*
* @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
*
* @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
* using an explicit mapping to the specified Java type.
* <p>
* The given result set mapping name must identify a mapping defined by a
* {@link jakarta.persistence.SqlResultSetMapping} annotation.
* The given result set mapping name must identify a mapping defined by
* a {@link jakarta.persistence.SqlResultSetMapping} annotation.
*
* @param sqlString The native (SQL) query string
* @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.SqlResultSetMapping
@ -153,8 +206,8 @@ public interface QueryProducer {
* Create a {@link NativeQuery} instance for the given native SQL query
* using an explicit mapping to the specified Java type.
* <p>
* The given result set mapping name must identify a mapping defined by a
* {@link jakarta.persistence.SqlResultSetMapping} annotation.
* The given result set mapping name must identify a mapping defined by
* a {@link jakarta.persistence.SqlResultSetMapping} annotation.
*
* @param sqlString The native (SQL) query string
* @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);
/**
* 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
*
* @see 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 #createSelectionQuery(String, Class)} to avoid ambiguity.
*
* @throws IllegalSelectQueryException if the given HQL query
* is an insert, update or delete query
*
* @deprecated Use {@link #createSelectionQuery(String, Class)}
*/
@Deprecated(since = "6.3")
SelectionQuery<?> createSelectionQuery(String hqlString);
/**
* Create a {@link SelectionQuery} reference for the given HQL.
*
* Only valid for select queries
* Create a {@link SelectionQuery} 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>
* 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)
*
@ -191,14 +292,15 @@ public interface QueryProducer {
<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)
*/
<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.
*
* @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.
* 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
*
@ -244,8 +346,7 @@ public interface QueryProducer {
*
* @see jakarta.persistence.EntityManager#createNamedQuery(String)
*
* @deprecated use {@link #createNamedQuery(String, Class)} or
* {@link #createNamedMutationQuery(String)}
* @deprecated use {@link #createNamedQuery(String, Class)}
*/
@Deprecated(since = "6.0") @SuppressWarnings("rawtypes")
Query createNamedQuery(String name);
@ -269,20 +370,29 @@ public interface QueryProducer {
<R> Query<R> createNamedQuery(String name, Class<R> resultClass);
/**
* Create a {@link SelectionQuery} instance for the
* named {@link jakarta.persistence.NamedQuery}
* Create a {@link SelectionQuery} instance for the named
* {@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
*
* @deprecated use {@link #createNamedSelectionQuery(String, Class)}
*/
@Deprecated(since = "6.3")
SelectionQuery<?> createNamedSelectionQuery(String name);
/**
* Create a {@link SelectionQuery} instance for the
* named {@link jakarta.persistence.NamedQuery} with the expected
* result-type
* Create a {@link SelectionQuery} instance for the named
* {@link jakarta.persistence.NamedQuery} with the given 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
*/
<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.
*
* @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
*
@ -317,7 +427,7 @@ public interface QueryProducer {
/**
* 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
*
@ -329,7 +439,7 @@ public interface QueryProducer {
/**
* 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
*

View File

@ -31,6 +31,7 @@ import java.util.Locale;
import java.util.Map;
import java.util.Set;
import org.hibernate.AssertionFailure;
import org.hibernate.QueryException;
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
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) {
// for now, this is slightly different to the legacy behavior where
// the root and each non-fetched-join was selected. For now, here, we simply
// select the root
final SqmSelectClause selectClause;
if ( fromClause.getNumberOfRoots() == 0 ) {
// should be impossible to get here
throw new AssertionFailure( "query has no 'select' clause, and no root entities");
}
final boolean expectingArray = expectedResultType != null && expectedResultType.isArray();
if ( expectingArray ) {
// triggers legacy interpretation of returning all roots
// and non-fetched joins
selectClause = new SqmSelectClause(
false,
creationContext.getNodeBuilder()
);
final NodeBuilder nodeBuilder = creationContext.getNodeBuilder();
final SqmSelectClause selectClause;
final boolean singleEntityResult;
if ( expectedResultType == null ) {
// no result type was specified
// - 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 {
selectClause = new SqmSelectClause(
false,
fromClause.getNumberOfRoots(),
creationContext.getNodeBuilder()
);
singleEntityResult = creationContext.getJpaMetamodel().findEntityType( expectedResultType ) != null;
if ( singleEntityResult ) {
// the result type is an entity class
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) -> {
selectClause.addSelection( new SqmSelection<>( sqmRoot, sqmRoot.getAlias(), creationContext.getNodeBuilder() ) );
if ( expectingArray ) {
selectClause.addSelection( new SqmSelection<>( sqmRoot, sqmRoot.getAlias(), nodeBuilder) );
if ( !singleEntityResult ) {
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) {
final StatisticsImplementor statistics = statisticsSupplier.get();
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 DomainParameterXref domainParameterXref;

View File

@ -9,7 +9,6 @@ package org.hibernate.query.internal;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.function.Supplier;
import jakarta.persistence.Tuple;
import org.hibernate.internal.util.collections.BoundedConcurrentHashMap;
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.sqm.internal.DomainParameterXref;
import org.hibernate.query.sqm.tree.SqmStatement;
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
import org.hibernate.stat.spi.StatisticsImplementor;
import org.jboss.logging.Logger;
@ -99,6 +99,45 @@ public class QueryInterpretationCacheStandardImpl implements QueryInterpretation
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
public HqlInterpretation resolveHqlInterpretation(
String queryString,
@ -106,17 +145,7 @@ public class QueryInterpretationCacheStandardImpl implements QueryInterpretation
Function<String, SqmStatement<?>> creator) {
log.tracef( "QueryPlan#resolveHqlInterpretation( `%s` )", queryString );
final String cacheKey;
if ( expectedResultType != null
&& ( expectedResultType.isArray() || Tuple.class.isAssignableFrom( expectedResultType ) ) ) {
cacheKey = "multi_" + queryString;
}
else {
cacheKey = queryString;
}
final HqlInterpretation existing = hqlInterpretationCache.get( cacheKey );
final HqlInterpretation existing = getFromCache( queryString, expectedResultType );
if ( existing != null ) {
final StatisticsImplementor statistics = statisticsSupplier.get();
if ( statistics.isStatisticsEnabled() ) {
@ -124,10 +153,12 @@ public class QueryInterpretationCacheStandardImpl implements QueryInterpretation
}
return existing;
}
final HqlInterpretation hqlInterpretation = createHqlInterpretation( queryString, creator, statisticsSupplier );
hqlInterpretationCache.put( cacheKey, hqlInterpretation );
return hqlInterpretation;
else {
final HqlInterpretation hqlInterpretation =
createHqlInterpretation( queryString, creator, statisticsSupplier );
putInCache( queryString, expectedResultType, hqlInterpretation );
return hqlInterpretation;
}
}
protected static HqlInterpretation createHqlInterpretation(
@ -136,7 +167,7 @@ public class QueryInterpretationCacheStandardImpl implements QueryInterpretation
Supplier<StatisticsImplementor> statisticsSupplier) {
final StatisticsImplementor statistics = statisticsSupplier.get();
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 ParameterMetadataImplementor parameterMetadata;

View File

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

View File

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

View File

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

View File

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

View File

@ -112,6 +112,8 @@ public interface NativeQueryImplementor<R> extends QueryImplementor<R>, NativeQu
@Override
NativeQueryImplementor<R> addEntity(@SuppressWarnings("rawtypes") Class entityType);
NativeQueryImplementor<R> addEntity(Class<R> entityType, LockMode lockMode);
@Override
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
public boolean hasJoins() {
return !( joins == null || joins.isEmpty() );
return joins != null && !joins.isEmpty();
}
@Override
@ -330,7 +330,6 @@ public abstract class AbstractSqmFrom<O,T> extends AbstractSqmPath<T> implements
}
@Override
@SuppressWarnings("unchecked")
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 );
addSqmJoin( join );
@ -646,7 +645,6 @@ public abstract class AbstractSqmFrom<O,T> extends AbstractSqmPath<T> implements
}
@Override
@SuppressWarnings("unchecked")
public <A> SqmSingularJoin<T, A> fetch(SingularAttribute<? super T, A> attribute, JoinType jt) {
final SqmSingularJoin<T, A> join = buildSingularJoin(
(SingularPersistentAttribute<? super T, A>) attribute,
@ -663,7 +661,6 @@ public abstract class AbstractSqmFrom<O,T> extends AbstractSqmPath<T> implements
}
@Override
@SuppressWarnings("unchecked")
public <A> SqmAttributeJoin<T, A> fetch(PluralAttribute<? super T, ?, A> attribute, JoinType jt) {
return buildJoin(
(PluralPersistentAttribute<? super T, ?, A>) attribute,

View File

@ -24,6 +24,7 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
/**
* @author Steve Ebersole
@ -34,6 +35,7 @@ import static org.assertj.core.api.Assertions.assertThat;
public class ImplicitSelectWithJoinTests {
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 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
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
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) -> {
final SelectionQuery<Object[]> query = session.createSelectionQuery( HQL, Object[].class );
@ -92,6 +179,7 @@ public class ImplicitSelectWithJoinTests {
assertThat( result ).hasSize( 2 );
assertThat( result[ 0 ] ).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[ 0 ] ).isNotNull();
assertThat( result[ 1 ] ).isNotNull();
assertThat( result ).hasExactlyElementsOfTypes(Product.class, Vendor.class);
}
} );
}
@ -161,10 +250,14 @@ public class ImplicitSelectWithJoinTests {
@BeforeEach
public void prepareTestData(SessionFactoryScope scope) {
scope.inTransaction( (session) -> {
final Vendor vendor = new Vendor( 1, "Steve's Curios", "Acme Corp." );
final Product product = new Product( 10, UUID.fromString( "53886a8a-7082-4879-b430-25cb94415be8" ), vendor );
session.persist( vendor );
session.persist( product );
final Vendor steve = new Vendor( 1, "Steve's Curios", "Acme Corp." );
final Product product1 = new Product( 10, UUID.fromString( "53886a8a-7082-4879-b430-25cb94415be8" ), steve );
final Vendor gavin = new Vendor( 2, "Gavin & Associates", "Acme Corp." );
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 );
} );
}