Fix missing table reference issues by translating all SqmRoots and SqmJoins to proper path interpretations

This commit is contained in:
Christian Beikov 2021-09-22 08:41:51 +02:00
parent e8d337828b
commit 07f6d31d2b
5 changed files with 146 additions and 114 deletions

View File

@ -182,6 +182,7 @@ xjc {
task copyBundleResources (type: Copy) { task copyBundleResources (type: Copy) {
inputs.property( "db", db )
ext { ext {
bundlesTargetDir = file( "${buildDir}/bundles" ) bundlesTargetDir = file( "${buildDir}/bundles" )
bundleTokens = dbBundle[db] bundleTokens = dbBundle[db]

View File

@ -916,6 +916,10 @@ public class ToOneAttributeMapping
if ( !canUseParentTableGroup ) { if ( !canUseParentTableGroup ) {
return false; return false;
} }
// Special case for resolving the table group for entity valued paths
if ( np == navigablePath ) {
return true;
}
NavigablePath path = np.getParent(); NavigablePath path = np.getParent();
// Fast path // Fast path
if ( path != null && navigablePath.equals( path ) ) { if ( path != null && navigablePath.equals( path ) ) {

View File

@ -47,6 +47,7 @@ import org.hibernate.metamodel.MappingMetamodel;
import org.hibernate.metamodel.mapping.Association; import org.hibernate.metamodel.mapping.Association;
import org.hibernate.metamodel.mapping.AttributeMapping; import org.hibernate.metamodel.mapping.AttributeMapping;
import org.hibernate.metamodel.mapping.BasicValuedMapping; import org.hibernate.metamodel.mapping.BasicValuedMapping;
import org.hibernate.metamodel.mapping.BasicValuedModelPart;
import org.hibernate.metamodel.mapping.Bindable; import org.hibernate.metamodel.mapping.Bindable;
import org.hibernate.metamodel.mapping.CollectionPart; import org.hibernate.metamodel.mapping.CollectionPart;
import org.hibernate.metamodel.mapping.ConvertibleModelPart; import org.hibernate.metamodel.mapping.ConvertibleModelPart;
@ -67,6 +68,7 @@ import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.metamodel.mapping.ValueMapping; import org.hibernate.metamodel.mapping.ValueMapping;
import org.hibernate.metamodel.mapping.internal.EmbeddedCollectionPart; import org.hibernate.metamodel.mapping.internal.EmbeddedCollectionPart;
import org.hibernate.metamodel.mapping.internal.EntityCollectionPart; import org.hibernate.metamodel.mapping.internal.EntityCollectionPart;
import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping;
import org.hibernate.metamodel.mapping.ordering.OrderByFragment; import org.hibernate.metamodel.mapping.ordering.OrderByFragment;
import org.hibernate.metamodel.model.convert.spi.BasicValueConverter; import org.hibernate.metamodel.model.convert.spi.BasicValueConverter;
import org.hibernate.metamodel.model.domain.AllowableFunctionReturnType; import org.hibernate.metamodel.model.domain.AllowableFunctionReturnType;
@ -269,6 +271,7 @@ import org.hibernate.sql.ast.tree.from.LazyTableGroup;
import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableGroupJoin; import org.hibernate.sql.ast.tree.from.TableGroupJoin;
import org.hibernate.sql.ast.tree.from.TableGroupJoinProducer; import org.hibernate.sql.ast.tree.from.TableGroupJoinProducer;
import org.hibernate.sql.ast.tree.from.TableReference;
import org.hibernate.sql.ast.tree.from.VirtualTableGroup; import org.hibernate.sql.ast.tree.from.VirtualTableGroup;
import org.hibernate.sql.ast.tree.insert.InsertStatement; import org.hibernate.sql.ast.tree.insert.InsertStatement;
import org.hibernate.sql.ast.tree.insert.Values; import org.hibernate.sql.ast.tree.insert.Values;
@ -464,6 +467,20 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
lastPoppedProcessingState = processingStateStack.pop(); lastPoppedProcessingState = processingStateStack.pop();
} }
private QuerySpec currentQuerySpec() {
return currentQueryPart().getLastQuerySpec();
}
private QueryPart currentQueryPart() {
final SqlAstQueryPartProcessingState processingState = (SqlAstQueryPartProcessingState) getProcessingStateStack()
.getCurrent();
return processingState.getInflightQueryPart();
}
protected SqmAliasedNodeCollector currentSqlSelectionCollector() {
return (SqmAliasedNodeCollector) getCurrentProcessingState().getSqlExpressionResolver();
}
protected SqmStatement<?> getStatement() { protected SqmStatement<?> getStatement() {
return statement; return statement;
} }
@ -1732,47 +1749,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
return new SqlTuple( expressions, null ); return new SqlTuple( expressions, null );
} }
final Expression expression = (Expression) groupByClauseExpression.accept( this ); return (Expression) groupByClauseExpression.accept( this );
// When a join alias is put into the GROUP BY or ORDER BY clause, we have to transform this to interpretations
if ( expression instanceof TableGroup ) {
final TableGroup tableGroup = (TableGroup) expression;
if ( tableGroup.getModelPart() instanceof EmbeddableValuedModelPart ) {
final EmbeddableValuedModelPart mapping = (EmbeddableValuedModelPart) tableGroup.getModelPart();
return new EmbeddableValuedPathInterpretation<>(
mapping.toSqlExpression(
tableGroup,
getCurrentClauseStack().getCurrent(),
this,
this
),
tableGroup.getNavigablePath(),
mapping,
tableGroup
);
}
else if ( tableGroup.getModelPart() instanceof EntityValuedModelPart ) {
final EntityValuedModelPart mapping = (EntityValuedModelPart) tableGroup.getModelPart();
final boolean expandToAllColumns;
if ( currentClauseStack.getCurrent() == Clause.GROUP ) {
// When the table group is known to be fetched i.e. a fetch join
// but also when the from clause is part of the select clause
// we need to expand to all columns, as we also expand this to all columns in the select clause
expandToAllColumns = tableGroup.isFetched()
|| groupByClauseExpression instanceof SqmFrom<?, ?> && selectClauseContains( (SqmFrom<?, ?>) groupByClauseExpression );
}
else {
expandToAllColumns = false;
}
return EntityValuedPathInterpretation.from(
tableGroup.getNavigablePath(),
tableGroup,
mapping,
expandToAllColumns,
this
);
}
}
return expression;
} }
private int indexOfExpression(List<? extends SqmAliasedNode<?>> selections, SqmExpression<?> node) { private int indexOfExpression(List<? extends SqmAliasedNode<?>> selections, SqmExpression<?> node) {
@ -2365,72 +2342,168 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
// handled during `#visitFromClause` // handled during `#visitFromClause`
@Override @Override
public TableGroup visitRootPath(SqmRoot<?> sqmRoot) { public Expression visitRootPath(SqmRoot<?> sqmRoot) {
final TableGroup resolved = getFromClauseAccess().findTableGroup( sqmRoot.getNavigablePath() ); final TableGroup resolved = getFromClauseAccess().findTableGroup( sqmRoot.getNavigablePath() );
if ( resolved != null ) { if ( resolved != null ) {
log.tracef( "SqmRoot [%s] resolved to existing TableGroup [%s]", sqmRoot, resolved ); log.tracef( "SqmRoot [%s] resolved to existing TableGroup [%s]", sqmRoot, resolved );
return resolved; return visitTableGroup( resolved, sqmRoot );
} }
throw new InterpretationException( "SqmRoot not yet resolved to TableGroup" ); throw new InterpretationException( "SqmRoot not yet resolved to TableGroup" );
} }
@Override @Override
public TableGroup visitQualifiedAttributeJoin(SqmAttributeJoin<?, ?> sqmJoin) { public Expression visitQualifiedAttributeJoin(SqmAttributeJoin<?, ?> sqmJoin) {
// todo (6.0) : have this resolve to TableGroup instead? // todo (6.0) : have this resolve to TableGroup instead?
// - trying to remove tracking of TableGroupJoin in the x-refs // - trying to remove tracking of TableGroupJoin in the x-refs
final TableGroup existing = getFromClauseAccess().findTableGroup( sqmJoin.getNavigablePath() ); final TableGroup existing = getFromClauseAccess().findTableGroup( sqmJoin.getNavigablePath() );
if ( existing != null ) { if ( existing != null ) {
log.tracef( "SqmAttributeJoin [%s] resolved to existing TableGroup [%s]", sqmJoin, existing ); log.tracef( "SqmAttributeJoin [%s] resolved to existing TableGroup [%s]", sqmJoin, existing );
return existing; return visitTableGroup( existing, sqmJoin );
} }
throw new InterpretationException( "SqmAttributeJoin not yet resolved to TableGroup" ); throw new InterpretationException( "SqmAttributeJoin not yet resolved to TableGroup" );
} }
private QuerySpec currentQuerySpec() {
return currentQueryPart().getLastQuerySpec();
}
private QueryPart currentQueryPart() {
final SqlAstQueryPartProcessingState processingState = (SqlAstQueryPartProcessingState) getProcessingStateStack()
.getCurrent();
return processingState.getInflightQueryPart();
}
protected SqmAliasedNodeCollector currentSqlSelectionCollector() {
return (SqmAliasedNodeCollector) getCurrentProcessingState().getSqlExpressionResolver();
}
@Override @Override
public TableGroup visitCrossJoin(SqmCrossJoin<?> sqmJoin) { public Expression visitCrossJoin(SqmCrossJoin<?> sqmJoin) {
// todo (6.0) : have this resolve to TableGroup instead? // todo (6.0) : have this resolve to TableGroup instead?
// - trying to remove tracking of TableGroupJoin in the x-refs // - trying to remove tracking of TableGroupJoin in the x-refs
final TableGroup existing = getFromClauseAccess().findTableGroup( sqmJoin.getNavigablePath() ); final TableGroup existing = getFromClauseAccess().findTableGroup( sqmJoin.getNavigablePath() );
if ( existing != null ) { if ( existing != null ) {
log.tracef( "SqmCrossJoin [%s] resolved to existing TableGroup [%s]", sqmJoin, existing ); log.tracef( "SqmCrossJoin [%s] resolved to existing TableGroup [%s]", sqmJoin, existing );
return existing; return visitTableGroup( existing, sqmJoin );
} }
throw new InterpretationException( "SqmCrossJoin not yet resolved to TableGroup" ); throw new InterpretationException( "SqmCrossJoin not yet resolved to TableGroup" );
} }
@Override @Override
public TableGroup visitQualifiedEntityJoin(SqmEntityJoin sqmJoin) { public Expression visitQualifiedEntityJoin(SqmEntityJoin sqmJoin) {
// todo (6.0) : have this resolve to TableGroup instead? // todo (6.0) : have this resolve to TableGroup instead?
// - trying to remove tracking of TableGroupJoin in the x-refs // - trying to remove tracking of TableGroupJoin in the x-refs
final TableGroup existing = getFromClauseAccess().findTableGroup( sqmJoin.getNavigablePath() ); final TableGroup existing = getFromClauseAccess().findTableGroup( sqmJoin.getNavigablePath() );
if ( existing != null ) { if ( existing != null ) {
log.tracef( "SqmEntityJoin [%s] resolved to existing TableGroup [%s]", sqmJoin, existing ); log.tracef( "SqmEntityJoin [%s] resolved to existing TableGroup [%s]", sqmJoin, existing );
return existing; return visitTableGroup( existing, sqmJoin );
} }
throw new InterpretationException( "SqmEntityJoin not yet resolved to TableGroup" ); throw new InterpretationException( "SqmEntityJoin not yet resolved to TableGroup" );
} }
private Expression visitTableGroup(TableGroup tableGroup, SqmFrom<?, ?> path) {
final ModelPartContainer modelPart = tableGroup.getModelPart();
final ModelPart keyPart;
final ModelPart resultPart;
if ( modelPart instanceof ToOneAttributeMapping ) {
final ToOneAttributeMapping toOneAttributeMapping = (ToOneAttributeMapping) modelPart;
keyPart = toOneAttributeMapping.findSubPart( toOneAttributeMapping.getTargetKeyPropertyName() );
resultPart = toOneAttributeMapping;
}
else if ( modelPart instanceof PluralAttributeMapping ) {
final PluralAttributeMapping pluralAttributeMapping = (PluralAttributeMapping) modelPart;
final CollectionPart elementDescriptor = pluralAttributeMapping.getElementDescriptor();
if ( elementDescriptor instanceof EntityCollectionPart ) {
keyPart = ( (EntityCollectionPart) elementDescriptor ).getKeyTargetMatchPart();
}
else {
keyPart = elementDescriptor;
}
resultPart = elementDescriptor;
}
else if ( modelPart instanceof EntityCollectionPart ) {
keyPart = ( (EntityCollectionPart) modelPart ).getKeyTargetMatchPart();
resultPart = modelPart;
}
else if ( modelPart instanceof EntityMappingType ) {
keyPart = ( (EntityMappingType) modelPart ).getIdentifierMapping();
resultPart = modelPart;
}
else {
keyPart = modelPart;
resultPart = modelPart;
}
final Expression result;
if ( resultPart instanceof EntityValuedModelPart ) {
final EntityValuedModelPart mapping = (EntityValuedModelPart) tableGroup.getModelPart();
final boolean expandToAllColumns;
if ( currentClauseStack.getCurrent() == Clause.GROUP ) {
// When the table group is known to be fetched i.e. a fetch join
// but also when the from clause is part of the select clause
// we need to expand to all columns, as we also expand this to all columns in the select clause
expandToAllColumns = tableGroup.isFetched() || selectClauseContains( path );
}
else {
expandToAllColumns = false;
}
result = EntityValuedPathInterpretation.from(
tableGroup.getNavigablePath(),
tableGroup,
mapping,
expandToAllColumns,
this
);
}
else if ( resultPart instanceof EmbeddableValuedModelPart ) {
final EmbeddableValuedModelPart mapping = (EmbeddableValuedModelPart) keyPart;
result = new EmbeddableValuedPathInterpretation<>(
mapping.toSqlExpression(
tableGroup,
currentClauseStack.getCurrent(),
this,
getSqlAstCreationState()
),
tableGroup.getNavigablePath(),
(EmbeddableValuedModelPart) resultPart,
tableGroup
);
}
else {
assert resultPart instanceof BasicValuedModelPart;
final BasicValuedModelPart mapping = (BasicValuedModelPart) keyPart;
final TableReference tableReference = tableGroup.resolveTableReference(
tableGroup.getNavigablePath().append( keyPart.getPartName() ),
mapping.getContainingTableExpression()
);
final Expression expression = getSqlExpressionResolver().resolveSqlExpression(
SqlExpressionResolver.createColumnReferenceKey(
tableReference,
mapping.getSelectionExpression()
),
sacs -> new ColumnReference(
tableReference.getIdentificationVariable(),
mapping,
getCreationContext().getSessionFactory()
)
);
final ColumnReference columnReference;
if ( expression instanceof ColumnReference ) {
columnReference = (ColumnReference) expression;
}
else if ( expression instanceof SqlSelectionExpression ) {
final Expression selectedExpression = ( (SqlSelectionExpression) expression ).getSelection().getExpression();
assert selectedExpression instanceof ColumnReference;
columnReference = (ColumnReference) selectedExpression;
}
else {
throw new UnsupportedOperationException( "Unsupported basic-valued path expression : " + expression );
}
result = new BasicValuedPathInterpretation<>(
columnReference,
tableGroup.getNavigablePath(),
(BasicValuedModelPart) resultPart,
tableGroup
);
}
return withTreatRestriction( result, path );
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// SqmPath // SqmPath

View File

@ -43,7 +43,8 @@ public class BasicValuedPathInterpretation<T> extends AbstractSqmPathInterpretat
SqlAstCreationState sqlAstCreationState, SqlAstCreationState sqlAstCreationState,
SemanticQueryWalker sqmWalker, SemanticQueryWalker sqmWalker,
boolean jpaQueryComplianceEnabled) { boolean jpaQueryComplianceEnabled) {
TableGroup tableGroup = sqlAstCreationState.getFromClauseAccess().getTableGroup( sqmPath.getLhs().getNavigablePath() ); final TableGroup tableGroup = sqlAstCreationState.getFromClauseAccess()
.getTableGroup( sqmPath.getLhs().getNavigablePath() );
EntityMappingType treatTarget = null; EntityMappingType treatTarget = null;
if ( jpaQueryComplianceEnabled ) { if ( jpaQueryComplianceEnabled ) {
@ -114,7 +115,7 @@ public class BasicValuedPathInterpretation<T> extends AbstractSqmPathInterpretat
private final ColumnReference columnReference; private final ColumnReference columnReference;
private BasicValuedPathInterpretation( public BasicValuedPathInterpretation(
ColumnReference columnReference, ColumnReference columnReference,
NavigablePath navigablePath, NavigablePath navigablePath,
BasicValuedModelPart mapping, BasicValuedModelPart mapping,

View File

@ -37,7 +37,6 @@ import org.hibernate.sql.ast.tree.cte.CteMaterialization;
import org.hibernate.sql.ast.tree.cte.CteSearchClauseKind; import org.hibernate.sql.ast.tree.cte.CteSearchClauseKind;
import org.hibernate.query.FetchClauseType; import org.hibernate.query.FetchClauseType;
import org.hibernate.LockOptions; import org.hibernate.LockOptions;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.dialect.Dialect; import org.hibernate.dialect.Dialect;
import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.spi.AbstractDelegatingWrapperOptions; import org.hibernate.engine.spi.AbstractDelegatingWrapperOptions;
@ -49,16 +48,10 @@ import org.hibernate.internal.util.StringHelper;
import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.internal.util.collections.Stack; import org.hibernate.internal.util.collections.Stack;
import org.hibernate.internal.util.collections.StandardStack; import org.hibernate.internal.util.collections.StandardStack;
import org.hibernate.metamodel.mapping.CollectionPart;
import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.ModelPartContainer; import org.hibernate.metamodel.mapping.ModelPartContainer;
import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.metamodel.mapping.SqlExpressable; import org.hibernate.metamodel.mapping.SqlExpressable;
import org.hibernate.metamodel.mapping.internal.BasicValuedCollectionPart;
import org.hibernate.metamodel.mapping.internal.EntityCollectionPart;
import org.hibernate.metamodel.mapping.internal.SimpleForeignKeyDescriptor;
import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping;
import org.hibernate.persister.entity.AbstractEntityPersister; import org.hibernate.persister.entity.AbstractEntityPersister;
import org.hibernate.persister.entity.Loadable; import org.hibernate.persister.entity.Loadable;
import org.hibernate.query.ComparisonOperator; import org.hibernate.query.ComparisonOperator;
@ -3500,53 +3493,13 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
@Override @Override
public void visitTableGroup(TableGroup tableGroup) { public void visitTableGroup(TableGroup tableGroup) {
// TableGroup and TableGroup handling should be performed as part of `#visitFromClause`... // TableGroup and TableGroup handling should be performed as part of `#visitFromClause`...
throw new UnsupportedOperationException( "This should never be invoked as org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter.visitTableGroup should handle this!" );
// todo (6.0) : what is the correct behavior here?
appendSql( tableGroup.getPrimaryTableReference().getIdentificationVariable() );
appendSql( '.' );
//TODO: pretty sure the typecast to Loadable is quite wrong here
ModelPartContainer modelPart = tableGroup.getModelPart();
if ( modelPart instanceof Loadable ) {
appendSql( ( (Loadable) tableGroup.getModelPart() ).getIdentifierColumnNames()[0] );
}
else if ( modelPart instanceof PluralAttributeMapping ) {
final CollectionPart elementDescriptor = ( (PluralAttributeMapping) modelPart ).getElementDescriptor();
if ( elementDescriptor instanceof BasicValuedCollectionPart ) {
String mappedColumnExpression = ( (BasicValuedCollectionPart) elementDescriptor ).getSelectionExpression();
appendSql( mappedColumnExpression );
}
else if ( elementDescriptor instanceof EntityCollectionPart ) {
final ForeignKeyDescriptor foreignKeyDescriptor = ( (EntityCollectionPart) elementDescriptor ).getForeignKeyDescriptor();
if ( foreignKeyDescriptor instanceof SimpleForeignKeyDescriptor ) {
foreignKeyDescriptor.visitTargetSelectables(
(selectionIndex, selectionMapping) -> appendSql( selectionMapping.getSelectionExpression() )
);
}
}
}
else if ( modelPart instanceof ToOneAttributeMapping ) {
final ForeignKeyDescriptor foreignKeyDescriptor = ( (ToOneAttributeMapping) modelPart ).getForeignKeyDescriptor();
if ( foreignKeyDescriptor instanceof SimpleForeignKeyDescriptor ) {
foreignKeyDescriptor.visitTargetSelectables(
(selectionIndex, selectionMapping) -> appendSql( selectionMapping.getSelectionExpression() )
);
}
}
else {
throw new NotYetImplementedFor6Exception( getClass() );
}
} }
@Override @Override
public void visitTableGroupJoin(TableGroupJoin tableGroupJoin) { public void visitTableGroupJoin(TableGroupJoin tableGroupJoin) {
// TableGroup and TableGroupJoin handling should be performed as part of `#visitFromClause`... // TableGroup and TableGroupJoin handling should be performed as part of `#visitFromClause`...
throw new UnsupportedOperationException( "This should never be invoked as org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter.visitTableGroup should handle this!" );
// todo (6.0) : what is the correct behavior here?
appendSql( tableGroupJoin.getJoinedGroup().getPrimaryTableReference().getIdentificationVariable() );
appendSql( '.' );
//TODO: pretty sure the typecast to Loadable is quite wrong here
appendSql( ( (Loadable) tableGroupJoin.getJoinedGroup().getModelPart() ).getIdentifierColumnNames()[0] );
} }
@Override @Override