org.hibernate.query.results.DomainResultCreationStateImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of hibernate-core Show documentation
Show all versions of hibernate-core Show documentation
Hibernate's core ORM functionality
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.query.results;
import java.util.AbstractMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
import org.hibernate.Internal;
import org.hibernate.LockMode;
import org.hibernate.engine.FetchTiming;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.internal.util.collections.Stack;
import org.hibernate.internal.util.collections.StandardStack;
import org.hibernate.metamodel.mapping.Association;
import org.hibernate.metamodel.mapping.AssociationKey;
import org.hibernate.metamodel.mapping.AttributeMapping;
import org.hibernate.metamodel.mapping.CompositeIdentifierMapping;
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
import org.hibernate.metamodel.mapping.EntityValuedModelPart;
import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.internal.BasicValuedCollectionPart;
import org.hibernate.metamodel.mapping.internal.NonAggregatedIdentifierMappingImpl;
import org.hibernate.metamodel.mapping.internal.SingleAttributeIdentifierMapping;
import org.hibernate.query.EntityIdentifierNavigablePath;
import org.hibernate.query.NavigablePath;
import org.hibernate.query.results.dynamic.DynamicFetchBuilderLegacy;
import org.hibernate.query.results.dynamic.LegacyFetchResolver;
import org.hibernate.sql.ast.spi.SqlAliasBaseGenerator;
import org.hibernate.sql.ast.spi.SqlAliasBaseManager;
import org.hibernate.sql.ast.spi.SqlAstCreationContext;
import org.hibernate.sql.ast.spi.SqlAstCreationState;
import org.hibernate.sql.ast.spi.SqlAstProcessingState;
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.results.ResultsLogger;
import org.hibernate.sql.results.graph.DomainResultCreationState;
import org.hibernate.sql.results.graph.Fetch;
import org.hibernate.sql.results.graph.FetchParent;
import org.hibernate.sql.results.graph.Fetchable;
import org.hibernate.sql.results.graph.FetchableContainer;
import org.hibernate.sql.results.jdbc.spi.JdbcValuesMetadata;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.spi.TypeConfiguration;
import static org.hibernate.query.results.ResultsHelper.attributeName;
/**
* @author Steve Ebersole
*/
@Internal
public class DomainResultCreationStateImpl
implements DomainResultCreationState, SqlAstCreationState, SqlAstProcessingState, SqlExpressionResolver {
private final String stateIdentifier;
private final FromClauseAccessImpl fromClauseAccess;
private final JdbcValuesMetadata jdbcResultsMetadata;
private final Consumer sqlSelectionConsumer;
private final Map sqlSelectionMap = new HashMap<>();
private boolean allowPositionalSelections = true;
private final SqlAliasBaseManager sqlAliasBaseManager;
private final LegacyFetchResolverImpl legacyFetchResolver;
private final SessionFactoryImplementor sessionFactory;
private final Stack> fetchBuilderResolverStack = new StandardStack<>( fetchableName -> null );
private final Stack> relativePathStack = new StandardStack<>();
private Map registeredLockModes;
private boolean processingKeyFetches = false;
private boolean resolvingCircularFetch;
private ForeignKeyDescriptor.Nature currentlyResolvingForeignKeySide;
public DomainResultCreationStateImpl(
String stateIdentifier,
JdbcValuesMetadata jdbcResultsMetadata,
Map> legacyFetchBuilders,
Consumer sqlSelectionConsumer,
SessionFactoryImplementor sessionFactory) {
this.stateIdentifier = stateIdentifier;
this.jdbcResultsMetadata = jdbcResultsMetadata;
this.sqlSelectionConsumer = sqlSelectionConsumer;
this.fromClauseAccess = new FromClauseAccessImpl();
this.sqlAliasBaseManager = new SqlAliasBaseManager();
this.legacyFetchResolver = new LegacyFetchResolverImpl( legacyFetchBuilders );
this.sessionFactory = sessionFactory;
}
public LegacyFetchResolver getLegacyFetchResolver() {
return legacyFetchResolver;
}
public SessionFactoryImplementor getSessionFactory() {
return sessionFactory;
}
public int getNumberOfProcessedSelections() {
return sqlSelectionMap.size();
}
public boolean arePositionalSelectionsAllowed() {
return allowPositionalSelections;
}
public void disallowPositionalSelections() {
ResultsLogger.LOGGER.debugf( "Disallowing positional selections : %s", stateIdentifier );
this.allowPositionalSelections = false;
}
public JdbcValuesMetadata getJdbcResultsMetadata() {
return jdbcResultsMetadata;
}
public NavigablePath getCurrentRelativePath() {
final Map.Entry entry = relativePathStack.getCurrent();
return entry == null ? null : entry.getValue();
}
public void pushExplicitFetchMementoResolver(Function resolver) {
fetchBuilderResolverStack.push( resolver );
}
public Function getCurrentExplicitFetchMementoResolver() {
return fetchBuilderResolverStack.getCurrent();
}
public Function popExplicitFetchMementoResolver() {
return fetchBuilderResolverStack.pop();
}
@SuppressWarnings( "unused" )
public void withExplicitFetchMementoResolver(Function resolver, Runnable runnable) {
pushExplicitFetchMementoResolver( resolver );
try {
runnable.run();
}
finally {
final Function popped = popExplicitFetchMementoResolver();
assert popped == resolver;
}
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// DomainResultCreationState
@Override
public FromClauseAccessImpl getFromClauseAccess() {
return fromClauseAccess;
}
@Override
public DomainResultCreationStateImpl getSqlAstCreationState() {
return this;
}
@Override
public SqlAliasBaseManager getSqlAliasBaseManager() {
return sqlAliasBaseManager;
}
@Override
public boolean forceIdentifierSelection() {
return true;
}
@Override
public boolean registerVisitedAssociationKey(AssociationKey associationKey) {
return false;
}
@Override
public boolean isAssociationKeyVisited(AssociationKey associationKey) {
return false;
}
@Override
public ModelPart resolveModelPart(NavigablePath navigablePath) {
final TableGroup tableGroup = fromClauseAccess.findTableGroup( navigablePath );
if ( tableGroup != null ) {
return tableGroup.getModelPart();
}
if ( navigablePath.getParent() != null ) {
final TableGroup parentTableGroup = fromClauseAccess.findTableGroup( navigablePath.getParent() );
if ( parentTableGroup != null ) {
return parentTableGroup.getModelPart().findSubPart( navigablePath.getLocalName(), null );
}
}
return null;
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// SqlAstCreationState
@Override
public DomainResultCreationStateImpl getSqlExpressionResolver() {
return getCurrentProcessingState();
}
@Override
public void registerLockMode(String identificationVariable, LockMode explicitLockMode) {
if (registeredLockModes == null ) {
registeredLockModes = new HashMap<>();
}
registeredLockModes.put( identificationVariable, explicitLockMode );
}
public Map getRegisteredLockModes() {
return registeredLockModes;
}
@Override
public DomainResultCreationStateImpl getCurrentProcessingState() {
return this;
}
public SqlAstCreationContext getCreationContext() {
return getSessionFactory();
}
@Override
public SqlAliasBaseGenerator getSqlAliasBaseGenerator() {
return sqlAliasBaseManager;
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// SqlAstProcessingState
@Override
public SqlAstProcessingState getParentState() {
return null;
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// SqlExpressionResolver
@Override
public Expression resolveSqlExpression(
String key,
Function creator) {
final SqlSelectionImpl existing = sqlSelectionMap.get( key );
if ( existing != null ) {
return existing;
}
final Expression created = creator.apply( this );
if ( created instanceof SqlSelectionImpl ) {
sqlSelectionMap.put( key, (SqlSelectionImpl) created );
sqlSelectionConsumer.accept( (SqlSelectionImpl) created );
}
else if ( created instanceof ColumnReference ) {
final ColumnReference columnReference = (ColumnReference) created;
final String columnExpression = columnReference.getColumnExpression();
final int jdbcPosition = jdbcResultsMetadata.resolveColumnPosition( columnExpression );
final int valuesArrayPosition = ResultsHelper.jdbcPositionToValuesArrayPosition( jdbcPosition );
final SqlSelectionImpl sqlSelection = new SqlSelectionImpl(
valuesArrayPosition,
columnReference.getJdbcMapping()
);
sqlSelectionMap.put( key, sqlSelection );
sqlSelectionConsumer.accept( sqlSelection );
return sqlSelection;
}
return created;
}
@Override
public SqlSelection resolveSqlSelection(
Expression expression,
JavaType javaTypeDescriptor,
TypeConfiguration typeConfiguration) {
if ( expression == null ) {
throw new IllegalArgumentException( "Expression cannot be null" );
}
assert expression instanceof SqlSelectionImpl;
return (SqlSelection) expression;
}
private static class LegacyFetchResolverImpl implements LegacyFetchResolver {
private final Map> legacyFetchBuilders;
public LegacyFetchResolverImpl(Map> legacyFetchBuilders) {
this.legacyFetchBuilders = legacyFetchBuilders;
}
@Override
public DynamicFetchBuilderLegacy resolve(String ownerTableAlias, String fetchedPartPath) {
if ( legacyFetchBuilders == null ) {
return null;
}
final Map fetchBuilders = legacyFetchBuilders.get( ownerTableAlias );
if ( fetchBuilders == null ) {
return null;
}
return fetchBuilders.get( fetchedPartPath );
}
}
@Override
public List visitFetches(FetchParent fetchParent) {
final FetchableContainer fetchableContainer = fetchParent.getReferencedMappingContainer();
final List fetches = CollectionHelper.arrayList( fetchableContainer.getNumberOfFetchables() );
final Consumer fetchableConsumer = fetchable -> {
final String fetchableName = fetchable.getFetchableName();
Map.Entry currentEntry;
if ( relativePathStack.isEmpty() ) {
currentEntry = new AbstractMap.SimpleEntry<>(
getRelativePath( "", fetchable, fetchableContainer ),
new NavigablePath( fetchableName )
);
}
else {
final Map.Entry oldEntry = relativePathStack.getCurrent();
final String key = oldEntry.getKey();
currentEntry = new AbstractMap.SimpleEntry<>(
getRelativePath( key, fetchable, fetchableContainer ),
oldEntry.getValue().append( fetchableName )
);
}
// todo (6.0): figure out if we can somehow create the navigable paths in a better way
final String fullPath = currentEntry.getKey();
FetchBuilder explicitFetchBuilder = fetchBuilderResolverStack
.getCurrent()
.apply( fullPath );
DynamicFetchBuilderLegacy fetchBuilderLegacy;
if ( explicitFetchBuilder == null ) {
fetchBuilderLegacy = legacyFetchResolver.resolve(
fromClauseAccess.findTableGroup( fetchParent.getNavigablePath() )
.getPrimaryTableReference()
.getIdentificationVariable(),
fetchableName
);
}
else {
fetchBuilderLegacy = null;
}
if ( fetchable instanceof Association && fetchable.getMappedFetchOptions().getTiming() == FetchTiming.DELAYED ) {
final Association association = (Association) fetchable;
final ForeignKeyDescriptor foreignKeyDescriptor = association.getForeignKeyDescriptor();
final String partName = attributeName( foreignKeyDescriptor.getSide( association.getSideNature().inverse() )
.getModelPart());
// If there are no fetch builders for this association, we only want to fetch the FK
if ( explicitFetchBuilder == null && fetchBuilderLegacy == null && partName != null ) {
currentEntry = new AbstractMap.SimpleEntry<>(
currentEntry.getKey() + "." + partName,
currentEntry.getValue().append( partName )
);
explicitFetchBuilder = fetchBuilderResolverStack
.getCurrent()
.apply( currentEntry.getKey() );
if ( explicitFetchBuilder == null ) {
fetchBuilderLegacy = legacyFetchResolver.resolve(
fromClauseAccess.findTableGroup( fetchParent.getNavigablePath() )
.getPrimaryTableReference()
.getIdentificationVariable(),
fetchableName
);
}
}
}
relativePathStack.push( currentEntry );
try {
final NavigablePath fetchPath = fetchParent.resolveNavigablePath( fetchable );
final FetchBuilder fetchBuilder;
if ( explicitFetchBuilder != null ) {
fetchBuilder = explicitFetchBuilder;
}
else {
if ( fetchBuilderLegacy == null ) {
fetchBuilder = Builders.implicitFetchBuilder( fetchPath, fetchable, this );
}
else {
fetchBuilder = fetchBuilderLegacy;
}
}
final Fetch fetch = fetchBuilder.buildFetch(
fetchParent,
fetchPath,
jdbcResultsMetadata,
(s, s2) -> {
throw new UnsupportedOperationException();
},
this
);
fetches.add( fetch );
}
finally {
relativePathStack.pop();
}
};
boolean previous = this.processingKeyFetches;
this.processingKeyFetches = true;
if ( fetchableContainer instanceof EntityValuedModelPart ) {
final EntityValuedModelPart entityValuedFetchable = (EntityValuedModelPart) fetchableContainer;
final EntityIdentifierMapping identifierMapping = entityValuedFetchable.getEntityMappingType().getIdentifierMapping();
final boolean idClass = identifierMapping instanceof NonAggregatedIdentifierMappingImpl;
final String identifierAttributeName = attributeName( identifierMapping );
if ( idClass ) {
final Map.Entry oldEntry = relativePathStack.getCurrent();
relativePathStack.push(
new AbstractMap.SimpleEntry<>(
oldEntry == null ? "" : oldEntry.getKey(),
new EntityIdentifierNavigablePath(
oldEntry == null ? fetchParent.getNavigablePath() : oldEntry.getValue(),
identifierAttributeName
)
)
);
}
else if ( identifierMapping instanceof CompositeIdentifierMapping ) {
final Map.Entry oldEntry = relativePathStack.getCurrent();
relativePathStack.push(
new AbstractMap.SimpleEntry<>(
oldEntry == null ?
identifierAttributeName :
oldEntry.getKey() + "." + identifierAttributeName,
new EntityIdentifierNavigablePath(
oldEntry == null ? fetchParent.getNavigablePath() : oldEntry.getValue(),
identifierAttributeName
)
)
);
}
try {
if ( identifierMapping instanceof FetchableContainer ) {
// essentially means the entity has a composite id - ask the embeddable to visit its fetchables
( (FetchableContainer) identifierMapping ).visitFetchables( fetchableConsumer, null );
}
else {
fetchableConsumer.accept( (Fetchable) identifierMapping );
}
}
finally {
this.processingKeyFetches = previous;
if ( idClass ) {
this.relativePathStack.pop();
}
}
}
fetchableContainer.visitKeyFetchables( fetchableConsumer, null );
fetchableContainer.visitFetchables( fetchableConsumer, null );
return fetches;
}
private String getRelativePath(String oldEntry, Fetchable fetchable, FetchableContainer fetchableContainer) {
if ( fetchable instanceof AttributeMapping || fetchable instanceof SingleAttributeIdentifierMapping || fetchable instanceof BasicValuedCollectionPart ) {
if ( !oldEntry.equals( "" ) ) {
return oldEntry + '.' + fetchable.getFetchableName();
}
return fetchable.getFetchableName();
}
return oldEntry;
}
@Override
public boolean isResolvingCircularFetch() {
return resolvingCircularFetch;
}
@Override
public void setResolvingCircularFetch(boolean resolvingCircularFetch) {
this.resolvingCircularFetch = resolvingCircularFetch;
}
@Override
public ForeignKeyDescriptor.Nature getCurrentlyResolvingForeignKeyPart() {
return currentlyResolvingForeignKeySide;
}
@Override
public void setCurrentlyResolvingForeignKeyPart(ForeignKeyDescriptor.Nature currentlyResolvingForeignKeySide) {
this.currentlyResolvingForeignKeySide = currentlyResolvingForeignKeySide;
}
}