Move annotations.loader tests and implement support for collection returns

This commit is contained in:
Christian Beikov 2021-08-04 21:29:39 +02:00
parent 15f2dca36d
commit b30c9aea8e
13 changed files with 270 additions and 26 deletions

View File

@ -24,6 +24,7 @@ import org.hibernate.boot.jaxb.hbm.spi.JaxbHbmNativeQueryPropertyReturnType;
import org.hibernate.boot.jaxb.hbm.spi.JaxbHbmNativeQueryReturnType;
import org.hibernate.boot.jaxb.hbm.spi.JaxbHbmNativeQueryScalarReturnType;
import org.hibernate.boot.jaxb.hbm.spi.JaxbHbmResultSetMappingType;
import org.hibernate.boot.spi.InFlightMetadataCollector;
import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.metamodel.mapping.BasicValuedModelPart;
@ -702,7 +703,14 @@ public class HbmResultSetMappingDescriptor implements NamedResultSetMappingDescr
Supplier<Map<String, Map<String, JoinDescriptor>>> joinDescriptorsAccess,
String registrationName,
MetadataBuildingContext context) {
this.collectionPath = new NavigablePath( hbmCollectionReturn.getRole() );
final String role = hbmCollectionReturn.getRole();
final int dotIndex = role.indexOf( '.' );
final String entityName = role.substring( 0, dotIndex );
final InFlightMetadataCollector metadataCollector = context.getMetadataCollector();
final String fullEntityName = metadataCollector.getImports().get( entityName );
this.collectionPath = new NavigablePath(
fullEntityName + "." + role.substring( dotIndex + 1 )
);
this.tableAlias = hbmCollectionReturn.getAlias();
if ( tableAlias == null ) {
throw new MappingException(
@ -744,6 +752,7 @@ public class HbmResultSetMappingDescriptor implements NamedResultSetMappingDescr
final FetchParentMemento thisAsParentMemento = resolveParentMemento( resolutionContext );
memento = new ResultMementoCollectionStandard(
tableAlias,
thisAsParentMemento.getNavigablePath(),
(PluralAttributeMapping) thisAsParentMemento.getFetchableContainer()
);

View File

@ -328,7 +328,9 @@ public class SessionFactoryImpl implements SessionFactoryImplementor {
failingQueries.append( sep ).append( entry.getKey() );
sep = ", ";
}
throw new HibernateException( failingQueries.toString() );
final HibernateException exception = new HibernateException( failingQueries.toString() );
errors.values().forEach( exception::addSuppressed );
throw exception;
}
}

View File

@ -11,31 +11,31 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.loader.ast.spi.CollectionLoader;
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.query.named.NamedQueryMemento;
import org.hibernate.query.spi.QueryImplementor;
/**
* @author Steve Ebersole
*/
public class CollectionLoaderNamedQuery implements CollectionLoader {
private final String loaderQueryName;
private final CollectionPersister persister;
private final PluralAttributeMapping attributeMapping;
private final NamedQueryMemento namedQueryMemento;
public CollectionLoaderNamedQuery(
String loaderQueryName,
CollectionPersister persister,
PluralAttributeMapping attributeMapping) {
this.loaderQueryName = loaderQueryName;
public CollectionLoaderNamedQuery(CollectionPersister persister, NamedQueryMemento namedQueryMemento) {
this.persister = persister;
this.attributeMapping = attributeMapping;
this.namedQueryMemento = namedQueryMemento;
}
@Override
public PluralAttributeMapping getLoadable() {
return attributeMapping;
return persister.getAttributeMapping();
}
@Override
@SuppressWarnings("rawtypes")
public PersistentCollection load(Object key, SharedSessionContractImplementor session) {
return null;
final QueryImplementor<PersistentCollection> query = namedQueryMemento.toQuery( session );
query.setParameter( 1, key );
return query.getResultList().get( 0 );
}
}

View File

@ -97,6 +97,7 @@ import org.hibernate.persister.walking.spi.CompositeCollectionElementDefinition;
import org.hibernate.persister.walking.spi.CompositionDefinition;
import org.hibernate.persister.walking.spi.EntityDefinition;
import org.hibernate.pretty.MessageHelper;
import org.hibernate.query.named.NamedQueryMemento;
import org.hibernate.sql.Alias;
import org.hibernate.sql.Delete;
import org.hibernate.sql.Insert;
@ -649,6 +650,14 @@ public abstract class AbstractCollectionPersister
this.indexConverter = null;
this.convertedIndexType = null;
}
if ( queryLoaderName != null ) {
// We must resolve the named query on-demand through the boot model because it isn't initialized yet
final NamedQueryMemento namedQueryMemento = factory.getQueryEngine().getNamedObjectRepository()
.resolve( factory, collectionBootDescriptor.getMetadata(), queryLoaderName );
if ( namedQueryMemento == null ) {
throw new IllegalArgumentException( "Could not resolve named load-query [" + navigableRole + "] : " + queryLoaderName );
}
}
}
@Override
@ -724,9 +733,15 @@ public abstract class AbstractCollectionPersister
@Override
public void postInstantiate() throws MappingException {
collectionLoader = queryLoaderName == null
? createCollectionLoader( LoadQueryInfluencers.NONE )
: new CollectionLoaderNamedQuery( queryLoaderName, this, attributeMapping );
if ( queryLoaderName == null ) {
collectionLoader = createCollectionLoader( LoadQueryInfluencers.NONE );
}
else {
// We pass null as metamodel because we did the initialization during construction already
final NamedQueryMemento namedQueryMemento = factory.getQueryEngine().getNamedObjectRepository()
.resolve( factory, null, queryLoaderName );
collectionLoader = new CollectionLoaderNamedQuery( this, namedQueryMemento );
}
if ( attributeMapping.getIndexDescriptor() != null ) {
collectionElementLoaderByIndex = new CollectionElementLoaderByIndex(
attributeMapping,

View File

@ -27,6 +27,7 @@ import org.hibernate.LockMode;
import org.hibernate.LockOptions;
import org.hibernate.MappingException;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.metamodel.model.domain.AllowableParameterType;
import org.hibernate.metamodel.model.domain.BasicDomainType;
import org.hibernate.transform.ResultTransformer;
@ -431,6 +432,15 @@ public interface NativeQuery<T> extends Query<T>, SynchronizeableQuery {
ReturnProperty addProperty(String propertyName);
}
interface CollectionReturn extends ReturnableResultNode {
String getTableAlias();
PluralAttributeMapping getPluralAttribute();
NavigablePath getNavigablePath();
}
/**
* Allows access to further control how join fetch returns are mapped back
* from result sets.

View File

@ -18,12 +18,15 @@ import org.hibernate.query.results.complete.CompleteResultBuilderCollectionStand
* @author Steve Ebersole
*/
public class ResultMementoCollectionStandard implements ModelPartResultMementoCollection {
private final String tableAlias;
private final NavigablePath navigablePath;
private final PluralAttributeMapping pluralAttributeDescriptor;
public ResultMementoCollectionStandard(
String tableAlias,
NavigablePath navigablePath,
PluralAttributeMapping pluralAttributeDescriptor) {
this.tableAlias = tableAlias;
this.navigablePath = navigablePath;
this.pluralAttributeDescriptor = pluralAttributeDescriptor;
}
@ -43,6 +46,7 @@ public class ResultMementoCollectionStandard implements ModelPartResultMementoCo
Consumer<String> querySpaceConsumer,
ResultSetMappingResolutionContext context) {
return new CompleteResultBuilderCollectionStandard(
tableAlias,
navigablePath,
pluralAttributeDescriptor
);

View File

@ -8,26 +8,75 @@ package org.hibernate.query.results.complete;
import java.util.function.BiFunction;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.mapping.BasicValuedMapping;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.EntityValuedModelPart;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.metamodel.mapping.SelectableConsumer;
import org.hibernate.query.NativeQuery;
import org.hibernate.query.NavigablePath;
import org.hibernate.query.results.DomainResultCreationStateImpl;
import org.hibernate.query.results.FromClauseAccessImpl;
import org.hibernate.query.results.ResultsHelper;
import org.hibernate.query.results.SqlSelectionImpl;
import org.hibernate.query.results.dynamic.DynamicFetchBuilderLegacy;
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.results.graph.DomainResult;
import org.hibernate.sql.results.graph.DomainResultCreationState;
import org.hibernate.sql.results.jdbc.spi.JdbcValuesMetadata;
import static org.hibernate.query.results.ResultsHelper.impl;
/**
* @author Steve Ebersole
*/
public class CompleteResultBuilderCollectionStandard implements CompleteResultBuilderCollection {
public class CompleteResultBuilderCollectionStandard implements CompleteResultBuilderCollection, NativeQuery.CollectionReturn {
private final String tableAlias;
private final NavigablePath navigablePath;
private final PluralAttributeMapping pluralAttributeDescriptor;
private final String[] keyColumnNames;
private final String[] indexColumnNames;
private final String[] elementColumnNames;
public CompleteResultBuilderCollectionStandard(
String tableAlias,
NavigablePath navigablePath,
PluralAttributeMapping pluralAttributeDescriptor) {
this( tableAlias, navigablePath, pluralAttributeDescriptor, null, null, null );
}
public CompleteResultBuilderCollectionStandard(
String tableAlias,
NavigablePath navigablePath,
PluralAttributeMapping pluralAttributeDescriptor,
String[] keyColumnNames,
String[] indexColumnNames,
String[] elementColumnNames) {
this.tableAlias = tableAlias;
this.navigablePath = navigablePath;
this.pluralAttributeDescriptor = pluralAttributeDescriptor;
this.keyColumnNames = keyColumnNames;
this.indexColumnNames = indexColumnNames;
this.elementColumnNames = elementColumnNames;
}
@Override
public String getTableAlias() {
return tableAlias;
}
@Override
public PluralAttributeMapping getPluralAttribute() {
return pluralAttributeDescriptor;
}
@Override
public NavigablePath getNavigablePath() {
return navigablePath;
}
@Override
@ -36,6 +85,88 @@ public class CompleteResultBuilderCollectionStandard implements CompleteResultBu
int resultPosition,
BiFunction<String, String, DynamicFetchBuilderLegacy> legacyFetchResolver,
DomainResultCreationState domainResultCreationState) {
throw new NotYetImplementedFor6Exception( getClass() );
final DomainResultCreationStateImpl creationStateImpl = impl( domainResultCreationState );
final SessionFactoryImplementor sessionFactory = creationStateImpl.getSessionFactory();
final FromClauseAccessImpl fromClauseAccess = creationStateImpl.getFromClauseAccess();
final TableGroup rootTableGroup = pluralAttributeDescriptor.createRootTableGroup(
false,
navigablePath,
tableAlias,
() -> predicate -> {},
creationStateImpl,
sessionFactory
);
fromClauseAccess.registerTableGroup( navigablePath, rootTableGroup );
resolveSelections(
rootTableGroup,
pluralAttributeDescriptor.getKeyDescriptor(),
keyColumnNames,
jdbcResultsMetadata,
creationStateImpl
);
if ( pluralAttributeDescriptor.getIndexDescriptor() != null ) {
resolveSelections(
rootTableGroup,
pluralAttributeDescriptor.getIndexDescriptor(),
indexColumnNames,
jdbcResultsMetadata,
creationStateImpl
);
}
resolveSelections(
rootTableGroup,
pluralAttributeDescriptor.getElementDescriptor(),
elementColumnNames,
jdbcResultsMetadata,
creationStateImpl
);
return pluralAttributeDescriptor.createDomainResult(
navigablePath,
rootTableGroup,
null,
domainResultCreationState
);
}
private void resolveSelections(
TableGroup tableGroup,
ModelPart modelPart,
String[] columnNames,
JdbcValuesMetadata jdbcResultsMetadata,
DomainResultCreationStateImpl creationStateImpl) {
final SelectableConsumer consumer = (selectionIndex, selectableMapping) -> {
final String columnName = columnNames[selectionIndex];
creationStateImpl.resolveSqlSelection(
creationStateImpl.resolveSqlExpression(
SqlExpressionResolver.createColumnReferenceKey(
tableGroup.getTableReference( selectableMapping.getContainingTableExpression() ),
selectableMapping.getSelectionExpression()
),
processingState -> {
final int jdbcPosition = jdbcResultsMetadata.resolveColumnPosition( columnName );
final BasicValuedMapping basicType = (BasicValuedMapping) selectableMapping.getJdbcMapping();
final int valuesArrayPosition = ResultsHelper.jdbcPositionToValuesArrayPosition(
jdbcPosition );
return new SqlSelectionImpl( valuesArrayPosition, basicType );
}
),
selectableMapping.getJdbcMapping().getMappedJavaTypeDescriptor(),
creationStateImpl.getSessionFactory().getTypeConfiguration()
);
};
if ( modelPart instanceof EntityValuedModelPart ) {
final EntityMappingType entityMappingType = ( (EntityValuedModelPart) modelPart ).getEntityMappingType();
int index = entityMappingType.getIdentifierMapping().forEachSelectable( consumer );
if ( entityMappingType.getDiscriminatorMapping() != null ) {
index += entityMappingType.getDiscriminatorMapping().forEachSelectable( index, consumer );
}
entityMappingType.forEachSelectable( index, consumer );
}
else {
modelPart.forEachSelectable( consumer );
}
}
}

View File

@ -6,6 +6,7 @@
*/
package org.hibernate.query.sql.internal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
@ -20,18 +21,22 @@ import org.hibernate.MappingException;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.loader.internal.AliasConstantsHelper;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.persister.collection.QueryableCollection;
import org.hibernate.persister.collection.SQLLoadableCollection;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.persister.entity.Joinable;
import org.hibernate.persister.entity.Loadable;
import org.hibernate.persister.entity.SQLLoadable;
import org.hibernate.query.NativeQuery;
import org.hibernate.query.NavigablePath;
import org.hibernate.query.results.FetchBuilder;
import org.hibernate.query.results.ResultSetMapping;
import org.hibernate.query.results.ResultSetMappingImpl;
import org.hibernate.query.results.complete.CompleteResultBuilderCollectionStandard;
import org.hibernate.query.results.dynamic.DynamicFetchBuilderLegacy;
import org.hibernate.query.results.dynamic.DynamicResultBuilderEntityStandard;
import org.hibernate.type.EntityType;
@ -107,6 +112,16 @@ public class ResultSetMappingProcessor implements SQLQueryParser.ParserContext {
alias2Return.put( rootReturn.getTableAlias(), rootReturn );
resultBuilder.visitFetchBuilders( this::processFetchBuilder );
}
else if ( resultBuilder instanceof NativeQuery.CollectionReturn ) {
final NativeQuery.CollectionReturn collectionReturn = (NativeQuery.CollectionReturn) resultBuilder;
alias2Return.put( collectionReturn.getTableAlias(), collectionReturn );
Map<String, String[]> propertyResultsMap = Collections.emptyMap();//fetchReturn.getPropertyResultsMap()
addCollection(
collectionReturn.getNavigablePath().getFullPath(),
collectionReturn.getTableAlias(),
propertyResultsMap
);
}
}
);
resultSetMapping.visitLegacyFetchBuilders(
@ -161,6 +176,23 @@ public class ResultSetMappingProcessor implements SQLQueryParser.ParserContext {
alias2Return.put( rootReturn.getTableAlias(), resultBuilderEntity );
}
}
else if ( resultBuilder instanceof NativeQuery.CollectionReturn ) {
final NativeQuery.CollectionReturn collectionReturn = (NativeQuery.CollectionReturn) resultBuilder;
final String suffix = alias2CollectionSuffix.get( collectionReturn.getTableAlias() );
if ( suffix == null ) {
resultSetMapping.addResultBuilder( resultBuilder );
}
else {
final CompleteResultBuilderCollectionStandard resultBuilderCollection = createSuffixedResultBuilder(
collectionReturn,
suffix,
alias2Suffix.get( collectionReturn.getTableAlias() )
);
resultSetMapping.addResultBuilder( resultBuilderCollection );
alias2Return.put( collectionReturn.getTableAlias(), resultBuilderCollection );
}
}
else {
resultSetMapping.addResultBuilder( resultBuilder );
}
@ -301,6 +333,44 @@ public class ResultSetMappingProcessor implements SQLQueryParser.ParserContext {
return resultBuilderEntity;
}
private CompleteResultBuilderCollectionStandard createSuffixedResultBuilder(
NativeQuery.CollectionReturn collectionReturn,
String suffix,
String entitySuffix) {
final CollectionPersister collectionPersister = collectionReturn.getPluralAttribute().getCollectionDescriptor();
final String[] elementColumnAliases;
if ( collectionPersister.getElementType().isEntityType() ) {
final Loadable elementPersister = (Loadable) ( ( QueryableCollection ) collectionPersister).getElementPersister();
final String[] propertyNames = elementPersister.getPropertyNames();
final String[] identifierAliases = elementPersister.getIdentifierAliases( entitySuffix );
final String discriminatorAlias = elementPersister.getDiscriminatorAlias( entitySuffix );
final List<String> aliases = new ArrayList<>(
propertyNames.length + identifierAliases.length + ( discriminatorAlias == null ? 0 : 1 )
);
Collections.addAll( aliases, identifierAliases );
if ( discriminatorAlias != null ) {
aliases.add( discriminatorAlias );
}
for ( int i = 0; i < propertyNames.length; i++ ) {
Collections.addAll( aliases, elementPersister.getPropertyAliases( entitySuffix, i ) );
}
elementColumnAliases = ArrayHelper.toStringArray( aliases );
}
else {
elementColumnAliases = collectionPersister.getElementColumnAliases( suffix );
}
return new CompleteResultBuilderCollectionStandard(
collectionReturn.getTableAlias(),
collectionReturn.getNavigablePath(),
collectionReturn.getPluralAttribute(),
collectionPersister.getKeyColumnAliases( suffix ),
collectionPersister.hasIndex()
? collectionPersister.getIndexColumnAliases( suffix )
: null,
elementColumnAliases
);
}
private SQLLoadable getSQLLoadable(String entityName) throws MappingException {
EntityPersister persister = factory.getEntityPersister( entityName );
if ( !(persister instanceof SQLLoadable) ) {

View File

@ -4,7 +4,7 @@
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.test.annotations.loader;
package org.hibernate.orm.test.annotations.loader;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
@ -27,7 +27,7 @@ public class LoaderTest extends BaseCoreFunctionalTestCase {
@Override
protected String[] getOrmXmlFiles() {
return new String[] {
"org/hibernate/test/annotations/loader/Loader.hbm.xml"
"org/hibernate/orm/test/annotations/loader/Loader.hbm.xml"
};
}

View File

@ -4,7 +4,7 @@
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.test.annotations.loader;
package org.hibernate.orm.test.annotations.loader;
import javax.persistence.Column;
import javax.persistence.Entity;
@ -20,6 +20,7 @@ import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.hibernate.testing.util.ExceptionUtil;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
/**
@ -41,8 +42,10 @@ public class LoaderWithInvalidQueryTest extends BaseEntityManagerFunctionalTestC
}
catch (Exception expected) {
HibernateException rootCause = (HibernateException) ExceptionUtil.rootCause( expected );
assertTrue(rootCause.getMessage().contains( "could not resolve property: valid" ));
assertTrue(rootCause.getMessage().contains( "_Person is not mapped" ));
Throwable[] suppressed = rootCause.getSuppressed();
assertEquals( 2, suppressed.length );
assertTrue( ExceptionUtil.rootCause( suppressed[0] ).getMessage().contains( "Could not resolve attribute named `valid`" ) );
assertTrue( ExceptionUtil.rootCause( suppressed[1] ).getMessage().contains( "Could not resolve entity reference: _Person" ) );
}
}

View File

@ -4,7 +4,7 @@
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.test.annotations.loader;
package org.hibernate.orm.test.annotations.loader;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

View File

@ -4,7 +4,7 @@
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.test.annotations.loader;
package org.hibernate.orm.test.annotations.loader;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.Entity;

View File

@ -17,7 +17,7 @@
-->
<hibernate-mapping package="org.hibernate.test.annotations.loader">
<hibernate-mapping package="org.hibernate.orm.test.annotations.loader">
<sql-query name="loadByTeam">
<load-collection alias="p" role="Team.players"/>