diff --git a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java index 69d3dac1c6..50c976c69b 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java @@ -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 ) diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/JpaMetamodel.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/JpaMetamodel.java index e6a13c7946..12a676d7c8 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/JpaMetamodel.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/JpaMetamodel.java @@ -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, Enum>> getAllowedEnumLiteralTexts(); diff --git a/hibernate-core/src/main/java/org/hibernate/query/QueryProducer.java b/hibernate-core/src/main/java/org/hibernate/query/QueryProducer.java index 8528faa72f..e645f5fc0e 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/QueryProducer.java +++ b/hibernate-core/src/main/java/org/hibernate/query/QueryProducer.java @@ -29,11 +29,28 @@ public interface QueryProducer { /** * Create a {@link Query} instance for the given HQL query, or * HQL insert, update, or delete statement. + *

+ * If a query has no explicit {@code select} list, the select list + * is inferred: + *

* * @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. + * + *

+ * If a query has no explicit {@code select} list, the select list + * is inferred from the given query result type: + *

*

* 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. - *

- * 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. + *

* * @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. *

- * 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. *

- * 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. *

- * 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 { NativeQuery createNativeQuery(String sqlString, String resultSetMappingName, Class resultClass); /** - * Create a {@link SelectionQuery} reference for the given HQL. + * Create a {@link SelectionQuery} reference for the given HQL + * {@code select} statement. + *

+ * If the statement has no explicit {@code select} list, the + * select list is inferred: + *

* - * 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. + * + *

+ * If a query has no explicit {@code select} list, the select list + * is inferred from the given query result type: + *

+ *

+ * 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 { SelectionQuery createSelectionQuery(String hqlString, Class 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) */ SelectionQuery createSelectionQuery(CriteriaQuery 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 { Query createNamedQuery(String name, Class 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 */ SelectionQuery createNamedSelectionQuery(String name, Class 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 * diff --git a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java index 072d3354b2..87b104a198 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java @@ -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 extends HqlParserBaseVisitor 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 ); } } ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryInterpretationCacheDisabledImpl.java b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryInterpretationCacheDisabledImpl.java index 598a71a3cb..eb563ff6d9 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryInterpretationCacheDisabledImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryInterpretationCacheDisabledImpl.java @@ -63,7 +63,7 @@ public class QueryInterpretationCacheDisabledImpl implements QueryInterpretation public HqlInterpretation resolveHqlInterpretation(String queryString, Class expectedResultType, Function> 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; diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryInterpretationCacheStandardImpl.java b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryInterpretationCacheStandardImpl.java index bf8fef9b1c..72e43ce796 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryInterpretationCacheStandardImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryInterpretationCacheStandardImpl.java @@ -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> 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 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; diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/Builders.java b/hibernate-core/src/main/java/org/hibernate/query/results/Builders.java index 211f659e4a..f936786199 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/Builders.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/Builders.java @@ -106,7 +106,7 @@ public class Builders { Class jdbcJavaType, AttributeConverter converter, SessionFactoryImplementor sessionFactory) { - return new DynamicResultBuilderBasicConverted( columnAlias, domainJavaType, jdbcJavaType, converter, sessionFactory ); + return new DynamicResultBuilderBasicConverted<>( columnAlias, domainJavaType, jdbcJavaType, converter, sessionFactory ); } public static ResultBuilder converted( @@ -123,7 +123,7 @@ public class Builders { Class jdbcJavaType, Class> 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 diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicResultBuilderEntityCalculated.java b/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicResultBuilderEntityCalculated.java index b082fb69ea..2d6258ed93 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicResultBuilderEntityCalculated.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicResultBuilderEntityCalculated.java @@ -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 diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/implicit/ImplicitResultClassBuilder.java b/hibernate-core/src/main/java/org/hibernate/query/results/implicit/ImplicitResultClassBuilder.java index 7726e02a53..ea8613d15d 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/implicit/ImplicitResultClassBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/implicit/ImplicitResultClassBuilder.java @@ -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; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeQueryImpl.java b/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeQueryImpl.java index ec52a8db1f..beb5ba8f2d 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeQueryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeQueryImpl.java @@ -1014,6 +1014,11 @@ public class NativeQueryImpl return addEntity( entityType.getName() ); } + @Override + public NativeQueryImplementor addEntity(Class entityType, LockMode lockMode) { + return addEntity( StringHelper.unqualify( entityType.getName() ), entityType.getName(), lockMode); + } + @Override public NativeQueryImplementor addEntity(String tableAlias, @SuppressWarnings("rawtypes") Class entityClass) { return addEntity( tableAlias, entityClass.getName() ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sql/spi/NativeQueryImplementor.java b/hibernate-core/src/main/java/org/hibernate/query/sql/spi/NativeQueryImplementor.java index 65afbf6e7d..c621fc3e8f 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sql/spi/NativeQueryImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sql/spi/NativeQueryImplementor.java @@ -112,6 +112,8 @@ public interface NativeQueryImplementor extends QueryImplementor, NativeQu @Override NativeQueryImplementor addEntity(@SuppressWarnings("rawtypes") Class entityType); + NativeQueryImplementor addEntity(Class entityType, LockMode lockMode); + @Override NativeQueryImplementor addEntity(String tableAlias, @SuppressWarnings("rawtypes") Class entityType); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/AbstractSqmFrom.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/AbstractSqmFrom.java index 06869348a4..54d7c40024 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/AbstractSqmFrom.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/AbstractSqmFrom.java @@ -238,7 +238,7 @@ public abstract class AbstractSqmFrom extends AbstractSqmPath implements @Override public boolean hasJoins() { - return !( joins == null || joins.isEmpty() ); + return joins != null && !joins.isEmpty(); } @Override @@ -330,7 +330,6 @@ public abstract class AbstractSqmFrom extends AbstractSqmPath implements } @Override - @SuppressWarnings("unchecked") public SqmSingularJoin join(SingularAttribute attribute, JoinType jt) { final SqmSingularJoin join = buildSingularJoin( (SingularPersistentAttribute) attribute, SqmJoinType.from( jt ), false ); addSqmJoin( join ); @@ -646,7 +645,6 @@ public abstract class AbstractSqmFrom extends AbstractSqmPath implements } @Override - @SuppressWarnings("unchecked") public SqmSingularJoin fetch(SingularAttribute attribute, JoinType jt) { final SqmSingularJoin join = buildSingularJoin( (SingularPersistentAttribute) attribute, @@ -663,7 +661,6 @@ public abstract class AbstractSqmFrom extends AbstractSqmPath implements } @Override - @SuppressWarnings("unchecked") public SqmAttributeJoin fetch(PluralAttribute attribute, JoinType jt) { return buildJoin( (PluralPersistentAttribute) attribute, diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/results/ImplicitSelectWithJoinTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/results/ImplicitSelectWithJoinTests.java index 04614ad2dc..0e684ab7b2 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/query/results/ImplicitSelectWithJoinTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/results/ImplicitSelectWithJoinTests.java @@ -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 query = session.createSelectionQuery( HQL3, Object[].class ); + + { + final List 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 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 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 ); } ); }