Various fixes

* Fix parsing soft-keywords as naked identifiers
* Create proper correlations during parsing
* Fix some type inference issues with entity valued paths
This commit is contained in:
Christian Beikov 2022-02-20 21:06:25 +01:00
parent 075cc8d108
commit 335ed19821
34 changed files with 848 additions and 426 deletions

View File

@ -191,15 +191,14 @@ entityName
*/ */
variable variable
: AS identifier : AS identifier
| IDENTIFIER | nakedIdentifier
| QUOTED_IDENTIFIER
; ;
/** /**
* A 'cross join' to a second root entity (a cartesian product) * A 'cross join' to a second root entity (a cartesian product)
*/ */
crossJoin crossJoin
: CROSS JOIN rootEntity variable? : CROSS JOIN entityName variable?
; ;
/** /**
@ -1461,7 +1460,8 @@ rollup
* This parser rule helps with that. Here we expect that the caller already understands their * This parser rule helps with that. Here we expect that the caller already understands their
* context enough to know that keywords-as-identifiers are allowed. * context enough to know that keywords-as-identifiers are allowed.
*/ */
identifier // All except the possible optional following keywords LEFT, RIGHT, INNER, FULL, OUTER
nakedIdentifier
: IDENTIFIER : IDENTIFIER
| QUOTED_IDENTIFIER | QUOTED_IDENTIFIER
| (ALL | (ALL
@ -1510,7 +1510,7 @@ identifier
| FOR | FOR
| FORMAT | FORMAT
| FROM | FROM
| FULL // | FULL
| FUNCTION | FUNCTION
| GROUP | GROUP
| GROUPS | GROUPS
@ -1522,7 +1522,7 @@ identifier
| IN | IN
| INDEX | INDEX
| INDICES | INDICES
| INNER // | INNER
| INSERT | INSERT
| INSTANT | INSTANT
| INTERSECT | INTERSECT
@ -1532,7 +1532,7 @@ identifier
| KEY | KEY
| LAST | LAST
| LEADING | LEADING
| LEFT // | LEFT
| LIKE | LIKE
| LIMIT | LIMIT
| LIST | LIST
@ -1569,7 +1569,7 @@ identifier
| OR | OR
| ORDER | ORDER
| OTHERS | OTHERS
| OUTER // | OUTER
| OVER | OVER
| OVERFLOW | OVERFLOW
| OVERLAY | OVERLAY
@ -1582,7 +1582,7 @@ identifier
| QUARTER | QUARTER
| RANGE | RANGE
| RESPECT | RESPECT
| RIGHT // | RIGHT
| ROLLUP | ROLLUP
| ROW | ROW
| ROWS | ROWS
@ -1621,3 +1621,13 @@ identifier
logUseOfReservedWordAsIdentifier( getCurrentToken() ); logUseOfReservedWordAsIdentifier( getCurrentToken() );
} }
; ;
identifier
: nakedIdentifier
| (FULL
| INNER
| LEFT
| OUTER
| RIGHT) {
logUseOfReservedWordAsIdentifier( getCurrentToken() );
}
;

View File

@ -548,7 +548,9 @@ public class EntityCollectionPart
@Override @Override
public ForeignKeyDescriptor.Nature getSideNature() { public ForeignKeyDescriptor.Nature getSideNature() {
return ForeignKeyDescriptor.Nature.TARGET; return collectionDescriptor.isOneToMany()
? ForeignKeyDescriptor.Nature.TARGET
: ForeignKeyDescriptor.Nature.KEY;
} }
@Override @Override

View File

@ -130,7 +130,7 @@ public class BasicDotIdentifierConsumer implements DotIdentifierConsumer {
.getCurrent() .getCurrent()
.getPathRegistry(); .getPathRegistry();
final SqmFrom<?,?> pathRootByAlias = sqmPathRegistry.findFromByAlias( identifier ); final SqmFrom<?,?> pathRootByAlias = sqmPathRegistry.findFromByAlias( identifier, true );
if ( pathRootByAlias != null ) { if ( pathRootByAlias != null ) {
// identifier is an alias (identification variable) // identifier is an alias (identification variable)
validateAsRoot( pathRootByAlias ); validateAsRoot( pathRootByAlias );

View File

@ -110,7 +110,7 @@ public class QualifiedJoinPathConsumer implements DotIdentifierConsumer {
final SqmCreationProcessingState processingState = creationState.getCurrentProcessingState(); final SqmCreationProcessingState processingState = creationState.getCurrentProcessingState();
final SqmPathRegistry pathRegistry = processingState.getPathRegistry(); final SqmPathRegistry pathRegistry = processingState.getPathRegistry();
final SqmFrom<?, Object> pathRootByAlias = pathRegistry.findFromByAlias( identifier ); final SqmFrom<?, Object> pathRootByAlias = pathRegistry.findFromByAlias( identifier, true );
if ( pathRootByAlias != null ) { if ( pathRootByAlias != null ) {
// identifier is an alias (identification variable) // identifier is an alias (identification variable)

View File

@ -13,6 +13,7 @@ import org.hibernate.query.hql.spi.SemanticPathPart;
import org.hibernate.query.hql.spi.SqmCreationProcessingState; import org.hibernate.query.hql.spi.SqmCreationProcessingState;
import org.hibernate.query.hql.spi.SqmCreationState; import org.hibernate.query.hql.spi.SqmCreationState;
import org.hibernate.query.sqm.tree.SqmQuery; import org.hibernate.query.sqm.tree.SqmQuery;
import org.hibernate.query.sqm.tree.domain.SqmCorrelation;
import org.hibernate.query.sqm.tree.from.SqmFrom; import org.hibernate.query.sqm.tree.from.SqmFrom;
import org.hibernate.query.sqm.tree.from.SqmFromClause; import org.hibernate.query.sqm.tree.from.SqmFromClause;
import org.hibernate.query.sqm.tree.from.SqmQualifiedJoin; import org.hibernate.query.sqm.tree.from.SqmQualifiedJoin;
@ -45,7 +46,7 @@ public class QualifiedJoinPredicatePathConsumer extends BasicDotIdentifierConsum
final SqmRoot<?> root = pathRoot.findRoot(); final SqmRoot<?> root = pathRoot.findRoot();
final SqmRoot<?> joinRoot = sqmJoin.findRoot(); final SqmRoot<?> joinRoot = sqmJoin.findRoot();
if ( root != joinRoot ) { if ( root != joinRoot ) {
// The root of a path within a query doesn't have the same root as the current join we are processing. // The root of a path within a join condition doesn't have the same root as the current join we are processing.
// The aim of this check is to prevent uses of different "spaces" i.e. `from A a, B b join b.id = a.id` would be illegal // The aim of this check is to prevent uses of different "spaces" i.e. `from A a, B b join b.id = a.id` would be illegal
SqmCreationProcessingState processingState = getCreationState().getCurrentProcessingState(); SqmCreationProcessingState processingState = getCreationState().getCurrentProcessingState();
// First, we need to find out if the current join is part of current processing query // First, we need to find out if the current join is part of current processing query
@ -56,7 +57,14 @@ public class QualifiedJoinPredicatePathConsumer extends BasicDotIdentifierConsum
// If the current processing query contains the root of the current join, // If the current processing query contains the root of the current join,
// then the root of the processing path must be a root of one of the parent queries // then the root of the processing path must be a root of one of the parent queries
if ( fromClause != null && fromClause.getRoots().contains( joinRoot ) ) { if ( fromClause != null && fromClause.getRoots().contains( joinRoot ) ) {
validateAsRootOnParentQueryClosure( pathRoot, root, processingState.getParentProcessingState() ); // It is allowed to use correlations from the same query
if ( !( root instanceof SqmCorrelation<?, ?> ) || !fromClause.getRoots().contains( root ) ) {
validateAsRootOnParentQueryClosure(
pathRoot,
root,
processingState.getParentProcessingState()
);
}
return; return;
} }
} }

View File

@ -86,7 +86,6 @@ import org.hibernate.query.sqm.UnaryArithmeticOperator;
import org.hibernate.query.sqm.UnknownEntityException; import org.hibernate.query.sqm.UnknownEntityException;
import org.hibernate.query.sqm.function.FunctionKind; import org.hibernate.query.sqm.function.FunctionKind;
import org.hibernate.query.sqm.function.NamedSqmFunctionDescriptor; import org.hibernate.query.sqm.function.NamedSqmFunctionDescriptor;
import org.hibernate.query.sqm.function.SelfRenderingSqmFunction;
import org.hibernate.query.sqm.function.SqmFunctionDescriptor; import org.hibernate.query.sqm.function.SqmFunctionDescriptor;
import org.hibernate.query.sqm.internal.ParameterCollector; import org.hibernate.query.sqm.internal.ParameterCollector;
import org.hibernate.query.sqm.internal.SqmCreationProcessingStateImpl; import org.hibernate.query.sqm.internal.SqmCreationProcessingStateImpl;
@ -1047,7 +1046,10 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
@Override @Override
public SqmPath<?> visitJpaSelectObjectSyntax(HqlParser.JpaSelectObjectSyntaxContext ctx) { public SqmPath<?> visitJpaSelectObjectSyntax(HqlParser.JpaSelectObjectSyntaxContext ctx) {
final String alias = ctx.getChild( 2 ).getText(); final String alias = ctx.getChild( 2 ).getText();
final SqmFrom<?, ?> sqmFromByAlias = processingStateStack.getCurrent().getPathRegistry().findFromByAlias( alias ); final SqmFrom<?, ?> sqmFromByAlias = processingStateStack.getCurrent().getPathRegistry().findFromByAlias(
alias,
true
);
if ( sqmFromByAlias == null ) { if ( sqmFromByAlias == null ) {
throw new SemanticException( "Unable to resolve alias [" + alias + "] in selection [" + ctx.getText() + "]" ); throw new SemanticException( "Unable to resolve alias [" + alias + "] in selection [" + ctx.getText() + "]" );
} }
@ -1102,7 +1104,10 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
return new SqmAliasedNodeRef( correspondingPosition, integerDomainType, creationContext.getNodeBuilder() ); return new SqmAliasedNodeRef( correspondingPosition, integerDomainType, creationContext.getNodeBuilder() );
} }
final SqmFrom<?, ?> sqmFrom = getCurrentProcessingState().getPathRegistry().findFromByAlias( identifierText ); final SqmFrom<?, ?> sqmFrom = getCurrentProcessingState().getPathRegistry().findFromByAlias(
identifierText,
true
);
if ( sqmFrom != null ) { if ( sqmFrom != null ) {
if ( definedCollate ) { if ( definedCollate ) {
// This is syntactically disallowed // This is syntactically disallowed
@ -1360,6 +1365,15 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
@Override @Override
public String visitIdentifier(HqlParser.IdentifierContext ctx) { public String visitIdentifier(HqlParser.IdentifierContext ctx) {
final ParseTree child = ctx.getChild( 0 );
if ( child instanceof TerminalNode ) {
return child.getText();
}
return visitNakedIdentifier( (HqlParser.NakedIdentifierContext) child );
}
@Override
public String visitNakedIdentifier(HqlParser.NakedIdentifierContext ctx) {
final TerminalNode node = (TerminalNode) ctx.getChild( 0 ); final TerminalNode node = (TerminalNode) ctx.getChild( 0 );
if ( node.getSymbol().getType() == HqlParser.QUOTED_IDENTIFIER ) { if ( node.getSymbol().getType() == HqlParser.QUOTED_IDENTIFIER ) {
return QuotingHelper.unquoteIdentifier( node.getText() ); return QuotingHelper.unquoteIdentifier( node.getText() );
@ -1413,7 +1427,11 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
@Override @Override
public SqmRoot<?> visitEntityWithJoins(HqlParser.EntityWithJoinsContext parserSpace) { public SqmRoot<?> visitEntityWithJoins(HqlParser.EntityWithJoinsContext parserSpace) {
final SqmRoot<?> sqmRoot = visitRootEntity( (HqlParser.RootEntityContext) parserSpace.getChild( 0 ) ); final SqmRoot<?> sqmRoot = visitRootEntity( (HqlParser.RootEntityContext) parserSpace.getChild( 0 ) );
currentQuerySpec().getFromClause().addRoot( sqmRoot ); final SqmFromClause fromClause = currentQuerySpec().getFromClause();
// Correlations are implicitly added to the from clause
if ( !( sqmRoot instanceof SqmCorrelation<?, ?> ) ) {
fromClause.addRoot( sqmRoot );
}
final int size = parserSpace.getChildCount(); final int size = parserSpace.getChildCount();
for ( int i = 1; i < size; i++ ) { for ( int i = 1; i < size; i++ ) {
final ParseTree parseTree = parserSpace.getChild( i ); final ParseTree parseTree = parserSpace.getChild( i );
@ -1461,12 +1479,9 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
// Handle the use of a correlation path in subqueries // Handle the use of a correlation path in subqueries
if ( processingStateStack.depth() > 1 && size > 2 ) { if ( processingStateStack.depth() > 1 && size > 2 ) {
final String parentAlias = entityNameParseTreeChildren.get( 0 ).getText(); final String parentAlias = entityNameParseTreeChildren.get( 0 ).getText();
final AbstractSqmFrom<?, ?> correlationBasis = processingState.getParentProcessingState() final AbstractSqmFrom<?, ?> correlation = processingState.getPathRegistry()
.getPathRegistry() .findFromByAlias( parentAlias, true );
.findFromByAlias( parentAlias ); if ( correlation instanceof SqmCorrelation<?, ?> ) {
if ( correlationBasis != null ) {
final SqmCorrelation<?, ?> correlation = correlationBasis.createCorrelation();
pathRegistry.register( correlation );
final DotIdentifierConsumer dotIdentifierConsumer = new QualifiedJoinPathConsumer( final DotIdentifierConsumer dotIdentifierConsumer = new QualifiedJoinPathConsumer(
correlation, correlation,
SqmJoinType.INNER, SqmJoinType.INNER,
@ -1487,7 +1502,7 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
false, false,
true true
); );
return correlation.getCorrelatedRoot(); return ( (SqmCorrelation<?, ?>) correlation ).getCorrelatedRoot();
} }
throw new SemanticException( "Could not resolve entity or correlation path '" + name + "'" ); throw new SemanticException( "Could not resolve entity or correlation path '" + name + "'" );
} }
@ -1545,11 +1560,24 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
return visitIdentifier( identifierContext ); return visitIdentifier( identifierContext );
} }
else { else {
final TerminalNode node = (TerminalNode) lastChild; final HqlParser.NakedIdentifierContext identifierContext = (HqlParser.NakedIdentifierContext) lastChild;
if ( node.getSymbol().getType() == HqlParser.QUOTED_IDENTIFIER ) { // in this branch, the alias could be a reserved word ("keyword as identifier")
return QuotingHelper.unquoteIdentifier( node.getText() ); // which JPA disallows...
if ( getCreationOptions().useStrictJpaCompliance() ) {
final Token identificationVariableToken = identifierContext.getStart();
if ( identificationVariableToken.getType() != IDENTIFIER ) {
throw new StrictJpaComplianceViolation(
String.format(
Locale.ROOT,
"Strict JPQL compliance was violated : %s [%s]",
StrictJpaComplianceViolation.Type.RESERVED_WORD_USED_AS_ALIAS.description(),
identificationVariableToken.getText()
),
StrictJpaComplianceViolation.Type.RESERVED_WORD_USED_AS_ALIAS
);
}
} }
return node.getText(); return visitNakedIdentifier( identifierContext );
} }
} }
@ -1571,8 +1599,7 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
} }
private <T> void consumeCrossJoin(HqlParser.CrossJoinContext parserJoin, SqmRoot<T> sqmRoot) { private <T> void consumeCrossJoin(HqlParser.CrossJoinContext parserJoin, SqmRoot<T> sqmRoot) {
final HqlParser.RootEntityContext pathRootContext = (HqlParser.RootEntityContext) parserJoin.getChild( 2 ); final HqlParser.EntityNameContext entityNameContext = (HqlParser.EntityNameContext) parserJoin.getChild( 2 );
final HqlParser.EntityNameContext entityNameContext = (HqlParser.EntityNameContext) pathRootContext.getChild( 0 );
final String name = getEntityName( entityNameContext ); final String name = getEntityName( entityNameContext );
SqmTreeCreationLogger.LOGGER.debugf( "Handling root path - %s", name ); SqmTreeCreationLogger.LOGGER.debugf( "Handling root path - %s", name );
@ -1584,8 +1611,8 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
throw new SemanticException( "Unmapped polymorphic reference cannot be used as a CROSS JOIN target" ); throw new SemanticException( "Unmapped polymorphic reference cannot be used as a CROSS JOIN target" );
} }
final HqlParser.VariableContext identificationVariableDefContext; final HqlParser.VariableContext identificationVariableDefContext;
if ( pathRootContext.getChildCount() > 1 ) { if ( parserJoin.getChildCount() > 3 ) {
identificationVariableDefContext = (HqlParser.VariableContext) pathRootContext.getChild( 1 ); identificationVariableDefContext = (HqlParser.VariableContext) parserJoin.getChild( 3 );
} }
else { else {
identificationVariableDefContext = null; identificationVariableDefContext = null;
@ -3628,6 +3655,16 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
else { else {
//for the shorter legacy Hibernate syntax "field(arg)" //for the shorter legacy Hibernate syntax "field(arg)"
extractFieldExpression = (SqmExtractUnit<?>) ctx.getChild( 0 ).accept(this); extractFieldExpression = (SqmExtractUnit<?>) ctx.getChild( 0 ).accept(this);
// //Prefer an existing native version if available
// final SqmFunctionDescriptor functionDescriptor = getFunctionDescriptor( extractFieldExpression.getUnit().name() );
// if ( functionDescriptor != null ) {
// return functionDescriptor.generateSqmExpression(
// expressionToExtract,
// null,
// creationContext.getQueryEngine(),
// creationContext.getJpaMetamodel().getTypeConfiguration()
// );
// }
} }
return getFunctionDescriptor("extract").generateSqmExpression( return getFunctionDescriptor("extract").generateSqmExpression(

View File

@ -14,17 +14,23 @@ import java.util.Map;
import java.util.function.Function; import java.util.function.Function;
import org.hibernate.jpa.spi.JpaCompliance; import org.hibernate.jpa.spi.JpaCompliance;
import org.hibernate.query.spi.NavigablePath;
import org.hibernate.query.hql.HqlLogging; import org.hibernate.query.hql.HqlLogging;
import org.hibernate.query.hql.spi.SqmCreationProcessingState; import org.hibernate.query.hql.spi.SqmCreationProcessingState;
import org.hibernate.query.hql.spi.SqmPathRegistry; import org.hibernate.query.hql.spi.SqmPathRegistry;
import org.hibernate.query.spi.NavigablePath;
import org.hibernate.query.sqm.AliasCollisionException; import org.hibernate.query.sqm.AliasCollisionException;
import org.hibernate.query.sqm.ParsingException; import org.hibernate.query.sqm.ParsingException;
import org.hibernate.query.sqm.SqmPathSource; import org.hibernate.query.sqm.SqmPathSource;
import org.hibernate.query.sqm.SqmTreeCreationLogger; import org.hibernate.query.sqm.SqmTreeCreationLogger;
import org.hibernate.query.sqm.tree.domain.SqmPath; import org.hibernate.query.sqm.tree.domain.SqmPath;
import org.hibernate.query.sqm.tree.from.SqmCrossJoin;
import org.hibernate.query.sqm.tree.from.SqmEntityJoin;
import org.hibernate.query.sqm.tree.from.SqmFrom; import org.hibernate.query.sqm.tree.from.SqmFrom;
import org.hibernate.query.sqm.tree.select.SqmAliasedNode; import org.hibernate.query.sqm.tree.select.SqmAliasedNode;
import org.hibernate.query.sqm.tree.select.SqmSubQuery;
import jakarta.persistence.criteria.Join;
import jakarta.persistence.criteria.Root;
/** /**
* Container for indexing needed while building an SQM tree. * Container for indexing needed while building an SQM tree.
@ -117,27 +123,12 @@ public class SqmPathRegistryImpl implements SqmPathRegistry {
@Override @Override
public <X extends SqmFrom<?, ?>> X findFromByPath(NavigablePath navigablePath) { public <X extends SqmFrom<?, ?>> X findFromByPath(NavigablePath navigablePath) {
final SqmFrom<?, ?> found = sqmFromByPath.get( navigablePath ); //noinspection unchecked
if ( found != null ) { return (X) sqmFromByPath.get( navigablePath );
//noinspection unchecked
return (X) found;
}
if ( associatedProcessingState.getParentProcessingState() != null ) {
final X containingQueryFrom = associatedProcessingState.getParentProcessingState()
.getPathRegistry()
.findFromByPath( navigablePath );
if ( containingQueryFrom != null ) {
// todo (6.0) create a correlation?
return containingQueryFrom;
}
}
return null;
} }
@Override @Override
public <X extends SqmFrom<?, ?>> X findFromByAlias(String alias) { public <X extends SqmFrom<?, ?>> X findFromByAlias(String alias, boolean searchParent) {
final String localAlias = jpaCompliance.isJpaQueryComplianceEnabled() final String localAlias = jpaCompliance.isJpaQueryComplianceEnabled()
? alias.toLowerCase( Locale.getDefault() ) ? alias.toLowerCase( Locale.getDefault() )
: alias; : alias;
@ -149,8 +140,39 @@ public class SqmPathRegistryImpl implements SqmPathRegistry {
return (X) registered; return (X) registered;
} }
if ( associatedProcessingState.getParentProcessingState() != null ) { SqmCreationProcessingState parentProcessingState = associatedProcessingState.getParentProcessingState();
return associatedProcessingState.getParentProcessingState().getPathRegistry().findFromByAlias( alias ); if ( searchParent && parentProcessingState != null ) {
X parentRegistered;
do {
parentRegistered = parentProcessingState.getPathRegistry().findFromByAlias(
alias,
false
);
parentProcessingState = parentProcessingState.getParentProcessingState();
} while (parentProcessingState != null && parentRegistered == null);
if ( parentRegistered != null ) {
// If a parent query contains the alias, we need to create a correlation on the subquery
final SqmSubQuery<?> selectQuery = ( SqmSubQuery<?> ) associatedProcessingState.getProcessingQuery();
SqmFrom<?, ?> correlated;
if ( parentRegistered instanceof Root<?> ) {
correlated = selectQuery.correlate( (Root<?>) parentRegistered );
}
else if ( parentRegistered instanceof Join<?, ?> ) {
correlated = selectQuery.correlate( (Join<?, ?>) parentRegistered );
}
else if ( parentRegistered instanceof SqmCrossJoin<?> ) {
correlated = selectQuery.correlate( (SqmCrossJoin<?>) parentRegistered );
}
else if ( parentRegistered instanceof SqmEntityJoin<?> ) {
correlated = selectQuery.correlate( (SqmEntityJoin<?>) parentRegistered );
}
else {
throw new UnsupportedOperationException( "Can't correlate from node: " + parentRegistered );
}
register( correlated );
//noinspection unchecked
return (X) correlated;
}
} }
return null; return null;

View File

@ -33,12 +33,12 @@ public interface SqmPathRegistry {
void register(SqmPath<?> sqmPath); void register(SqmPath<?> sqmPath);
/** /**
* Find a SqmFrom by its identification variable (alias). Will search any * Find a SqmFrom by its identification variable (alias).
* parent contexts as well * If the SqmFrom is found in a parent context, the correlation for the path will be returned.
* *
* @return matching SqmFrom or {@code null} * @return matching SqmFrom or {@code null}
*/ */
<X extends SqmFrom<?, ?>> X findFromByAlias(String identificationVariable); <X extends SqmFrom<?, ?>> X findFromByAlias(String identificationVariable, boolean searchParent);
/** /**
* Find a SqmFrom by its NavigablePath. Will search any parent contexts as well * Find a SqmFrom by its NavigablePath. Will search any parent contexts as well

View File

@ -43,8 +43,8 @@ public class FromClauseAccessImpl implements FromClauseAccess {
} }
@Override @Override
public TableGroup findTableGroupOnLeaf(NavigablePath navigablePath) { public TableGroup findTableGroupOnParents(NavigablePath navigablePath) {
return findTableGroup( navigablePath ); return null;
} }
@Override @Override

View File

@ -7,14 +7,15 @@
package org.hibernate.query.sqm.internal; package org.hibernate.query.sqm.internal;
import java.util.function.Function; import java.util.function.Function;
import jakarta.persistence.metamodel.Bindable;
import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.MappingMetamodel;
import org.hibernate.metamodel.mapping.CollectionPart; import org.hibernate.metamodel.mapping.CollectionPart;
import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.MappingModelExpressible; import org.hibernate.metamodel.mapping.MappingModelExpressible;
import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.ModelPartContainer; import org.hibernate.metamodel.mapping.ModelPartContainer;
import org.hibernate.metamodel.mapping.internal.EntityCollectionPart;
import org.hibernate.metamodel.model.domain.AnyMappingDomainType; import org.hibernate.metamodel.model.domain.AnyMappingDomainType;
import org.hibernate.metamodel.model.domain.BasicDomainType; import org.hibernate.metamodel.model.domain.BasicDomainType;
import org.hibernate.metamodel.model.domain.DomainType; import org.hibernate.metamodel.model.domain.DomainType;
@ -25,7 +26,6 @@ import org.hibernate.metamodel.model.domain.internal.AnyMappingSqmPathSource;
import org.hibernate.metamodel.model.domain.internal.BasicSqmPathSource; import org.hibernate.metamodel.model.domain.internal.BasicSqmPathSource;
import org.hibernate.metamodel.model.domain.internal.EmbeddedSqmPathSource; import org.hibernate.metamodel.model.domain.internal.EmbeddedSqmPathSource;
import org.hibernate.metamodel.model.domain.internal.EntitySqmPathSource; import org.hibernate.metamodel.model.domain.internal.EntitySqmPathSource;
import org.hibernate.metamodel.MappingMetamodel;
import org.hibernate.metamodel.model.domain.internal.MappedSuperclassSqmPathSource; import org.hibernate.metamodel.model.domain.internal.MappedSuperclassSqmPathSource;
import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.query.spi.NavigablePath; import org.hibernate.query.spi.NavigablePath;
@ -39,6 +39,8 @@ import org.hibernate.query.sqm.tree.domain.SqmTreatedPath;
import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.type.BasicType; import org.hibernate.type.BasicType;
import jakarta.persistence.metamodel.Bindable;
/** /**
* Helper for dealing with Hibernate's "mapping model" while processing an SQM which is defined * Helper for dealing with Hibernate's "mapping model" while processing an SQM which is defined
* in terms of the JPA/SQM metamodel * in terms of the JPA/SQM metamodel
@ -165,7 +167,17 @@ public class SqmMappingModelHelper {
sqmPath.getLhs().getReferencedPathSource().getPathName(), sqmPath.getLhs().getReferencedPathSource().getPathName(),
null null
); );
return pluralPart.findSubPart( sqmPath.getReferencedPathSource().getPathName(), null ); final CollectionPart collectionPart = (CollectionPart) pluralPart.findSubPart(
sqmPath.getReferencedPathSource()
.getPathName(),
null
);
// For entity collection parts, we must return the entity mapping type,
// as that is the mapping type of the expression
if ( collectionPart instanceof EntityCollectionPart ) {
return ( (EntityCollectionPart) collectionPart ).getEntityMappingType();
}
return collectionPart;
} }
if ( sqmPath.getLhs() == null ) { if ( sqmPath.getLhs() == null ) {

View File

@ -26,6 +26,7 @@ import org.hibernate.metamodel.MappingMetamodel;
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;
import org.hibernate.metamodel.mapping.EntityAssociationMapping;
import org.hibernate.metamodel.mapping.EntityIdentifierMapping; import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.JdbcMapping;
@ -352,9 +353,6 @@ public class SqmUtil {
if ( parameterType == null ) { if ( parameterType == null ) {
throw new SqlTreeCreationException( "Unable to interpret mapping-model type for Query parameter : " + domainParam ); throw new SqlTreeCreationException( "Unable to interpret mapping-model type for Query parameter : " + domainParam );
} }
if ( parameterType instanceof CollectionPart && ( (CollectionPart) parameterType ).getPartMappingType() instanceof Bindable ) {
parameterType = (Bindable) ( (CollectionPart) parameterType ).getPartMappingType();
}
if ( parameterType instanceof EntityIdentifierMapping ) { if ( parameterType instanceof EntityIdentifierMapping ) {
final EntityIdentifierMapping identifierMapping = (EntityIdentifierMapping) parameterType; final EntityIdentifierMapping identifierMapping = (EntityIdentifierMapping) parameterType;
@ -371,8 +369,8 @@ public class SqmUtil {
bindValue = identifierMapping.getIdentifier( bindValue ); bindValue = identifierMapping.getIdentifier( bindValue );
} }
} }
else if ( parameterType instanceof ToOneAttributeMapping ) { else if ( parameterType instanceof EntityAssociationMapping ) {
ToOneAttributeMapping association = (ToOneAttributeMapping) parameterType; EntityAssociationMapping association = (EntityAssociationMapping) parameterType;
bindValue = association.getForeignKeyDescriptor().getAssociationKeyFromSide( bindValue = association.getForeignKeyDescriptor().getAssociationKeyFromSide(
bindValue, bindValue,
association.getSideNature().inverse(), association.getSideNature().inverse(),

View File

@ -248,6 +248,7 @@ import org.hibernate.query.sqm.tree.update.SqmSetClause;
import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement;
import org.hibernate.sql.ast.Clause; import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.SqlAstJoinType; import org.hibernate.sql.ast.SqlAstJoinType;
import org.hibernate.sql.ast.SqlTreeCreationException;
import org.hibernate.sql.ast.SqlTreeCreationLogger; import org.hibernate.sql.ast.SqlTreeCreationLogger;
import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator; import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator;
import org.hibernate.sql.ast.spi.FromClauseAccess; import org.hibernate.sql.ast.spi.FromClauseAccess;
@ -565,13 +566,13 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
// FromClauseAccess // FromClauseAccess
@Override @Override
public TableGroup findTableGroupOnLeaf(NavigablePath navigablePath) { public TableGroup findTableGroup(NavigablePath navigablePath) {
return getFromClauseAccess().findTableGroupOnLeaf( navigablePath ); return getFromClauseAccess().findTableGroup( navigablePath );
} }
@Override @Override
public TableGroup findTableGroup(NavigablePath navigablePath) { public TableGroup findTableGroupOnParents(NavigablePath navigablePath) {
return getFromClauseAccess().findTableGroup( navigablePath ); return getFromClauseAccess().findTableGroupOnParents( navigablePath );
} }
@Override @Override
@ -1753,6 +1754,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
protected void visitOrderByOffsetAndFetch(SqmQueryPart<?> sqmQueryPart, QueryPart sqlQueryPart) { protected void visitOrderByOffsetAndFetch(SqmQueryPart<?> sqmQueryPart, QueryPart sqlQueryPart) {
if ( sqmQueryPart.getOrderByClause() != null ) { if ( sqmQueryPart.getOrderByClause() != null ) {
currentClauseStack.push( Clause.ORDER ); currentClauseStack.push( Clause.ORDER );
inferrableTypeAccessStack.push( () -> null );
try { try {
for ( SqmSortSpecification sortSpecification : sqmQueryPart.getOrderByClause() for ( SqmSortSpecification sortSpecification : sqmQueryPart.getOrderByClause()
.getSortSpecifications() ) { .getSortSpecifications() ) {
@ -1763,6 +1765,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
} }
} }
finally { finally {
inferrableTypeAccessStack.pop();
currentClauseStack.pop(); currentClauseStack.pop();
} }
} }
@ -1773,11 +1776,18 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
// case by using a subquery e.g. `... where alias in (select subAlias from ... limit ...)` // case by using a subquery e.g. `... where alias in (select subAlias from ... limit ...)`
// or use window functions e.g. `select ... from (select ..., dense_rank() over(order by ..., id) rn from ...) tmp where tmp.rn between ...` // or use window functions e.g. `select ... from (select ..., dense_rank() over(order by ..., id) rn from ...) tmp where tmp.rn between ...`
// but these transformations/translations are non-trivial and can be done later // but these transformations/translations are non-trivial and can be done later
inferrableTypeAccessStack.push( () -> getTypeConfiguration().getBasicTypeForJavaType( Integer.class ) );
sqlQueryPart.setOffsetClauseExpression( visitOffsetExpression( sqmQueryPart.getOffsetExpression() ) ); sqlQueryPart.setOffsetClauseExpression( visitOffsetExpression( sqmQueryPart.getOffsetExpression() ) );
if ( sqmQueryPart.getFetchClauseType() == FetchClauseType.PERCENT_ONLY
|| sqmQueryPart.getFetchClauseType() == FetchClauseType.PERCENT_WITH_TIES ) {
inferrableTypeAccessStack.pop();
inferrableTypeAccessStack.push( () -> getTypeConfiguration().getBasicTypeForJavaType( Double.class ) );
}
sqlQueryPart.setFetchClauseExpression( sqlQueryPart.setFetchClauseExpression(
visitFetchExpression( sqmQueryPart.getFetchExpression() ), visitFetchExpression( sqmQueryPart.getFetchExpression() ),
sqmQueryPart.getFetchClauseType() sqmQueryPart.getFetchClauseType()
); );
inferrableTypeAccessStack.pop();
} }
} }
@ -2065,6 +2075,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
public List<Expression> visitGroupByClause(List<SqmExpression<?>> groupByClauseExpressions) { public List<Expression> visitGroupByClause(List<SqmExpression<?>> groupByClauseExpressions) {
if ( !groupByClauseExpressions.isEmpty() ) { if ( !groupByClauseExpressions.isEmpty() ) {
currentClauseStack.push( Clause.GROUP ); currentClauseStack.push( Clause.GROUP );
inferrableTypeAccessStack.push( () -> null );
try { try {
final List<Expression> expressions = new ArrayList<>( groupByClauseExpressions.size() ); final List<Expression> expressions = new ArrayList<>( groupByClauseExpressions.size() );
for ( SqmExpression<?> groupByClauseExpression : groupByClauseExpressions ) { for ( SqmExpression<?> groupByClauseExpression : groupByClauseExpressions ) {
@ -2073,6 +2084,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
return expressions; return expressions;
} }
finally { finally {
inferrableTypeAccessStack.pop();
currentClauseStack.pop(); currentClauseStack.pop();
} }
} }
@ -2084,6 +2096,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
return null; return null;
} }
currentClauseStack.push( Clause.WHERE ); currentClauseStack.push( Clause.WHERE );
inferrableTypeAccessStack.push( () -> null );
try { try {
return SqlAstTreeHelper.combinePredicates( return SqlAstTreeHelper.combinePredicates(
(Predicate) sqmPredicate.accept( this ), (Predicate) sqmPredicate.accept( this ),
@ -2091,6 +2104,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
); );
} }
finally { finally {
inferrableTypeAccessStack.pop();
currentClauseStack.pop(); currentClauseStack.pop();
} }
} }
@ -2101,6 +2115,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
return null; return null;
} }
currentClauseStack.push( Clause.HAVING ); currentClauseStack.push( Clause.HAVING );
inferrableTypeAccessStack.push( () -> null );
try { try {
return SqlAstTreeHelper.combinePredicates( return SqlAstTreeHelper.combinePredicates(
(Predicate) sqmPredicate.accept( this ), (Predicate) sqmPredicate.accept( this ),
@ -2108,6 +2123,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
); );
} }
finally { finally {
inferrableTypeAccessStack.pop();
currentClauseStack.pop(); currentClauseStack.pop();
} }
} }
@ -2170,6 +2186,8 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
currentClauseStack.push( Clause.FROM ); currentClauseStack.push( Clause.FROM );
try { try {
// First, consume correlated roots, because these table groups can be used in join predicates of other from nodes
sqmFromClause.visitRoots( this::consumeFromClauseCorrelatedRoot );
sqmFromClause.visitRoots( this::consumeFromClauseRoot ); sqmFromClause.visitRoots( this::consumeFromClauseRoot );
} }
finally { finally {
@ -2179,6 +2197,185 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
return null; return null;
} }
protected void consumeFromClauseCorrelatedRoot(SqmRoot<?> sqmRoot) {
log.tracef( "Resolving SqmRoot [%s] to TableGroup", sqmRoot );
final FromClauseIndex fromClauseIndex = getFromClauseIndex();
if ( fromClauseIndex.isResolved( sqmRoot ) ) {
log.tracef( "Already resolved SqmRoot [%s] to TableGroup", sqmRoot );
}
final QuerySpec currentQuerySpec = currentQuerySpec();
final TableGroup tableGroup;
if ( !sqmRoot.isCorrelated() ) {
return;
}
final SessionFactoryImplementor sessionFactory = creationContext.getSessionFactory();
if ( sqmRoot.containsOnlyInnerJoins() ) {
// If we have just inner joins against a correlated root, we can render the joins as references
final SqmFrom<?, ?> from;
// If we correlate a join, we have to create a special SqmRoot shell called SqmCorrelatedRootJoin.
// The only purpose of that is to serve as SqmRoot, which is needed for the FROM clause.
// It will always contain just a single correlated join though, which is what is actually correlated
if ( sqmRoot instanceof SqmCorrelatedRootJoin<?> ) {
assert sqmRoot.getSqmJoins().size() == 1;
assert sqmRoot.getSqmJoins().get( 0 ).isCorrelated();
from = sqmRoot.getSqmJoins().get( 0 );
}
else {
from = sqmRoot;
}
final TableGroup parentTableGroup = fromClauseIndex.findTableGroupOnParents(
from.getCorrelationParent().getNavigablePath()
);
final SqlAliasBase sqlAliasBase = sqlAliasBaseManager.createSqlAliasBase( parentTableGroup.getGroupAlias() );
if ( parentTableGroup instanceof PluralTableGroup ) {
final PluralTableGroup pluralTableGroup = (PluralTableGroup) parentTableGroup;
final CorrelatedPluralTableGroup correlatedPluralTableGroup = new CorrelatedPluralTableGroup(
parentTableGroup,
sqlAliasBase,
currentQuerySpec,
predicate -> additionalRestrictions = SqlAstTreeHelper.combinePredicates(
additionalRestrictions,
predicate
),
sessionFactory
);
final TableGroup elementTableGroup = pluralTableGroup.getElementTableGroup();
if ( elementTableGroup != null ) {
final TableGroup correlatedElementTableGroup = new CorrelatedTableGroup(
elementTableGroup,
sqlAliasBase,
currentQuerySpec,
predicate -> additionalRestrictions = SqlAstTreeHelper.combinePredicates(
additionalRestrictions,
predicate
),
sessionFactory
);
final TableGroupJoin tableGroupJoin = new TableGroupJoin(
elementTableGroup.getNavigablePath(),
SqlAstJoinType.INNER,
correlatedElementTableGroup
);
correlatedPluralTableGroup.registerElementTableGroup( tableGroupJoin );
}
final TableGroup indexTableGroup = pluralTableGroup.getIndexTableGroup();
if ( indexTableGroup != null ) {
final TableGroup correlatedIndexTableGroup = new CorrelatedTableGroup(
indexTableGroup,
sqlAliasBase,
currentQuerySpec,
predicate -> additionalRestrictions = SqlAstTreeHelper.combinePredicates(
additionalRestrictions,
predicate
),
sessionFactory
);
final TableGroupJoin tableGroupJoin = new TableGroupJoin(
indexTableGroup.getNavigablePath(),
SqlAstJoinType.INNER,
correlatedIndexTableGroup
);
correlatedPluralTableGroup.registerIndexTableGroup( tableGroupJoin );
}
tableGroup = correlatedPluralTableGroup;
}
else {
tableGroup = new CorrelatedTableGroup(
parentTableGroup,
sqlAliasBase,
currentQuerySpec,
predicate -> additionalRestrictions = SqlAstTreeHelper.combinePredicates(
additionalRestrictions,
predicate
),
sessionFactory
);
}
fromClauseIndex.register( from, tableGroup );
registerPluralTableGroupParts( tableGroup );
log.tracef( "Resolved SqmRoot [%s] to correlated TableGroup [%s]", sqmRoot, tableGroup );
consumeExplicitJoins( from, tableGroup );
return;
}
else {
final EntityPersister entityDescriptor = resolveEntityPersister( sqmRoot.getReferencedPathSource() );
final TableGroup parentTableGroup = fromClauseIndex.findTableGroupOnParents(
sqmRoot.getCorrelationParent().getNavigablePath()
);
// If we have non-inner joins against a correlated root, we must render the root with a correlation predicate
tableGroup = entityDescriptor.createRootTableGroup(
true,
sqmRoot.getNavigablePath(),
sqmRoot.getExplicitAlias(),
() -> predicate -> {},
this,
creationContext
);
final EntityIdentifierMapping identifierMapping = entityDescriptor.getIdentifierMapping();
final NavigablePath navigablePath = sqmRoot.getNavigablePath().append( identifierMapping.getNavigableRole().getNavigableName() );
final int jdbcTypeCount = identifierMapping.getJdbcTypeCount();
if ( jdbcTypeCount == 1 ) {
identifierMapping.forEachSelectable(
(index, selectable) -> additionalRestrictions = SqlAstTreeHelper.combinePredicates(
additionalRestrictions,
new ComparisonPredicate(
new ColumnReference(
parentTableGroup.resolveTableReference( navigablePath, selectable.getContainingTableExpression() ),
selectable,
sessionFactory
),
ComparisonOperator.EQUAL,
new ColumnReference(
tableGroup.resolveTableReference( navigablePath, selectable.getContainingTableExpression() ),
selectable,
sessionFactory
)
)
)
);
}
else {
final List<Expression> lhs = new ArrayList<>( jdbcTypeCount );
final List<Expression> rhs = new ArrayList<>( jdbcTypeCount );
identifierMapping.forEachSelectable(
(index, selectable) -> {
lhs.add(
new ColumnReference(
parentTableGroup.resolveTableReference( navigablePath, selectable.getContainingTableExpression() ),
selectable,
sessionFactory
)
);
rhs.add(
new ColumnReference(
tableGroup.resolveTableReference( navigablePath, selectable.getContainingTableExpression() ),
selectable,
sessionFactory
)
);
}
);
additionalRestrictions = SqlAstTreeHelper.combinePredicates(
additionalRestrictions,
new ComparisonPredicate(
new SqlTuple( lhs, identifierMapping ),
ComparisonOperator.EQUAL,
new SqlTuple( rhs, identifierMapping )
)
);
}
}
log.tracef( "Resolved SqmRoot [%s] to new TableGroup [%s]", sqmRoot, tableGroup );
fromClauseIndex.register( sqmRoot, tableGroup );
currentQuerySpec.getFromClause().addRoot( tableGroup );
consumeJoins( sqmRoot, fromClauseIndex, tableGroup );
}
protected void consumeFromClauseRoot(SqmRoot<?> sqmRoot) { protected void consumeFromClauseRoot(SqmRoot<?> sqmRoot) {
log.tracef( "Resolving SqmRoot [%s] to TableGroup", sqmRoot ); log.tracef( "Resolving SqmRoot [%s] to TableGroup", sqmRoot );
final FromClauseIndex fromClauseIndex = getFromClauseIndex(); final FromClauseIndex fromClauseIndex = getFromClauseIndex();
@ -2188,195 +2385,39 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
final QuerySpec currentQuerySpec = currentQuerySpec(); final QuerySpec currentQuerySpec = currentQuerySpec();
final TableGroup tableGroup; final TableGroup tableGroup;
if ( sqmRoot.isCorrelated() ) { if ( sqmRoot.isCorrelated() ) {
final SessionFactoryImplementor sessionFactory = creationContext.getSessionFactory(); return;
final EntityPersister entityDescriptor = resolveEntityPersister( sqmRoot.getReferencedPathSource() );
if ( sqmRoot.containsOnlyInnerJoins() ) {
// If we have just inner joins against a correlated root, we can render the joins as references
final SqmFrom<?, ?> from;
// If we correlate a join, we have to create a special SqmRoot shell called SqmCorrelatedRootJoin.
// The only purpose of that is to serve as SqmRoot, which is needed for the FROM clause.
// It will always contain just a single correlated join though, which is what is actually correlated
if ( sqmRoot instanceof SqmCorrelatedRootJoin<?> ) {
assert sqmRoot.getSqmJoins().size() == 1;
assert sqmRoot.getSqmJoins().get( 0 ).isCorrelated();
from = sqmRoot.getSqmJoins().get( 0 );
}
else {
from = sqmRoot;
}
final TableGroup parentTableGroup = fromClauseIndex.findTableGroup(
from.getCorrelationParent().getNavigablePath()
);
final SqlAliasBase sqlAliasBase = sqlAliasBaseManager.createSqlAliasBase( parentTableGroup.getGroupAlias() );
if ( parentTableGroup instanceof PluralTableGroup ) {
final PluralTableGroup pluralTableGroup = (PluralTableGroup) parentTableGroup;
final CorrelatedPluralTableGroup correlatedPluralTableGroup = new CorrelatedPluralTableGroup(
parentTableGroup,
sqlAliasBase,
currentQuerySpec,
predicate -> additionalRestrictions = SqlAstTreeHelper.combinePredicates(
additionalRestrictions,
predicate
),
sessionFactory
);
final TableGroup elementTableGroup = pluralTableGroup.getElementTableGroup();
if ( elementTableGroup != null ) {
final TableGroup correlatedElementTableGroup = new CorrelatedTableGroup(
elementTableGroup,
sqlAliasBase,
currentQuerySpec,
predicate -> additionalRestrictions = SqlAstTreeHelper.combinePredicates(
additionalRestrictions,
predicate
),
sessionFactory
);
final TableGroupJoin tableGroupJoin = new TableGroupJoin(
elementTableGroup.getNavigablePath(),
SqlAstJoinType.INNER,
correlatedElementTableGroup
);
correlatedPluralTableGroup.registerElementTableGroup( tableGroupJoin );
}
final TableGroup indexTableGroup = pluralTableGroup.getIndexTableGroup();
if ( indexTableGroup != null ) {
final TableGroup correlatedIndexTableGroup = new CorrelatedTableGroup(
indexTableGroup,
sqlAliasBase,
currentQuerySpec,
predicate -> additionalRestrictions = SqlAstTreeHelper.combinePredicates(
additionalRestrictions,
predicate
),
sessionFactory
);
final TableGroupJoin tableGroupJoin = new TableGroupJoin(
indexTableGroup.getNavigablePath(),
SqlAstJoinType.INNER,
correlatedIndexTableGroup
);
correlatedPluralTableGroup.registerIndexTableGroup( tableGroupJoin );
}
tableGroup = correlatedPluralTableGroup;
}
else {
tableGroup = new CorrelatedTableGroup(
parentTableGroup,
sqlAliasBase,
currentQuerySpec,
predicate -> additionalRestrictions = SqlAstTreeHelper.combinePredicates(
additionalRestrictions,
predicate
),
sessionFactory
);
}
fromClauseIndex.register( from, tableGroup );
registerPluralTableGroupParts( tableGroup );
log.tracef( "Resolved SqmRoot [%s] to correlated TableGroup [%s]", sqmRoot, tableGroup );
consumeExplicitJoins( from, tableGroup );
return;
}
else {
final TableGroup parentTableGroup = fromClauseIndex.findTableGroup(
sqmRoot.getCorrelationParent().getNavigablePath()
);
// If we have non-inner joins against a correlated root, we must render the root with a correlation predicate
tableGroup = entityDescriptor.createRootTableGroup(
true,
sqmRoot.getNavigablePath(),
sqmRoot.getExplicitAlias(),
() -> predicate -> {},
this,
creationContext
);
final EntityIdentifierMapping identifierMapping = entityDescriptor.getIdentifierMapping();
final NavigablePath navigablePath = sqmRoot.getNavigablePath().append( identifierMapping.getNavigableRole().getNavigableName() );
final int jdbcTypeCount = identifierMapping.getJdbcTypeCount();
if ( jdbcTypeCount == 1 ) {
identifierMapping.forEachSelectable(
(index, selectable) -> additionalRestrictions = SqlAstTreeHelper.combinePredicates(
additionalRestrictions,
new ComparisonPredicate(
new ColumnReference(
parentTableGroup.resolveTableReference( navigablePath, selectable.getContainingTableExpression() ),
selectable,
sessionFactory
),
ComparisonOperator.EQUAL,
new ColumnReference(
tableGroup.resolveTableReference( navigablePath, selectable.getContainingTableExpression() ),
selectable,
sessionFactory
)
)
)
);
}
else {
final List<Expression> lhs = new ArrayList<>( jdbcTypeCount );
final List<Expression> rhs = new ArrayList<>( jdbcTypeCount );
identifierMapping.forEachSelectable(
(index, selectable) -> {
lhs.add(
new ColumnReference(
parentTableGroup.resolveTableReference( navigablePath, selectable.getContainingTableExpression() ),
selectable,
sessionFactory
)
);
rhs.add(
new ColumnReference(
tableGroup.resolveTableReference( navigablePath, selectable.getContainingTableExpression() ),
selectable,
sessionFactory
)
);
}
);
additionalRestrictions = SqlAstTreeHelper.combinePredicates(
additionalRestrictions,
new ComparisonPredicate(
new SqlTuple( lhs, identifierMapping ),
ComparisonOperator.EQUAL,
new SqlTuple( rhs, identifierMapping )
)
);
}
}
} }
else { final EntityPersister entityDescriptor = resolveEntityPersister( sqmRoot.getReferencedPathSource() );
final EntityPersister entityDescriptor = resolveEntityPersister( sqmRoot.getReferencedPathSource() ); tableGroup = entityDescriptor.createRootTableGroup(
tableGroup = entityDescriptor.createRootTableGroup( true,
true, sqmRoot.getNavigablePath(),
sqmRoot.getNavigablePath(), sqmRoot.getExplicitAlias(),
sqmRoot.getExplicitAlias(), () -> predicate -> additionalRestrictions = SqlAstTreeHelper.combinePredicates(
() -> predicate -> additionalRestrictions = SqlAstTreeHelper.combinePredicates( additionalRestrictions,
additionalRestrictions, predicate
predicate ),
), this,
this, creationContext
creationContext );
);
entityDescriptor.applyBaseRestrictions( entityDescriptor.applyBaseRestrictions(
currentQuerySpec::applyPredicate, currentQuerySpec::applyPredicate,
tableGroup, tableGroup,
true, true,
getLoadQueryInfluencers().getEnabledFilters(), getLoadQueryInfluencers().getEnabledFilters(),
null, null,
this this
); );
}
log.tracef( "Resolved SqmRoot [%s] to new TableGroup [%s]", sqmRoot, tableGroup ); log.tracef( "Resolved SqmRoot [%s] to new TableGroup [%s]", sqmRoot, tableGroup );
fromClauseIndex.register( sqmRoot, tableGroup ); fromClauseIndex.register( sqmRoot, tableGroup );
currentQuerySpec.getFromClause().addRoot( tableGroup ); currentQuerySpec.getFromClause().addRoot( tableGroup );
consumeJoins( sqmRoot, fromClauseIndex, tableGroup );
}
private void consumeJoins(SqmRoot<?> sqmRoot, FromClauseIndex fromClauseIndex, TableGroup tableGroup) {
if ( sqmRoot.getOrderedJoins() == null ) { if ( sqmRoot.getOrderedJoins() == null ) {
consumeExplicitJoins( sqmRoot, tableGroup ); consumeExplicitJoins( sqmRoot, tableGroup );
} }
@ -2401,7 +2442,8 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
} }
} }
assert ownerTableGroup != null; assert ownerTableGroup != null;
lastTableGroup = consumeExplicitJoin( join, lastTableGroup, ownerTableGroup, false ); final TableGroup actualTableGroup = findActualTableGroup( ownerTableGroup, join );
lastTableGroup = consumeExplicitJoin( join, lastTableGroup, actualTableGroup, false );
} }
} }
} }
@ -2762,6 +2804,10 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
} }
private <X> X prepareReusablePath(SqmPath<?> sqmPath, Supplier<X> supplier) { private <X> X prepareReusablePath(SqmPath<?> sqmPath, Supplier<X> supplier) {
return prepareReusablePath( sqmPath, fromClauseIndexStack.getCurrent(), supplier );
}
private <X> X prepareReusablePath(SqmPath<?> sqmPath, FromClauseIndex fromClauseIndex, Supplier<X> supplier) {
final Consumer<TableGroup> implicitJoinChecker; final Consumer<TableGroup> implicitJoinChecker;
if ( getCurrentProcessingState() instanceof SqlAstQueryPartProcessingState ) { if ( getCurrentProcessingState() instanceof SqlAstQueryPartProcessingState ) {
implicitJoinChecker = tg -> {}; implicitJoinChecker = tg -> {};
@ -2769,7 +2815,6 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
else { else {
implicitJoinChecker = BaseSqmToSqlAstConverter::verifyManipulationImplicitJoin; implicitJoinChecker = BaseSqmToSqlAstConverter::verifyManipulationImplicitJoin;
} }
final FromClauseIndex fromClauseIndex = fromClauseIndexStack.getCurrent();
prepareReusablePath( fromClauseIndex, sqmPath, implicitJoinChecker ); prepareReusablePath( fromClauseIndex, sqmPath, implicitJoinChecker );
return supplier.get(); return supplier.get();
} }
@ -2795,6 +2840,13 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
parentPath, parentPath,
implicitJoinChecker implicitJoinChecker
); );
if ( parentTableGroup == null ) {
final TableGroup parent = fromClauseIndex.findTableGroupOnParents( parentPath.getNavigablePath() );
if ( parent != null ) {
throw new SqlTreeCreationException( "Found un-correlated path usage in sub query - " + parentPath );
}
throw new SqlTreeCreationException( "Could not locate TableGroup - " + parentPath.getNavigablePath() );
}
if ( parentPath instanceof SqmTreatedPath<?, ?> ) { if ( parentPath instanceof SqmTreatedPath<?, ?> ) {
fromClauseIndex.register( (SqmPath<?>) parentPath, parentTableGroup ); fromClauseIndex.register( (SqmPath<?>) parentPath, parentTableGroup );
return parentTableGroup; return parentTableGroup;
@ -2877,9 +2929,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
final TableGroup tableGroup; final TableGroup tableGroup;
if ( subPart instanceof TableGroupJoinProducer ) { if ( subPart instanceof TableGroupJoinProducer ) {
final TableGroupJoinProducer joinProducer = (TableGroupJoinProducer) subPart; final TableGroupJoinProducer joinProducer = (TableGroupJoinProducer) subPart;
final SqlAstJoinType defaultSqlAstJoinType; if ( fromClauseIndex.findTableGroup( actualParentTableGroup.getNavigablePath() ) == null ) {
if ( fromClauseIndex.findTableGroupOnLeaf( actualParentTableGroup.getNavigablePath() ) == null ) {
final QuerySpec querySpec = currentQuerySpec(); final QuerySpec querySpec = currentQuerySpec();
// The parent table group is on a parent query, so we need a root table group // The parent table group is on a parent query, so we need a root table group
tableGroup = joinProducer.createRootTableGroupJoin( tableGroup = joinProducer.createRootTableGroupJoin(
@ -3056,47 +3106,97 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
} }
private Expression visitTableGroup(TableGroup tableGroup, SqmFrom<?, ?> path) { private Expression visitTableGroup(TableGroup tableGroup, SqmFrom<?, ?> path) {
final ModelPartContainer modelPart = tableGroup.getModelPart(); final ModelPartContainer modelPart;
final ModelPart keyPart; final MappingModelExpressible<?> inferredValueMapping = getInferredValueMapping();
final ModelPart resultPart; // For plain SqmFrom node uses, prefer the mapping type from the context if possible
if ( !( inferredValueMapping instanceof ModelPartContainer ) ) {
modelPart = tableGroup.getModelPart();
}
else {
modelPart = (ModelPartContainer) inferredValueMapping;
}
final ModelPart resultModelPart;
final ModelPart interpretationModelPart;
final TableGroup parentGroupToUse;
if ( modelPart instanceof ToOneAttributeMapping ) { if ( modelPart instanceof ToOneAttributeMapping ) {
final ToOneAttributeMapping toOneAttributeMapping = (ToOneAttributeMapping) modelPart; final ToOneAttributeMapping toOneAttributeMapping = (ToOneAttributeMapping) modelPart;
keyPart = toOneAttributeMapping.findSubPart( toOneAttributeMapping.getTargetKeyPropertyName() ); final ModelPart targetPart = toOneAttributeMapping.getForeignKeyDescriptor().getPart(
resultPart = modelPart; toOneAttributeMapping.getSideNature().inverse()
);
if ( tableGroup.getModelPart().getPartMappingType() == modelPart.getPartMappingType() ) {
resultModelPart = targetPart;
}
else {
// If the table group is for a different mapping type i.e. an inheritance subtype,
// lookup the target part on that mapping type
resultModelPart = tableGroup.getModelPart().findSubPart( targetPart.getPartName(), null );
}
interpretationModelPart = modelPart;
parentGroupToUse = null;
} }
else if ( modelPart instanceof PluralAttributeMapping ) { else if ( modelPart instanceof PluralAttributeMapping ) {
final PluralAttributeMapping pluralAttributeMapping = (PluralAttributeMapping) modelPart; final PluralAttributeMapping pluralAttributeMapping = (PluralAttributeMapping) modelPart;
final CollectionPart elementDescriptor = pluralAttributeMapping.getElementDescriptor(); final CollectionPart elementDescriptor = pluralAttributeMapping.getElementDescriptor();
if ( elementDescriptor instanceof EntityCollectionPart ) { if ( elementDescriptor instanceof EntityCollectionPart ) {
keyPart = ( (EntityCollectionPart) elementDescriptor ).getKeyTargetMatchPart(); // Usually, we need to resolve to the PK for visitTableGroup
final EntityCollectionPart collectionPart = (EntityCollectionPart) elementDescriptor;
final ModelPart collectionTargetPart = collectionPart.getForeignKeyDescriptor()
.getPart( collectionPart.getSideNature().inverse() );
final EntityIdentifierMapping identifierMapping = collectionPart.getEntityMappingType()
.getIdentifierMapping();
// If the FK points to the PK, we can use the FK part though, if this is not a root
if ( collectionTargetPart == identifierMapping && !( path instanceof SqmRoot<?> ) ) {
resultModelPart = collectionPart.getForeignKeyDescriptor().getPart( collectionPart.getSideNature() );
}
else {
resultModelPart = identifierMapping;
}
} }
else { else {
keyPart = elementDescriptor; resultModelPart = elementDescriptor;
} }
resultPart = elementDescriptor; interpretationModelPart = elementDescriptor;
parentGroupToUse = null;
} }
else if ( modelPart instanceof EntityCollectionPart ) { else if ( modelPart instanceof EntityCollectionPart ) {
keyPart = ( (EntityCollectionPart) modelPart ).getKeyTargetMatchPart(); // Usually, we need to resolve to the PK for visitTableGroup
resultPart = modelPart; final EntityCollectionPart collectionPart = (EntityCollectionPart) modelPart;
final ModelPart collectionTargetPart = collectionPart.getForeignKeyDescriptor()
.getPart( collectionPart.getSideNature().inverse() );
final EntityIdentifierMapping identifierMapping = collectionPart.getEntityMappingType()
.getIdentifierMapping();
// If the FK points to the PK, we can use the FK part though, if this is not a root
if ( collectionTargetPart == identifierMapping && !( path instanceof SqmRoot<?> ) ) {
resultModelPart = collectionPart.getForeignKeyDescriptor().getPart( collectionPart.getSideNature() );
}
else {
resultModelPart = identifierMapping;
}
interpretationModelPart = modelPart;
parentGroupToUse = findTableGroup( tableGroup.getNavigablePath().getParent() );
} }
else if ( modelPart instanceof EntityMappingType ) { else if ( modelPart instanceof EntityMappingType ) {
keyPart = ( (EntityMappingType) modelPart ).getIdentifierMapping(); resultModelPart = ( (EntityMappingType) modelPart ).getIdentifierMapping();
resultPart = modelPart; interpretationModelPart = modelPart;
// todo: I think this will always be null anyways because EntityMappingType will only be the model part
// of a TableGroup if that is a root TableGroup, so check if we can just switch to null
parentGroupToUse = findTableGroup( tableGroup.getNavigablePath().getParent() );
} }
else { else {
keyPart = modelPart; resultModelPart = modelPart;
resultPart = modelPart; interpretationModelPart = modelPart;
parentGroupToUse = null;
} }
final NavigablePath navigablePath; final NavigablePath navigablePath;
if ( resultPart == modelPart ) { if ( interpretationModelPart == modelPart ) {
navigablePath = tableGroup.getNavigablePath(); navigablePath = tableGroup.getNavigablePath();
} }
else { else {
navigablePath = tableGroup.getNavigablePath().append( resultPart.getPartName() ); navigablePath = tableGroup.getNavigablePath().append( interpretationModelPart.getPartName() );
} }
final Expression result; final Expression result;
if ( resultPart instanceof EntityValuedModelPart ) { if ( interpretationModelPart instanceof EntityValuedModelPart ) {
final boolean expandToAllColumns; final boolean expandToAllColumns;
if ( currentClauseStack.getCurrent() == Clause.GROUP ) { if ( currentClauseStack.getCurrent() == Clause.GROUP ) {
// When the table group is known to be fetched i.e. a fetch join // When the table group is known to be fetched i.e. a fetch join
@ -3108,7 +3208,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
expandToAllColumns = false; expandToAllColumns = false;
} }
final EntityValuedModelPart mapping = (EntityValuedModelPart) resultPart; final EntityValuedModelPart mapping = (EntityValuedModelPart) interpretationModelPart;
EntityMappingType mappingType; EntityMappingType mappingType;
if ( path instanceof SqmTreatedPath ) { if ( path instanceof SqmTreatedPath ) {
mappingType = creationContext.getSessionFactory() mappingType = creationContext.getSessionFactory()
@ -3120,18 +3220,17 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
mappingType = mapping.getEntityMappingType(); mappingType = mapping.getEntityMappingType();
} }
final TableGroup parentGroupToUse = findTableGroup( navigablePath.getParent() );
result = EntityValuedPathInterpretation.from( result = EntityValuedPathInterpretation.from(
navigablePath, navigablePath,
parentGroupToUse == null ? tableGroup : parentGroupToUse, parentGroupToUse == null ? tableGroup : parentGroupToUse,
(EntityValuedModelPart) resultPart, expandToAllColumns ? null : resultModelPart,
(EntityValuedModelPart) interpretationModelPart,
mappingType, mappingType,
expandToAllColumns,
this this
); );
} }
else if ( resultPart instanceof EmbeddableValuedModelPart ) { else if ( interpretationModelPart instanceof EmbeddableValuedModelPart ) {
final EmbeddableValuedModelPart mapping = (EmbeddableValuedModelPart) keyPart; final EmbeddableValuedModelPart mapping = (EmbeddableValuedModelPart) resultModelPart;
result = new EmbeddableValuedPathInterpretation<>( result = new EmbeddableValuedPathInterpretation<>(
mapping.toSqlExpression( mapping.toSqlExpression(
tableGroup, tableGroup,
@ -3140,15 +3239,15 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
getSqlAstCreationState() getSqlAstCreationState()
), ),
navigablePath, navigablePath,
(EmbeddableValuedModelPart) resultPart, (EmbeddableValuedModelPart) interpretationModelPart,
tableGroup tableGroup
); );
} }
else { else {
assert resultPart instanceof BasicValuedModelPart; assert interpretationModelPart instanceof BasicValuedModelPart;
final BasicValuedModelPart mapping = (BasicValuedModelPart) keyPart; final BasicValuedModelPart mapping = (BasicValuedModelPart) resultModelPart;
final TableReference tableReference = tableGroup.resolveTableReference( final TableReference tableReference = tableGroup.resolveTableReference(
navigablePath.append( keyPart.getPartName() ), navigablePath.append( resultModelPart.getPartName() ),
mapping.getContainingTableExpression() mapping.getContainingTableExpression()
); );
@ -3178,7 +3277,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
result = new BasicValuedPathInterpretation<>( result = new BasicValuedPathInterpretation<>(
columnReference, columnReference,
navigablePath, navigablePath,
(BasicValuedModelPart) resultPart, (BasicValuedModelPart) interpretationModelPart,
tableGroup tableGroup
); );
} }
@ -3328,7 +3427,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
@Override @Override
public Expression visitEntityValuedPath(SqmEntityValuedSimplePath<?> sqmPath) { public Expression visitEntityValuedPath(SqmEntityValuedSimplePath<?> sqmPath) {
return withTreatRestriction( return withTreatRestriction(
prepareReusablePath( sqmPath, () -> EntityValuedPathInterpretation.from( sqmPath, this ) ), prepareReusablePath( sqmPath, () -> EntityValuedPathInterpretation.from( sqmPath, getInferredValueMapping(), this ) ),
sqmPath sqmPath
); );
} }
@ -4447,19 +4546,23 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
@Override @Override
public MappingModelExpressible<?> determineValueMapping(SqmExpression<?> sqmExpression) { public MappingModelExpressible<?> determineValueMapping(SqmExpression<?> sqmExpression) {
return determineValueMapping( sqmExpression, fromClauseIndexStack.getCurrent() );
}
private MappingModelExpressible<?> determineValueMapping(SqmExpression<?> sqmExpression, FromClauseIndex fromClauseIndex) {
if ( sqmExpression instanceof SqmParameter ) { if ( sqmExpression instanceof SqmParameter ) {
return determineValueMapping( (SqmParameter<?>) sqmExpression ); return determineValueMapping( (SqmParameter<?>) sqmExpression );
} }
else if ( sqmExpression instanceof SqmPath ) { else if ( sqmExpression instanceof SqmPath ) {
log.debugf( "Determining mapping-model type for SqmPath : %s ", sqmExpression ); log.debugf( "Determining mapping-model type for SqmPath : %s ", sqmExpression );
prepareReusablePath( (SqmPath<?>) sqmExpression, () -> null ); prepareReusablePath( (SqmPath<?>) sqmExpression, fromClauseIndex, () -> null );
final MappingMetamodel domainModel = creationContext.getSessionFactory() final MappingMetamodel domainModel = creationContext.getSessionFactory()
.getRuntimeMetamodels() .getRuntimeMetamodels()
.getMappingMetamodel(); .getMappingMetamodel();
return SqmMappingModelHelper.resolveMappingModelExpressible( return SqmMappingModelHelper.resolveMappingModelExpressible(
sqmExpression, sqmExpression,
domainModel, domainModel,
getFromClauseAccess()::findTableGroup fromClauseIndex::findTableGroup
); );
} }
// The model type of an enum literal is always inferred // The model type of an enum literal is always inferred
@ -4492,12 +4595,12 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
final SqmPath<?> sqmPath = (SqmPath<?>) subQuerySelection.getSelectableNode(); final SqmPath<?> sqmPath = (SqmPath<?>) subQuerySelection.getSelectableNode();
final NavigablePath navigablePath = sqmPath.getNavigablePath().getParent(); final NavigablePath navigablePath = sqmPath.getNavigablePath().getParent();
if ( navigablePath.getParent() != null ) { if ( navigablePath.getParent() != null ) {
final TableGroup parentTableGroup = findTableGroup( navigablePath.getParent() ); final TableGroup parentTableGroup = fromClauseIndex.findTableGroup( navigablePath.getParent() );
final PluralAttributeMapping pluralPart = (PluralAttributeMapping) parentTableGroup.getModelPart() final PluralAttributeMapping pluralPart = (PluralAttributeMapping) parentTableGroup.getModelPart()
.findSubPart( navigablePath.getUnaliasedLocalName(), null ); .findSubPart( navigablePath.getUnaliasedLocalName(), null );
return pluralPart.findSubPart( pathSource.getPathName(), null ); return pluralPart.findSubPart( pathSource.getPathName(), null );
} }
return findTableGroup( navigablePath ).getModelPart(); return fromClauseIndex.findTableGroup( navigablePath ).getModelPart();
} }
} }
else { else {
@ -4537,7 +4640,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
.getMappingMetamodel(); .getMappingMetamodel();
final MappingModelExpressible<?> valueMapping = domainModel.resolveMappingExpressible( final MappingModelExpressible<?> valueMapping = domainModel.resolveMappingExpressible(
nodeType, nodeType,
this::findTableGroupByPath fromClauseIndex::getTableGroup
); );
if ( valueMapping == null ) { if ( valueMapping == null ) {
@ -4548,6 +4651,10 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
} }
if ( valueMapping == null ) { if ( valueMapping == null ) {
// For literals it is totally possible that we can't figure out a mapping type
if ( sqmExpression instanceof SqmLiteral<?> ) {
return null;
}
throw new ConversionException( "Could not determine ValueMapping for SqmExpression: " + sqmExpression ); throw new ConversionException( "Could not determine ValueMapping for SqmExpression: " + sqmExpression );
} }
@ -4558,7 +4665,11 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
final MappingModelExpressible<?> inferredMapping = resolveInferredType(); final MappingModelExpressible<?> inferredMapping = resolveInferredType();
if ( inferredMapping != null ) { if ( inferredMapping != null ) {
if ( inferredMapping instanceof PluralAttributeMapping ) { if ( inferredMapping instanceof PluralAttributeMapping ) {
return ( (PluralAttributeMapping) inferredMapping ).getElementDescriptor(); final CollectionPart elementDescriptor = ( (PluralAttributeMapping) inferredMapping ).getElementDescriptor();
if ( elementDescriptor instanceof EntityCollectionPart ) {
return ( (EntityCollectionPart) elementDescriptor ).getEntityMappingType();
}
return elementDescriptor;
} }
else if ( !( inferredMapping instanceof JavaObjectType ) ) { else if ( !( inferredMapping instanceof JavaObjectType ) ) {
// Never report back the "object type" as inferred type and instead rely on the value type // Never report back the "object type" as inferred type and instead rely on the value type
@ -4602,6 +4713,15 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
return inferredValueMapping; return inferredValueMapping;
} }
} }
else if ( paramType instanceof EntityDomainType ) {
// In JPA Criteria, it is possible to define a parameter of an entity type,
// but that should infer the mapping type from context,
// otherwise this would default to binding the PK which might be wrong
final MappingModelExpressible<?> inferredValueMapping = getInferredValueMapping();
if ( inferredValueMapping != null ) {
return inferredValueMapping;
}
}
final SqmExpressible<?> paramSqmType = paramType.resolveExpressible( creationContext.getSessionFactory() ); final SqmExpressible<?> paramSqmType = paramType.resolveExpressible( creationContext.getSessionFactory() );
@ -4653,7 +4773,8 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
sqmParameterMappingModelTypes.put( expression, valueMapping ); sqmParameterMappingModelTypes.put( expression, valueMapping );
final Bindable bindable; final Bindable bindable;
if ( valueMapping instanceof EntityAssociationMapping ) { if ( valueMapping instanceof EntityAssociationMapping ) {
bindable = ( (EntityAssociationMapping) valueMapping ).getKeyTargetMatchPart(); final EntityAssociationMapping mapping = (EntityAssociationMapping) valueMapping;
bindable = mapping.getForeignKeyDescriptor().getPart( mapping.getSideNature() );
} }
else if ( valueMapping instanceof EntityMappingType ) { else if ( valueMapping instanceof EntityMappingType ) {
bindable = ( (EntityMappingType) valueMapping ).getIdentifierMapping(); bindable = ( (EntityMappingType) valueMapping ).getIdentifierMapping();
@ -4905,10 +5026,11 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
} }
else { else {
// Infer one operand type through the other // Infer one operand type through the other
inferrableTypeAccessStack.push( () -> determineValueMapping( rightOperand ) ); final FromClauseIndex fromClauseIndex = fromClauseIndexStack.getCurrent();
inferrableTypeAccessStack.push( () -> determineValueMapping( rightOperand, fromClauseIndex ) );
final Expression lhs = toSqlExpression( leftOperand.accept( this ) ); final Expression lhs = toSqlExpression( leftOperand.accept( this ) );
inferrableTypeAccessStack.pop(); inferrableTypeAccessStack.pop();
inferrableTypeAccessStack.push( () -> determineValueMapping( leftOperand ) ); inferrableTypeAccessStack.push( () -> determineValueMapping( leftOperand, fromClauseIndex ) );
final Expression rhs = toSqlExpression( rightOperand.accept( this ) ); final Expression rhs = toSqlExpression( rightOperand.accept( this ) );
inferrableTypeAccessStack.pop(); inferrableTypeAccessStack.pop();
@ -5435,7 +5557,8 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
} }
private <X> X visitWithInferredType(SqmExpression<?> expression, SqmExpression<?> inferred) { private <X> X visitWithInferredType(SqmExpression<?> expression, SqmExpression<?> inferred) {
inferrableTypeAccessStack.push( () -> determineValueMapping( inferred ) ); final FromClauseIndex fromClauseIndex = fromClauseIndexStack.getCurrent();
inferrableTypeAccessStack.push( () -> determineValueMapping( inferred, fromClauseIndex ) );
try { try {
return (X) expression.accept( this ); return (X) expression.accept( this );
} }
@ -5762,7 +5885,8 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
@Override @Override
public ComparisonPredicate visitComparisonPredicate(SqmComparisonPredicate predicate) { public ComparisonPredicate visitComparisonPredicate(SqmComparisonPredicate predicate) {
inferrableTypeAccessStack.push( () -> determineValueMapping( predicate.getRightHandExpression() ) ); final FromClauseIndex fromClauseIndex = fromClauseIndexStack.getCurrent();
inferrableTypeAccessStack.push( () -> determineValueMapping( predicate.getRightHandExpression(), fromClauseIndex ) );
final Expression lhs; final Expression lhs;
try { try {
@ -5772,7 +5896,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
inferrableTypeAccessStack.pop(); inferrableTypeAccessStack.pop();
} }
inferrableTypeAccessStack.push( () -> determineValueMapping( predicate.getLeftHandExpression() ) ); inferrableTypeAccessStack.push( () -> determineValueMapping( predicate.getLeftHandExpression(), fromClauseIndex ) );
final Expression rhs; final Expression rhs;
try { try {
@ -5862,14 +5986,15 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
@Override @Override
public BetweenPredicate visitBetweenPredicate(SqmBetweenPredicate predicate) { public BetweenPredicate visitBetweenPredicate(SqmBetweenPredicate predicate) {
final FromClauseIndex fromClauseIndex = fromClauseIndexStack.getCurrent();
final Expression expression; final Expression expression;
final Expression lowerBound; final Expression lowerBound;
final Expression upperBound; final Expression upperBound;
inferrableTypeAccessStack.push( inferrableTypeAccessStack.push(
() -> coalesceSuppliedValues( () -> coalesceSuppliedValues(
() -> determineValueMapping( predicate.getLowerBound() ), () -> determineValueMapping( predicate.getLowerBound(), fromClauseIndex ),
() -> determineValueMapping( predicate.getUpperBound() ) () -> determineValueMapping( predicate.getUpperBound(), fromClauseIndex )
) )
); );
@ -5882,8 +6007,8 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
inferrableTypeAccessStack.push( inferrableTypeAccessStack.push(
() -> coalesceSuppliedValues( () -> coalesceSuppliedValues(
() -> determineValueMapping( predicate.getExpression() ), () -> determineValueMapping( predicate.getExpression(), fromClauseIndex ),
() -> determineValueMapping( predicate.getUpperBound() ) () -> determineValueMapping( predicate.getUpperBound(), fromClauseIndex )
) )
); );
try { try {
@ -5895,8 +6020,8 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
inferrableTypeAccessStack.push( inferrableTypeAccessStack.push(
() -> coalesceSuppliedValues( () -> coalesceSuppliedValues(
() -> determineValueMapping( predicate.getExpression() ), () -> determineValueMapping( predicate.getExpression(), fromClauseIndex ),
() -> determineValueMapping( predicate.getLowerBound() ) () -> determineValueMapping( predicate.getLowerBound(), fromClauseIndex )
) )
); );
try { try {
@ -5960,10 +6085,11 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
} }
// otherwise - no special case... // otherwise - no special case...
final FromClauseIndex fromClauseIndex = fromClauseIndexStack.getCurrent();
inferrableTypeAccessStack.push( inferrableTypeAccessStack.push(
() -> { () -> {
for ( SqmExpression<?> listExpression : predicate.getListExpressions() ) { for ( SqmExpression<?> listExpression : predicate.getListExpressions() ) {
final MappingModelExpressible<?> mapping = determineValueMapping( listExpression ); final MappingModelExpressible<?> mapping = determineValueMapping( listExpression, fromClauseIndex );
if ( mapping != null ) { if ( mapping != null ) {
return mapping; return mapping;
} }
@ -5985,7 +6111,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
getBooleanType() getBooleanType()
); );
inferrableTypeAccessStack.push( () -> determineValueMapping( predicate.getTestExpression() ) ); inferrableTypeAccessStack.push( () -> determineValueMapping( predicate.getTestExpression(), fromClauseIndex ) );
try { try {
for ( SqmExpression<?> expression : predicate.getListExpressions() ) { for ( SqmExpression<?> expression : predicate.getListExpressions() ) {
@ -6052,8 +6178,9 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
return inListPredicate; return inListPredicate;
} }
final FromClauseIndex fromClauseIndex = fromClauseIndexStack.getCurrent();
inferrableTypeAccessStack.push( inferrableTypeAccessStack.push(
() -> determineValueMapping( sqmPredicate.getTestExpression() ) () -> determineValueMapping( sqmPredicate.getTestExpression(), fromClauseIndex )
); );
try { try {

View File

@ -12,13 +12,13 @@ import java.util.List;
import java.util.function.Consumer; import java.util.function.Consumer;
import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.mapping.BasicEntityIdentifierMapping;
import org.hibernate.metamodel.mapping.BasicValuedModelPart; import org.hibernate.metamodel.mapping.BasicValuedModelPart;
import org.hibernate.metamodel.mapping.EntityAssociationMapping; import org.hibernate.metamodel.mapping.EntityAssociationMapping;
import org.hibernate.metamodel.mapping.EntityDiscriminatorMapping; import org.hibernate.metamodel.mapping.EntityDiscriminatorMapping;
import org.hibernate.metamodel.mapping.EntityIdentifierMapping; import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.EntityValuedModelPart; import org.hibernate.metamodel.mapping.EntityValuedModelPart;
import org.hibernate.metamodel.mapping.MappingModelExpressible;
import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.SelectableConsumer; import org.hibernate.metamodel.mapping.SelectableConsumer;
import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping;
@ -46,22 +46,67 @@ public class EntityValuedPathInterpretation<T> extends AbstractSqmPathInterpreta
public static <T> EntityValuedPathInterpretation<T> from( public static <T> EntityValuedPathInterpretation<T> from(
SqmEntityValuedSimplePath<T> sqmPath, SqmEntityValuedSimplePath<T> sqmPath,
MappingModelExpressible<?> inferredMapping,
SqmToSqlAstConverter sqlAstCreationState) { SqmToSqlAstConverter sqlAstCreationState) {
final TableGroup tableGroup = sqlAstCreationState final TableGroup tableGroup = sqlAstCreationState
.getFromClauseAccess() .getFromClauseAccess()
.findTableGroup( sqmPath.getLhs().getNavigablePath() ); .findTableGroup( sqmPath.getLhs().getNavigablePath() );
final EntityValuedModelPart pathMapping = (EntityValuedModelPart) sqlAstCreationState
final EntityValuedModelPart mapping = (EntityValuedModelPart) sqlAstCreationState
.getFromClauseAccess() .getFromClauseAccess()
.findTableGroup( sqmPath.getLhs().getNavigablePath() ) .findTableGroup( sqmPath.getLhs().getNavigablePath() )
.getModelPart() .getModelPart()
.findSubPart( sqmPath.getReferencedPathSource().getPathName(), null ); .findSubPart( sqmPath.getReferencedPathSource().getPathName(), null );
final EntityValuedModelPart mapping;
if ( inferredMapping instanceof EntityAssociationMapping ) {
final EntityAssociationMapping inferredAssociation = (EntityAssociationMapping) inferredMapping;
if ( pathMapping instanceof EntityAssociationMapping && inferredMapping != pathMapping ) {
// In here, the inferred mapping and the actual path mapping are association mappings,
// but for different associations, so we have to check if both associations point to the same target
final EntityAssociationMapping pathAssociation = (EntityAssociationMapping) pathMapping;
final ModelPart pathTargetPart = pathAssociation.getForeignKeyDescriptor()
.getPart( pathAssociation.getSideNature().inverse() );
final ModelPart inferredTargetPart = inferredAssociation.getForeignKeyDescriptor()
.getPart( inferredAssociation.getSideNature().inverse() );
// If the inferred association and path association targets are the same, we can use the path mapping type
// which will render the FK of the path association
if ( pathTargetPart == inferredTargetPart ) {
mapping = pathMapping;
}
else {
// Otherwise, we need to use the entity mapping type to force rendering the PK
// for e.g. `a.assoc1 = a.assoc2` when both associations have different target join columns
mapping = pathMapping.getEntityMappingType();
}
}
else {
// This is the case when the inferred mapping is an association, but the path mapping is not,
// or the path mapping and the inferred mapping are for the same association
mapping = (EntityValuedModelPart) inferredMapping;
}
}
else {
mapping = pathMapping;
}
final ModelPart resultModelPart;
if ( mapping instanceof EntityAssociationMapping ) {
final EntityAssociationMapping associationMapping = (EntityAssociationMapping) mapping;
final ModelPart keyTargetMatchPart = associationMapping.getKeyTargetMatchPart();
if ( keyTargetMatchPart instanceof ToOneAttributeMapping ) {
resultModelPart = ( (ToOneAttributeMapping) keyTargetMatchPart ).getKeyTargetMatchPart();
}
else {
resultModelPart = keyTargetMatchPart;
}
}
else {
resultModelPart = mapping.getEntityMappingType().getIdentifierMapping();
}
return from( return from(
sqmPath.getNavigablePath(), sqmPath.getNavigablePath(),
tableGroup, tableGroup,
resultModelPart,
mapping, mapping,
mapping, mapping,
false,
sqlAstCreationState sqlAstCreationState
); );
} }
@ -69,15 +114,15 @@ public class EntityValuedPathInterpretation<T> extends AbstractSqmPathInterpreta
public static <T> EntityValuedPathInterpretation<T> from( public static <T> EntityValuedPathInterpretation<T> from(
NavigablePath navigablePath, NavigablePath navigablePath,
TableGroup tableGroup, TableGroup tableGroup,
ModelPart resultModelPart,
EntityValuedModelPart mapping, EntityValuedModelPart mapping,
EntityValuedModelPart treatedMapping, EntityValuedModelPart treatedMapping,
boolean expandToAllColumns,
SqmToSqlAstConverter sqlAstCreationState) { SqmToSqlAstConverter sqlAstCreationState) {
final SqlExpressionResolver sqlExprResolver = sqlAstCreationState.getSqlExpressionResolver(); final SqlExpressionResolver sqlExprResolver = sqlAstCreationState.getSqlExpressionResolver();
final SessionFactoryImplementor sessionFactory = sqlAstCreationState.getCreationContext().getSessionFactory(); final SessionFactoryImplementor sessionFactory = sqlAstCreationState.getCreationContext().getSessionFactory();
final Expression sqlExpression; final Expression sqlExpression;
if ( expandToAllColumns ) { if ( resultModelPart == null ) {
final EntityMappingType entityMappingType = mapping.getEntityMappingType(); final EntityMappingType entityMappingType = mapping.getEntityMappingType();
final EntityIdentifierMapping identifierMapping = entityMappingType.getIdentifierMapping(); final EntityIdentifierMapping identifierMapping = entityMappingType.getIdentifierMapping();
final EntityDiscriminatorMapping discriminatorMapping = entityMappingType.getDiscriminatorMapping(); final EntityDiscriminatorMapping discriminatorMapping = entityMappingType.getDiscriminatorMapping();
@ -113,19 +158,9 @@ public class EntityValuedPathInterpretation<T> extends AbstractSqmPathInterpreta
entityMappingType.forEachSelectable( selectableConsumer ); entityMappingType.forEachSelectable( selectableConsumer );
sqlExpression = new SqlTuple( expressions, entityMappingType ); sqlExpression = new SqlTuple( expressions, entityMappingType );
} }
else if ( mapping instanceof EntityAssociationMapping ) { else {
final EntityAssociationMapping associationMapping = (EntityAssociationMapping) mapping; if ( resultModelPart instanceof BasicValuedModelPart ) {
final ModelPart keyTargetMatchPart = associationMapping.getKeyTargetMatchPart(); final BasicValuedModelPart basicValuedModelPart = (BasicValuedModelPart) resultModelPart;
final ModelPart lhsPart;
if ( keyTargetMatchPart instanceof ToOneAttributeMapping ) {
lhsPart = ( (ToOneAttributeMapping) keyTargetMatchPart ).getKeyTargetMatchPart();
}
else {
lhsPart = keyTargetMatchPart;
}
if ( lhsPart instanceof BasicValuedModelPart ) {
final BasicValuedModelPart basicValuedModelPart = (BasicValuedModelPart) lhsPart;
final TableReference tableReference = tableGroup.resolveTableReference( final TableReference tableReference = tableGroup.resolveTableReference(
navigablePath, navigablePath,
basicValuedModelPart.getContainingTableExpression() basicValuedModelPart.getContainingTableExpression()
@ -140,8 +175,8 @@ public class EntityValuedPathInterpretation<T> extends AbstractSqmPathInterpreta
); );
} }
else { else {
final List<Expression> expressions = new ArrayList<>( lhsPart.getJdbcTypeCount() ); final List<Expression> expressions = new ArrayList<>( resultModelPart.getJdbcTypeCount() );
lhsPart.forEachSelectable( resultModelPart.forEachSelectable(
(selectionIndex, selectableMapping) -> { (selectionIndex, selectableMapping) -> {
final TableReference tableReference = tableGroup.resolveTableReference( final TableReference tableReference = tableGroup.resolveTableReference(
navigablePath, navigablePath,
@ -162,59 +197,9 @@ public class EntityValuedPathInterpretation<T> extends AbstractSqmPathInterpreta
); );
} }
); );
sqlExpression = new SqlTuple( expressions, lhsPart ); sqlExpression = new SqlTuple( expressions, resultModelPart );
} }
} }
else {
assert mapping instanceof EntityMappingType;
final TableGroup parentTableGroup = tableGroup;
final EntityMappingType entityMappingType = (EntityMappingType) mapping;
final EntityIdentifierMapping identifierMapping = entityMappingType.getIdentifierMapping();
if ( identifierMapping instanceof BasicEntityIdentifierMapping ) {
final BasicEntityIdentifierMapping simpleIdMapping = (BasicEntityIdentifierMapping) identifierMapping;
final TableReference tableReference = parentTableGroup.resolveTableReference(
navigablePath,
simpleIdMapping.getContainingTableExpression()
);
assert tableReference != null : "Could not resolve table-group : " + simpleIdMapping.getContainingTableExpression();
sqlExpression = sqlExprResolver.resolveSqlExpression(
createColumnReferenceKey( tableReference, simpleIdMapping.getSelectionExpression() ),
processingState -> new ColumnReference(
tableReference,
simpleIdMapping,
sessionFactory
)
);
}
else {
final List<Expression> expressions = new ArrayList<>();
identifierMapping.forEachSelectable(
(selectionIndex, selectableMapping) -> {
final TableReference tableReference = parentTableGroup.resolveTableReference(
navigablePath, selectableMapping.getContainingTableExpression() );
expressions.add(
sqlExprResolver.resolveSqlExpression(
createColumnReferenceKey(
tableReference,
selectableMapping.getSelectionExpression()
),
processingState -> new ColumnReference(
tableReference,
selectableMapping,
sessionFactory
)
)
);
}
);
sqlExpression = new SqlTuple( expressions, identifierMapping );
}
}
return new EntityValuedPathInterpretation<>( return new EntityValuedPathInterpretation<>(
sqlExpression, sqlExpression,
navigablePath, navigablePath,

View File

@ -12,6 +12,7 @@ import java.util.function.Function;
import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.mapping.DiscriminatedAssociationModelPart; import org.hibernate.metamodel.mapping.DiscriminatedAssociationModelPart;
import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart; import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart;
import org.hibernate.metamodel.mapping.EntityAssociationMapping;
import org.hibernate.metamodel.mapping.EntityValuedModelPart; import org.hibernate.metamodel.mapping.EntityValuedModelPart;
import org.hibernate.metamodel.mapping.MappingModelExpressible; import org.hibernate.metamodel.mapping.MappingModelExpressible;
import org.hibernate.query.BindableType; import org.hibernate.query.BindableType;
@ -51,7 +52,11 @@ public class SqmParameterInterpretation implements Expression, DomainResultProdu
this.queryParameter = queryParameter; this.queryParameter = queryParameter;
this.queryParameterBindingResolver = queryParameterBindingResolver; this.queryParameterBindingResolver = queryParameterBindingResolver;
if ( valueMapping instanceof EntityValuedModelPart ) { if ( valueMapping instanceof EntityAssociationMapping ) {
final EntityAssociationMapping mapping = (EntityAssociationMapping) valueMapping;
this.valueMapping = mapping.getForeignKeyDescriptor().getPart( mapping.getSideNature() );
}
else if ( valueMapping instanceof EntityValuedModelPart ) {
this.valueMapping = ( (EntityValuedModelPart) valueMapping ).getEntityMappingType().getIdentifierMapping(); this.valueMapping = ( (EntityValuedModelPart) valueMapping ).getEntityMappingType().getIdentifierMapping();
} }
else { else {

View File

@ -153,7 +153,8 @@ public abstract class AbstractSqmFrom<O,T> extends AbstractSqmPath<T> implements
SqmPath<?> resolvedPath = null; SqmPath<?> resolvedPath = null;
ModelPartContainer modelPartContainer = null; ModelPartContainer modelPartContainer = null;
for ( SqmJoin<?, ?> sqmJoin : getSqmJoins() ) { for ( SqmJoin<?, ?> sqmJoin : getSqmJoins() ) {
if ( sqmJoin instanceof SqmAttributeJoin<?, ?> // We can only match singular joins here, as plural path parts are interpreted like sub-queries
if ( sqmJoin instanceof SqmSingularJoin<?, ?>
&& name.equals( sqmJoin.getReferencedPathSource().getPathName() ) ) { && name.equals( sqmJoin.getReferencedPathSource().getPathName() ) ) {
final SqmAttributeJoin<?, ?> attributeJoin = (SqmAttributeJoin<?, ?>) sqmJoin; final SqmAttributeJoin<?, ?> attributeJoin = (SqmAttributeJoin<?, ?>) sqmJoin;
if ( attributeJoin.getOn() == null ) { if ( attributeJoin.getOn() == null ) {

View File

@ -26,8 +26,9 @@ public class SqmCorrelatedBagJoin<O, T> extends SqmBagJoin<O, T> implements SqmC
public SqmCorrelatedBagJoin(SqmBagJoin<O, T> correlationParent) { public SqmCorrelatedBagJoin(SqmBagJoin<O, T> correlationParent) {
super( super(
correlationParent.getLhs(), correlationParent.getLhs(),
correlationParent.getNavigablePath(),
correlationParent.getAttribute(), correlationParent.getAttribute(),
null, correlationParent.getExplicitAlias(),
SqmJoinType.INNER, SqmJoinType.INNER,
false, false,
correlationParent.nodeBuilder() correlationParent.nodeBuilder()

View File

@ -23,8 +23,9 @@ public class SqmCorrelatedCrossJoin<T> extends SqmCrossJoin<T> implements SqmCor
public SqmCorrelatedCrossJoin(SqmCrossJoin<T> correlationParent) { public SqmCorrelatedCrossJoin(SqmCrossJoin<T> correlationParent) {
super( super(
correlationParent.getNavigablePath(),
correlationParent.getReferencedPathSource(), correlationParent.getReferencedPathSource(),
null, correlationParent.getExplicitAlias(),
correlationParent.getRoot() correlationParent.getRoot()
); );
this.correlatedRootJoin = SqmCorrelatedRootJoin.create( correlationParent, this ); this.correlatedRootJoin = SqmCorrelatedRootJoin.create( correlationParent, this );

View File

@ -24,8 +24,9 @@ public class SqmCorrelatedEntityJoin<T> extends SqmEntityJoin<T> implements SqmC
public SqmCorrelatedEntityJoin(SqmEntityJoin<T> correlationParent) { public SqmCorrelatedEntityJoin(SqmEntityJoin<T> correlationParent) {
super( super(
correlationParent.getNavigablePath(),
correlationParent.getReferencedPathSource(), correlationParent.getReferencedPathSource(),
null, correlationParent.getExplicitAlias(),
SqmJoinType.INNER, SqmJoinType.INNER,
correlationParent.getRoot() correlationParent.getRoot()
); );

View File

@ -26,8 +26,9 @@ public class SqmCorrelatedListJoin<O, T> extends SqmListJoin<O, T> implements Sq
public SqmCorrelatedListJoin(SqmListJoin<O, T> correlationParent) { public SqmCorrelatedListJoin(SqmListJoin<O, T> correlationParent) {
super( super(
correlationParent.getLhs(), correlationParent.getLhs(),
correlationParent.getNavigablePath(),
correlationParent.getAttribute(), correlationParent.getAttribute(),
null, correlationParent.getExplicitAlias(),
SqmJoinType.INNER, SqmJoinType.INNER,
false, false,
correlationParent.nodeBuilder() correlationParent.nodeBuilder()

View File

@ -26,8 +26,9 @@ public class SqmCorrelatedMapJoin<O, K, V> extends SqmMapJoin<O, K, V> implement
public SqmCorrelatedMapJoin(SqmMapJoin<O, K, V> correlationParent) { public SqmCorrelatedMapJoin(SqmMapJoin<O, K, V> correlationParent) {
super( super(
correlationParent.getLhs(), correlationParent.getLhs(),
correlationParent.getNavigablePath(),
correlationParent.getAttribute(), correlationParent.getAttribute(),
null, correlationParent.getExplicitAlias(),
SqmJoinType.INNER, SqmJoinType.INNER,
false, false,
correlationParent.nodeBuilder() correlationParent.nodeBuilder()

View File

@ -22,8 +22,9 @@ public class SqmCorrelatedPluralPartJoin<O, T> extends SqmPluralPartJoin<O, T> i
public SqmCorrelatedPluralPartJoin(SqmPluralPartJoin<O, T> correlationParent) { public SqmCorrelatedPluralPartJoin(SqmPluralPartJoin<O, T> correlationParent) {
super( super(
(SqmFrom<?, O>) correlationParent.getLhs(), (SqmFrom<?, O>) correlationParent.getLhs(),
correlationParent.getNavigablePath(),
correlationParent.getReferencedPathSource(), correlationParent.getReferencedPathSource(),
null, correlationParent.getExplicitAlias(),
SqmJoinType.INNER, SqmJoinType.INNER,
correlationParent.nodeBuilder() correlationParent.nodeBuilder()
); );

View File

@ -22,7 +22,7 @@ public class SqmCorrelatedRoot<T> extends SqmRoot<T> implements SqmPathWrapper<T
super( super(
correlationParent.getNavigablePath(), correlationParent.getNavigablePath(),
correlationParent.getReferencedPathSource(), correlationParent.getReferencedPathSource(),
null, correlationParent.getExplicitAlias(),
correlationParent.nodeBuilder() correlationParent.nodeBuilder()
); );
this.correlationParent = correlationParent; this.correlationParent = correlationParent;

View File

@ -26,8 +26,9 @@ public class SqmCorrelatedSetJoin<O, T> extends SqmSetJoin<O, T> implements SqmC
public SqmCorrelatedSetJoin(SqmSetJoin<O, T> correlationParent) { public SqmCorrelatedSetJoin(SqmSetJoin<O, T> correlationParent) {
super( super(
correlationParent.getLhs(), correlationParent.getLhs(),
correlationParent.getNavigablePath(),
correlationParent.getAttribute(), correlationParent.getAttribute(),
null, correlationParent.getExplicitAlias(),
SqmJoinType.INNER, SqmJoinType.INNER,
false, false,
correlationParent.nodeBuilder() correlationParent.nodeBuilder()

View File

@ -26,8 +26,9 @@ public class SqmCorrelatedSingularJoin<O, T> extends SqmSingularJoin<O, T> imple
public SqmCorrelatedSingularJoin(SqmSingularJoin<O, T> correlationParent) { public SqmCorrelatedSingularJoin(SqmSingularJoin<O, T> correlationParent) {
super( super(
correlationParent.getLhs(), correlationParent.getLhs(),
correlationParent.getNavigablePath(),
correlationParent.getAttribute(), correlationParent.getAttribute(),
null, correlationParent.getExplicitAlias(),
SqmJoinType.INNER, SqmJoinType.INNER,
false, false,
correlationParent.nodeBuilder() correlationParent.nodeBuilder()

View File

@ -24,51 +24,49 @@ import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
import org.hibernate.LockMode; import org.hibernate.LockMode;
import org.hibernate.LockOptions;
import org.hibernate.QueryException; import org.hibernate.QueryException;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.RowLockStrategy; import org.hibernate.dialect.RowLockStrategy;
import org.hibernate.dialect.SelectItemReferenceStrategy; import org.hibernate.dialect.SelectItemReferenceStrategy;
import org.hibernate.internal.util.MathHelper;
import org.hibernate.metamodel.mapping.AttributeMapping;
import org.hibernate.metamodel.mapping.EntityAssociationMapping;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.persister.entity.Queryable;
import org.hibernate.persister.internal.SqlFragmentPredicate;
import org.hibernate.query.IllegalQueryOperationException;
import org.hibernate.query.sqm.FrameExclusion;
import org.hibernate.query.sqm.FrameKind;
import org.hibernate.query.sqm.FrameMode;
import org.hibernate.query.sqm.SetOperator;
import org.hibernate.query.sqm.sql.internal.SqmPathInterpretation;
import org.hibernate.sql.ast.tree.cte.CteMaterialization;
import org.hibernate.sql.ast.tree.cte.CteSearchClauseKind;
import org.hibernate.query.sqm.FetchClauseType;
import org.hibernate.LockOptions;
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;
import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.internal.FilterJdbcParameter; import org.hibernate.internal.FilterJdbcParameter;
import org.hibernate.internal.util.MathHelper;
import org.hibernate.internal.util.StringHelper; 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.AttributeMapping;
import org.hibernate.metamodel.mapping.EntityAssociationMapping;
import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.ModelPart;
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.SqlExpressible; import org.hibernate.metamodel.mapping.SqlExpressible;
import org.hibernate.persister.entity.AbstractEntityPersister; import org.hibernate.persister.entity.AbstractEntityPersister;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.persister.entity.Loadable; import org.hibernate.persister.entity.Loadable;
import org.hibernate.query.sqm.ComparisonOperator; import org.hibernate.persister.entity.Queryable;
import org.hibernate.persister.internal.SqlFragmentPredicate;
import org.hibernate.query.IllegalQueryOperationException;
import org.hibernate.query.spi.Limit; import org.hibernate.query.spi.Limit;
import org.hibernate.query.spi.QueryOptions;
import org.hibernate.query.sqm.ComparisonOperator;
import org.hibernate.query.sqm.FetchClauseType;
import org.hibernate.query.sqm.FrameExclusion;
import org.hibernate.query.sqm.FrameKind;
import org.hibernate.query.sqm.FrameMode;
import org.hibernate.query.sqm.NullPrecedence; import org.hibernate.query.sqm.NullPrecedence;
import org.hibernate.query.sqm.SetOperator;
import org.hibernate.query.sqm.SortOrder; import org.hibernate.query.sqm.SortOrder;
import org.hibernate.query.sqm.UnaryArithmeticOperator; import org.hibernate.query.sqm.UnaryArithmeticOperator;
import org.hibernate.query.spi.QueryOptions;
import org.hibernate.query.sqm.function.AbstractSqmSelfRenderingFunctionDescriptor; import org.hibernate.query.sqm.function.AbstractSqmSelfRenderingFunctionDescriptor;
import org.hibernate.query.sqm.sql.internal.SqmParameterInterpretation; import org.hibernate.query.sqm.sql.internal.SqmParameterInterpretation;
import org.hibernate.query.sqm.sql.internal.SqmPathInterpretation;
import org.hibernate.query.sqm.tree.expression.Conversion; import org.hibernate.query.sqm.tree.expression.Conversion;
import org.hibernate.sql.ast.Clause; import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.SqlAstJoinType; import org.hibernate.sql.ast.SqlAstJoinType;
@ -80,6 +78,8 @@ import org.hibernate.sql.ast.tree.SqlAstNode;
import org.hibernate.sql.ast.tree.Statement; import org.hibernate.sql.ast.tree.Statement;
import org.hibernate.sql.ast.tree.cte.CteColumn; import org.hibernate.sql.ast.tree.cte.CteColumn;
import org.hibernate.sql.ast.tree.cte.CteContainer; import org.hibernate.sql.ast.tree.cte.CteContainer;
import org.hibernate.sql.ast.tree.cte.CteMaterialization;
import org.hibernate.sql.ast.tree.cte.CteSearchClauseKind;
import org.hibernate.sql.ast.tree.cte.CteStatement; import org.hibernate.sql.ast.tree.cte.CteStatement;
import org.hibernate.sql.ast.tree.cte.SearchClauseSpecification; import org.hibernate.sql.ast.tree.cte.SearchClauseSpecification;
import org.hibernate.sql.ast.tree.delete.DeleteStatement; import org.hibernate.sql.ast.tree.delete.DeleteStatement;
@ -3608,9 +3608,12 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
clauseStack.push( Clause.FROM ); clauseStack.push( Clause.FROM );
String separator = NO_SEPARATOR; String separator = NO_SEPARATOR;
for ( TableGroup root : fromClause.getRoots() ) { for ( TableGroup root : fromClause.getRoots() ) {
appendSql( separator ); // Skip virtual table group roots which we use for simple correlations
renderRootTableGroup( root, null ); if ( !( root instanceof VirtualTableGroup ) ) {
separator = COMA_SEPARATOR; appendSql( separator );
renderRootTableGroup( root, null );
separator = COMA_SEPARATOR;
}
} }
} }
finally { finally {

View File

@ -20,7 +20,12 @@ import org.hibernate.sql.ast.tree.from.TableGroup;
*/ */
public interface FromClauseAccess { public interface FromClauseAccess {
TableGroup findTableGroupOnLeaf(NavigablePath navigablePath); /**
* Find a TableGroup by the NavigablePath it is registered under,
* and if not found on the current from clause level, ask the parent. Returns
* {@code null} if no TableGroup is registered under that NavigablePath
*/
TableGroup findTableGroupOnParents(NavigablePath navigablePath);
/** /**
* Find a TableGroup by the NavigablePath it is registered under. Returns * Find a TableGroup by the NavigablePath it is registered under. Returns

View File

@ -32,17 +32,17 @@ public class SimpleFromClauseAccessImpl implements FromClauseAccess {
} }
@Override @Override
public TableGroup findTableGroupOnLeaf(NavigablePath navigablePath) { public TableGroup findTableGroupOnParents(NavigablePath navigablePath) {
return tableGroupMap.get( navigablePath ); final TableGroup tableGroup = tableGroupMap.get( navigablePath );
if ( tableGroup == null && parent != null ) {
return parent.findTableGroupOnParents( navigablePath );
}
return tableGroup;
} }
@Override @Override
public TableGroup findTableGroup(NavigablePath navigablePath) { public TableGroup findTableGroup(NavigablePath navigablePath) {
final TableGroup tableGroup = tableGroupMap.get( navigablePath ); return tableGroupMap.get( navigablePath );
if ( tableGroup == null && parent != null ) {
return parent.findTableGroup( navigablePath );
}
return tableGroup;
} }
@Override @Override

View File

@ -20,7 +20,7 @@ import org.hibernate.query.spi.NavigablePath;
* *
* @author Steve Ebersole * @author Steve Ebersole
*/ */
public class MutatingTableReferenceGroupWrapper implements VirtualTableGroup { public class MutatingTableReferenceGroupWrapper implements TableGroup {
private final NavigablePath navigablePath; private final NavigablePath navigablePath;
private final ModelPartContainer modelPart; private final ModelPartContainer modelPart;
private final NamedTableReference mutatingTableReference; private final NamedTableReference mutatingTableReference;

View File

@ -16,7 +16,7 @@ import org.hibernate.query.spi.NavigablePath;
/** /**
* @author Andrea Boriero * @author Andrea Boriero
*/ */
public class UnionTableGroup extends AbstractTableGroup implements VirtualTableGroup { public class UnionTableGroup extends AbstractTableGroup {
private final UnionTableReference tableReference; private final UnionTableReference tableReference;
public UnionTableGroup( public UnionTableGroup(

View File

@ -869,4 +869,12 @@ public class IndexedCollectionTest {
} }
); );
} }
@Test
public void testQueryWithKeywordAsFromAlias(SessionFactoryScope scope) {
// This test would fail if we didn't use the proper parsing rule for the FROM alias
scope.inSession(
s -> s.createQuery( "from Version version" ).getResultList()
);
}
} }

View File

@ -35,7 +35,7 @@ public class SubqueryInSelectClauseTest extends AbstractSubqueryInSelectClauseTe
Subquery<Long> personCount = query.subquery( Long.class ); Subquery<Long> personCount = query.subquery( Long.class );
Root<Person> person = personCount.from( Person.class ); Root<Person> person = personCount.from( Person.class );
personCount.select( cb.count( person ) ).where( cb.equal( contacts.get( "id" ), person.get( "id" ) ) ); personCount.select( cb.count( person ) ).where( cb.equal( personCount.correlate( contacts ).get( "id" ), person.get( "id" ) ) );
query.multiselect( document.get( "id" ), personCount.getSelection() ); query.multiselect( document.get( "id" ), personCount.getSelection() );

View File

@ -0,0 +1,173 @@
/*
* 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.orm.test.jpa.criteria.valuehandlingmode.inline;
import java.util.List;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.testing.orm.junit.EntityManagerFactoryScope;
import org.hibernate.testing.orm.junit.Jpa;
import org.hibernate.testing.orm.junit.Setting;
import org.junit.jupiter.api.Test;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Root;
import static org.junit.jupiter.api.Assertions.assertTrue;
@Jpa(
annotatedClasses = {
NonPkAssociationEqualityPredicateParameterTest.Customer.class,
NonPkAssociationEqualityPredicateParameterTest.Order.class
}
, properties = @Setting(name = AvailableSettings.CRITERIA_VALUE_HANDLING_MODE, value = "bind")
)
public class NonPkAssociationEqualityPredicateParameterTest {
@Test
public void testEqualityCheck(EntityManagerFactoryScope scope) {
scope.inTransaction(
entityManager -> {
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Order> orderCriteria = builder.createQuery( Order.class );
Root<Order> orderRoot = orderCriteria.from( Order.class );
orderCriteria.select( orderRoot );
Customer c = new Customer();
c.customerNumber = 123L;
orderCriteria.where(
builder.equal( orderRoot.get( "customer" ), c )
);
List<Order> orders = entityManager.createQuery( orderCriteria ).getResultList();
assertTrue( orders.size() == 0 );
}
);
}
@Entity
@Table(name = "ORDER_TABLE")
public static class Order {
private String id;
private double totalPrice;
private Customer customer;
public Order() {
}
public Order(String id, double totalPrice) {
this.id = id;
this.totalPrice = totalPrice;
}
public Order(String id, Customer customer) {
this.id = id;
this.customer = customer;
}
public Order(String id) {
this.id = id;
}
@Id
@Column(name = "ID")
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
@Column(name = "TOTALPRICE")
public double getTotalPrice() {
return totalPrice;
}
public void setTotalPrice(double price) {
this.totalPrice = price;
}
@ManyToOne
@JoinColumn(name = "FK4_FOR_CUSTOMER_TABLE", referencedColumnName = "CUSTOMER_NUMBER")
public Customer getCustomer() {
return customer;
}
public void setCustomer(Customer customer) {
this.customer = customer;
}
}
@Entity
@Table(name = "CUSTOMER_TABLE")
public static class Customer {
private String id;
private Long customerNumber;
private String name;
private Integer age;
public Customer() {
}
public Customer(String id, String name) {
this.id = id;
this.name = name;
}
// Used by test case for HHH-8699.
public Customer(String id, String name, String greeting, Boolean something) {
this.id = id;
this.name = name;
}
@Id
@Column(name = "ID")
public String getId() {
return id;
}
public void setId(String v) {
this.id = v;
}
@Column(name = "CUSTOMER_NUMBER", unique = true)
public Long getCustomerNumber() {
return customerNumber;
}
public void setCustomerNumber(Long customerNumber) {
this.customerNumber = customerNumber;
}
@Column(name = "NAME")
public String getName() {
return name;
}
public void setName(String v) {
this.name = v;
}
@Column(name = "AGE")
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
}

View File

@ -60,8 +60,17 @@ public class NonPkAssociationEqualityPredicateTest {
} }
); );
} }
@Test
public void testDifferentAssociationsEqualityCheck(EntityManagerFactoryScope scope) {
scope.inTransaction(
entityManager -> {
// This fails because we compare a ToOne with non-PK to something with a EntityValuedModelPart which defaults to the PK mapping
entityManager.createQuery( "from Order o, Customer c where o.customer = c" ).getResultList();
}
);
}
@Entity @Entity(name = "Order")
@Table(name = "ORDER_TABLE") @Table(name = "ORDER_TABLE")
public static class Order { public static class Order {
private String id; private String id;
@ -115,7 +124,7 @@ public class NonPkAssociationEqualityPredicateTest {
} }
} }
@Entity @Entity(name = "Customer")
@Table(name = "CUSTOMER_TABLE") @Table(name = "CUSTOMER_TABLE")
public static class Customer { public static class Customer {
private String id; private String id;

View File

@ -52,6 +52,15 @@ public class IsEmptyPredicateTest {
} ); } );
} }
@Test
public void testEmptinessPredicatesWithJoin(SessionFactoryScope scope) {
scope.inTransaction( (session) -> {
final List<Integer> ids = session.createQuery( "select p.id from Person p left join p.nicknames n where p.nicknames is not empty", Integer.class ).list();
assertThat( ids ).contains( personaWithSingleNicknameId, personWithMultipleNicknamesId );
assertThat( ids ).doesNotContain( personWithoutNicknameId );
} );
}
@BeforeEach @BeforeEach
protected void prepareTestData(SessionFactoryScope scope) { protected void prepareTestData(SessionFactoryScope scope) {
scope.inTransaction( (session) -> { scope.inTransaction( (session) -> {