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
: AS identifier
| IDENTIFIER
| QUOTED_IDENTIFIER
| nakedIdentifier
;
/**
* A 'cross join' to a second root entity (a cartesian product)
*/
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
* 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
| QUOTED_IDENTIFIER
| (ALL
@ -1510,7 +1510,7 @@ identifier
| FOR
| FORMAT
| FROM
| FULL
// | FULL
| FUNCTION
| GROUP
| GROUPS
@ -1522,7 +1522,7 @@ identifier
| IN
| INDEX
| INDICES
| INNER
// | INNER
| INSERT
| INSTANT
| INTERSECT
@ -1532,7 +1532,7 @@ identifier
| KEY
| LAST
| LEADING
| LEFT
// | LEFT
| LIKE
| LIMIT
| LIST
@ -1569,7 +1569,7 @@ identifier
| OR
| ORDER
| OTHERS
| OUTER
// | OUTER
| OVER
| OVERFLOW
| OVERLAY
@ -1582,7 +1582,7 @@ identifier
| QUARTER
| RANGE
| RESPECT
| RIGHT
// | RIGHT
| ROLLUP
| ROW
| ROWS
@ -1621,3 +1621,13 @@ identifier
logUseOfReservedWordAsIdentifier( getCurrentToken() );
}
;
identifier
: nakedIdentifier
| (FULL
| INNER
| LEFT
| OUTER
| RIGHT) {
logUseOfReservedWordAsIdentifier( getCurrentToken() );
}
;

View File

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

View File

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

View File

@ -110,7 +110,7 @@ public class QualifiedJoinPathConsumer implements DotIdentifierConsumer {
final SqmCreationProcessingState processingState = creationState.getCurrentProcessingState();
final SqmPathRegistry pathRegistry = processingState.getPathRegistry();
final SqmFrom<?, Object> pathRootByAlias = pathRegistry.findFromByAlias( identifier );
final SqmFrom<?, Object> pathRootByAlias = pathRegistry.findFromByAlias( identifier, true );
if ( pathRootByAlias != null ) {
// 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.SqmCreationState;
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.SqmFromClause;
import org.hibernate.query.sqm.tree.from.SqmQualifiedJoin;
@ -45,7 +46,7 @@ public class QualifiedJoinPredicatePathConsumer extends BasicDotIdentifierConsum
final SqmRoot<?> root = pathRoot.findRoot();
final SqmRoot<?> joinRoot = sqmJoin.findRoot();
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
SqmCreationProcessingState processingState = getCreationState().getCurrentProcessingState();
// 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,
// then the root of the processing path must be a root of one of the parent queries
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;
}
}

View File

@ -86,7 +86,6 @@ import org.hibernate.query.sqm.UnaryArithmeticOperator;
import org.hibernate.query.sqm.UnknownEntityException;
import org.hibernate.query.sqm.function.FunctionKind;
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.internal.ParameterCollector;
import org.hibernate.query.sqm.internal.SqmCreationProcessingStateImpl;
@ -1047,7 +1046,10 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
@Override
public SqmPath<?> visitJpaSelectObjectSyntax(HqlParser.JpaSelectObjectSyntaxContext ctx) {
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 ) {
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() );
}
final SqmFrom<?, ?> sqmFrom = getCurrentProcessingState().getPathRegistry().findFromByAlias( identifierText );
final SqmFrom<?, ?> sqmFrom = getCurrentProcessingState().getPathRegistry().findFromByAlias(
identifierText,
true
);
if ( sqmFrom != null ) {
if ( definedCollate ) {
// This is syntactically disallowed
@ -1360,6 +1365,15 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
@Override
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 );
if ( node.getSymbol().getType() == HqlParser.QUOTED_IDENTIFIER ) {
return QuotingHelper.unquoteIdentifier( node.getText() );
@ -1413,7 +1427,11 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
@Override
public SqmRoot<?> visitEntityWithJoins(HqlParser.EntityWithJoinsContext parserSpace) {
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();
for ( int i = 1; i < size; 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
if ( processingStateStack.depth() > 1 && size > 2 ) {
final String parentAlias = entityNameParseTreeChildren.get( 0 ).getText();
final AbstractSqmFrom<?, ?> correlationBasis = processingState.getParentProcessingState()
.getPathRegistry()
.findFromByAlias( parentAlias );
if ( correlationBasis != null ) {
final SqmCorrelation<?, ?> correlation = correlationBasis.createCorrelation();
pathRegistry.register( correlation );
final AbstractSqmFrom<?, ?> correlation = processingState.getPathRegistry()
.findFromByAlias( parentAlias, true );
if ( correlation instanceof SqmCorrelation<?, ?> ) {
final DotIdentifierConsumer dotIdentifierConsumer = new QualifiedJoinPathConsumer(
correlation,
SqmJoinType.INNER,
@ -1487,7 +1502,7 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
false,
true
);
return correlation.getCorrelatedRoot();
return ( (SqmCorrelation<?, ?>) correlation ).getCorrelatedRoot();
}
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 );
}
else {
final TerminalNode node = (TerminalNode) lastChild;
if ( node.getSymbol().getType() == HqlParser.QUOTED_IDENTIFIER ) {
return QuotingHelper.unquoteIdentifier( node.getText() );
final HqlParser.NakedIdentifierContext identifierContext = (HqlParser.NakedIdentifierContext) lastChild;
// in this branch, the alias could be a reserved word ("keyword as identifier")
// 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) {
final HqlParser.RootEntityContext pathRootContext = (HqlParser.RootEntityContext) parserJoin.getChild( 2 );
final HqlParser.EntityNameContext entityNameContext = (HqlParser.EntityNameContext) pathRootContext.getChild( 0 );
final HqlParser.EntityNameContext entityNameContext = (HqlParser.EntityNameContext) parserJoin.getChild( 2 );
final String name = getEntityName( entityNameContext );
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" );
}
final HqlParser.VariableContext identificationVariableDefContext;
if ( pathRootContext.getChildCount() > 1 ) {
identificationVariableDefContext = (HqlParser.VariableContext) pathRootContext.getChild( 1 );
if ( parserJoin.getChildCount() > 3 ) {
identificationVariableDefContext = (HqlParser.VariableContext) parserJoin.getChild( 3 );
}
else {
identificationVariableDefContext = null;
@ -3628,6 +3655,16 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
else {
//for the shorter legacy Hibernate syntax "field(arg)"
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(

View File

@ -14,17 +14,23 @@ import java.util.Map;
import java.util.function.Function;
import org.hibernate.jpa.spi.JpaCompliance;
import org.hibernate.query.spi.NavigablePath;
import org.hibernate.query.hql.HqlLogging;
import org.hibernate.query.hql.spi.SqmCreationProcessingState;
import org.hibernate.query.hql.spi.SqmPathRegistry;
import org.hibernate.query.spi.NavigablePath;
import org.hibernate.query.sqm.AliasCollisionException;
import org.hibernate.query.sqm.ParsingException;
import org.hibernate.query.sqm.SqmPathSource;
import org.hibernate.query.sqm.SqmTreeCreationLogger;
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.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.
@ -117,27 +123,12 @@ public class SqmPathRegistryImpl implements SqmPathRegistry {
@Override
public <X extends SqmFrom<?, ?>> X findFromByPath(NavigablePath navigablePath) {
final SqmFrom<?, ?> found = sqmFromByPath.get( navigablePath );
if ( found != null ) {
//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;
//noinspection unchecked
return (X) sqmFromByPath.get( navigablePath );
}
@Override
public <X extends SqmFrom<?, ?>> X findFromByAlias(String alias) {
public <X extends SqmFrom<?, ?>> X findFromByAlias(String alias, boolean searchParent) {
final String localAlias = jpaCompliance.isJpaQueryComplianceEnabled()
? alias.toLowerCase( Locale.getDefault() )
: alias;
@ -149,8 +140,39 @@ public class SqmPathRegistryImpl implements SqmPathRegistry {
return (X) registered;
}
if ( associatedProcessingState.getParentProcessingState() != null ) {
return associatedProcessingState.getParentProcessingState().getPathRegistry().findFromByAlias( alias );
SqmCreationProcessingState parentProcessingState = associatedProcessingState.getParentProcessingState();
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;

View File

@ -33,12 +33,12 @@ public interface SqmPathRegistry {
void register(SqmPath<?> sqmPath);
/**
* Find a SqmFrom by its identification variable (alias). Will search any
* parent contexts as well
* Find a SqmFrom by its identification variable (alias).
* If the SqmFrom is found in a parent context, the correlation for the path will be returned.
*
* @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

View File

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

View File

@ -7,14 +7,15 @@
package org.hibernate.query.sqm.internal;
import java.util.function.Function;
import jakarta.persistence.metamodel.Bindable;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.MappingMetamodel;
import org.hibernate.metamodel.mapping.CollectionPart;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.MappingModelExpressible;
import org.hibernate.metamodel.mapping.ModelPart;
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.BasicDomainType;
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.EmbeddedSqmPathSource;
import org.hibernate.metamodel.model.domain.internal.EntitySqmPathSource;
import org.hibernate.metamodel.MappingMetamodel;
import org.hibernate.metamodel.model.domain.internal.MappedSuperclassSqmPathSource;
import org.hibernate.persister.entity.EntityPersister;
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.type.BasicType;
import jakarta.persistence.metamodel.Bindable;
/**
* Helper for dealing with Hibernate's "mapping model" while processing an SQM which is defined
* in terms of the JPA/SQM metamodel
@ -165,7 +167,17 @@ public class SqmMappingModelHelper {
sqmPath.getLhs().getReferencedPathSource().getPathName(),
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 ) {

View File

@ -26,6 +26,7 @@ import org.hibernate.metamodel.MappingMetamodel;
import org.hibernate.metamodel.mapping.Bindable;
import org.hibernate.metamodel.mapping.CollectionPart;
import org.hibernate.metamodel.mapping.ConvertibleModelPart;
import org.hibernate.metamodel.mapping.EntityAssociationMapping;
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.JdbcMapping;
@ -352,9 +353,6 @@ public class SqmUtil {
if ( parameterType == null ) {
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 ) {
final EntityIdentifierMapping identifierMapping = (EntityIdentifierMapping) parameterType;
@ -371,8 +369,8 @@ public class SqmUtil {
bindValue = identifierMapping.getIdentifier( bindValue );
}
}
else if ( parameterType instanceof ToOneAttributeMapping ) {
ToOneAttributeMapping association = (ToOneAttributeMapping) parameterType;
else if ( parameterType instanceof EntityAssociationMapping ) {
EntityAssociationMapping association = (EntityAssociationMapping) parameterType;
bindValue = association.getForeignKeyDescriptor().getAssociationKeyFromSide(
bindValue,
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.sql.ast.Clause;
import org.hibernate.sql.ast.SqlAstJoinType;
import org.hibernate.sql.ast.SqlTreeCreationException;
import org.hibernate.sql.ast.SqlTreeCreationLogger;
import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator;
import org.hibernate.sql.ast.spi.FromClauseAccess;
@ -565,13 +566,13 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
// FromClauseAccess
@Override
public TableGroup findTableGroupOnLeaf(NavigablePath navigablePath) {
return getFromClauseAccess().findTableGroupOnLeaf( navigablePath );
public TableGroup findTableGroup(NavigablePath navigablePath) {
return getFromClauseAccess().findTableGroup( navigablePath );
}
@Override
public TableGroup findTableGroup(NavigablePath navigablePath) {
return getFromClauseAccess().findTableGroup( navigablePath );
public TableGroup findTableGroupOnParents(NavigablePath navigablePath) {
return getFromClauseAccess().findTableGroupOnParents( navigablePath );
}
@Override
@ -1753,6 +1754,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
protected void visitOrderByOffsetAndFetch(SqmQueryPart<?> sqmQueryPart, QueryPart sqlQueryPart) {
if ( sqmQueryPart.getOrderByClause() != null ) {
currentClauseStack.push( Clause.ORDER );
inferrableTypeAccessStack.push( () -> null );
try {
for ( SqmSortSpecification sortSpecification : sqmQueryPart.getOrderByClause()
.getSortSpecifications() ) {
@ -1763,6 +1765,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
}
}
finally {
inferrableTypeAccessStack.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 ...)`
// 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
inferrableTypeAccessStack.push( () -> getTypeConfiguration().getBasicTypeForJavaType( Integer.class ) );
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(
visitFetchExpression( sqmQueryPart.getFetchExpression() ),
sqmQueryPart.getFetchClauseType()
);
inferrableTypeAccessStack.pop();
}
}
@ -2065,6 +2075,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
public List<Expression> visitGroupByClause(List<SqmExpression<?>> groupByClauseExpressions) {
if ( !groupByClauseExpressions.isEmpty() ) {
currentClauseStack.push( Clause.GROUP );
inferrableTypeAccessStack.push( () -> null );
try {
final List<Expression> expressions = new ArrayList<>( groupByClauseExpressions.size() );
for ( SqmExpression<?> groupByClauseExpression : groupByClauseExpressions ) {
@ -2073,6 +2084,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
return expressions;
}
finally {
inferrableTypeAccessStack.pop();
currentClauseStack.pop();
}
}
@ -2084,6 +2096,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
return null;
}
currentClauseStack.push( Clause.WHERE );
inferrableTypeAccessStack.push( () -> null );
try {
return SqlAstTreeHelper.combinePredicates(
(Predicate) sqmPredicate.accept( this ),
@ -2091,6 +2104,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
);
}
finally {
inferrableTypeAccessStack.pop();
currentClauseStack.pop();
}
}
@ -2101,6 +2115,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
return null;
}
currentClauseStack.push( Clause.HAVING );
inferrableTypeAccessStack.push( () -> null );
try {
return SqlAstTreeHelper.combinePredicates(
(Predicate) sqmPredicate.accept( this ),
@ -2108,6 +2123,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
);
}
finally {
inferrableTypeAccessStack.pop();
currentClauseStack.pop();
}
}
@ -2170,6 +2186,8 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
currentClauseStack.push( Clause.FROM );
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 );
}
finally {
@ -2179,6 +2197,185 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
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) {
log.tracef( "Resolving SqmRoot [%s] to TableGroup", sqmRoot );
final FromClauseIndex fromClauseIndex = getFromClauseIndex();
@ -2188,195 +2385,39 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
final QuerySpec currentQuerySpec = currentQuerySpec();
final TableGroup tableGroup;
if ( sqmRoot.isCorrelated() ) {
final SessionFactoryImplementor sessionFactory = creationContext.getSessionFactory();
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 )
)
);
}
}
return;
}
else {
final EntityPersister entityDescriptor = resolveEntityPersister( sqmRoot.getReferencedPathSource() );
tableGroup = entityDescriptor.createRootTableGroup(
true,
sqmRoot.getNavigablePath(),
sqmRoot.getExplicitAlias(),
() -> predicate -> additionalRestrictions = SqlAstTreeHelper.combinePredicates(
additionalRestrictions,
predicate
),
this,
creationContext
);
final EntityPersister entityDescriptor = resolveEntityPersister( sqmRoot.getReferencedPathSource() );
tableGroup = entityDescriptor.createRootTableGroup(
true,
sqmRoot.getNavigablePath(),
sqmRoot.getExplicitAlias(),
() -> predicate -> additionalRestrictions = SqlAstTreeHelper.combinePredicates(
additionalRestrictions,
predicate
),
this,
creationContext
);
entityDescriptor.applyBaseRestrictions(
currentQuerySpec::applyPredicate,
tableGroup,
true,
getLoadQueryInfluencers().getEnabledFilters(),
null,
this
);
}
entityDescriptor.applyBaseRestrictions(
currentQuerySpec::applyPredicate,
tableGroup,
true,
getLoadQueryInfluencers().getEnabledFilters(),
null,
this
);
log.tracef( "Resolved SqmRoot [%s] to new TableGroup [%s]", sqmRoot, tableGroup );
fromClauseIndex.register( sqmRoot, tableGroup );
currentQuerySpec.getFromClause().addRoot( tableGroup );
consumeJoins( sqmRoot, fromClauseIndex, tableGroup );
}
private void consumeJoins(SqmRoot<?> sqmRoot, FromClauseIndex fromClauseIndex, TableGroup tableGroup) {
if ( sqmRoot.getOrderedJoins() == null ) {
consumeExplicitJoins( sqmRoot, tableGroup );
}
@ -2401,7 +2442,8 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
}
}
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) {
return prepareReusablePath( sqmPath, fromClauseIndexStack.getCurrent(), supplier );
}
private <X> X prepareReusablePath(SqmPath<?> sqmPath, FromClauseIndex fromClauseIndex, Supplier<X> supplier) {
final Consumer<TableGroup> implicitJoinChecker;
if ( getCurrentProcessingState() instanceof SqlAstQueryPartProcessingState ) {
implicitJoinChecker = tg -> {};
@ -2769,7 +2815,6 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
else {
implicitJoinChecker = BaseSqmToSqlAstConverter::verifyManipulationImplicitJoin;
}
final FromClauseIndex fromClauseIndex = fromClauseIndexStack.getCurrent();
prepareReusablePath( fromClauseIndex, sqmPath, implicitJoinChecker );
return supplier.get();
}
@ -2795,6 +2840,13 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
parentPath,
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<?, ?> ) {
fromClauseIndex.register( (SqmPath<?>) parentPath, parentTableGroup );
return parentTableGroup;
@ -2877,9 +2929,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
final TableGroup tableGroup;
if ( subPart instanceof TableGroupJoinProducer ) {
final TableGroupJoinProducer joinProducer = (TableGroupJoinProducer) subPart;
final SqlAstJoinType defaultSqlAstJoinType;
if ( fromClauseIndex.findTableGroupOnLeaf( actualParentTableGroup.getNavigablePath() ) == null ) {
if ( fromClauseIndex.findTableGroup( actualParentTableGroup.getNavigablePath() ) == null ) {
final QuerySpec querySpec = currentQuerySpec();
// The parent table group is on a parent query, so we need a root table group
tableGroup = joinProducer.createRootTableGroupJoin(
@ -3056,47 +3106,97 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
}
private Expression visitTableGroup(TableGroup tableGroup, SqmFrom<?, ?> path) {
final ModelPartContainer modelPart = tableGroup.getModelPart();
final ModelPart keyPart;
final ModelPart resultPart;
final ModelPartContainer modelPart;
final MappingModelExpressible<?> inferredValueMapping = getInferredValueMapping();
// 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 ) {
final ToOneAttributeMapping toOneAttributeMapping = (ToOneAttributeMapping) modelPart;
keyPart = toOneAttributeMapping.findSubPart( toOneAttributeMapping.getTargetKeyPropertyName() );
resultPart = modelPart;
final ModelPart targetPart = toOneAttributeMapping.getForeignKeyDescriptor().getPart(
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 ) {
final PluralAttributeMapping pluralAttributeMapping = (PluralAttributeMapping) modelPart;
final CollectionPart elementDescriptor = pluralAttributeMapping.getElementDescriptor();
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 {
keyPart = elementDescriptor;
resultModelPart = elementDescriptor;
}
resultPart = elementDescriptor;
interpretationModelPart = elementDescriptor;
parentGroupToUse = null;
}
else if ( modelPart instanceof EntityCollectionPart ) {
keyPart = ( (EntityCollectionPart) modelPart ).getKeyTargetMatchPart();
resultPart = modelPart;
// Usually, we need to resolve to the PK for visitTableGroup
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 ) {
keyPart = ( (EntityMappingType) modelPart ).getIdentifierMapping();
resultPart = modelPart;
resultModelPart = ( (EntityMappingType) modelPart ).getIdentifierMapping();
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 {
keyPart = modelPart;
resultPart = modelPart;
resultModelPart = modelPart;
interpretationModelPart = modelPart;
parentGroupToUse = null;
}
final NavigablePath navigablePath;
if ( resultPart == modelPart ) {
if ( interpretationModelPart == modelPart ) {
navigablePath = tableGroup.getNavigablePath();
}
else {
navigablePath = tableGroup.getNavigablePath().append( resultPart.getPartName() );
navigablePath = tableGroup.getNavigablePath().append( interpretationModelPart.getPartName() );
}
final Expression result;
if ( resultPart instanceof EntityValuedModelPart ) {
if ( interpretationModelPart instanceof EntityValuedModelPart ) {
final boolean expandToAllColumns;
if ( currentClauseStack.getCurrent() == Clause.GROUP ) {
// 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;
}
final EntityValuedModelPart mapping = (EntityValuedModelPart) resultPart;
final EntityValuedModelPart mapping = (EntityValuedModelPart) interpretationModelPart;
EntityMappingType mappingType;
if ( path instanceof SqmTreatedPath ) {
mappingType = creationContext.getSessionFactory()
@ -3120,18 +3220,17 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
mappingType = mapping.getEntityMappingType();
}
final TableGroup parentGroupToUse = findTableGroup( navigablePath.getParent() );
result = EntityValuedPathInterpretation.from(
navigablePath,
parentGroupToUse == null ? tableGroup : parentGroupToUse,
(EntityValuedModelPart) resultPart,
expandToAllColumns ? null : resultModelPart,
(EntityValuedModelPart) interpretationModelPart,
mappingType,
expandToAllColumns,
this
);
}
else if ( resultPart instanceof EmbeddableValuedModelPart ) {
final EmbeddableValuedModelPart mapping = (EmbeddableValuedModelPart) keyPart;
else if ( interpretationModelPart instanceof EmbeddableValuedModelPart ) {
final EmbeddableValuedModelPart mapping = (EmbeddableValuedModelPart) resultModelPart;
result = new EmbeddableValuedPathInterpretation<>(
mapping.toSqlExpression(
tableGroup,
@ -3140,15 +3239,15 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
getSqlAstCreationState()
),
navigablePath,
(EmbeddableValuedModelPart) resultPart,
(EmbeddableValuedModelPart) interpretationModelPart,
tableGroup
);
}
else {
assert resultPart instanceof BasicValuedModelPart;
final BasicValuedModelPart mapping = (BasicValuedModelPart) keyPart;
assert interpretationModelPart instanceof BasicValuedModelPart;
final BasicValuedModelPart mapping = (BasicValuedModelPart) resultModelPart;
final TableReference tableReference = tableGroup.resolveTableReference(
navigablePath.append( keyPart.getPartName() ),
navigablePath.append( resultModelPart.getPartName() ),
mapping.getContainingTableExpression()
);
@ -3178,7 +3277,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
result = new BasicValuedPathInterpretation<>(
columnReference,
navigablePath,
(BasicValuedModelPart) resultPart,
(BasicValuedModelPart) interpretationModelPart,
tableGroup
);
}
@ -3328,7 +3427,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
@Override
public Expression visitEntityValuedPath(SqmEntityValuedSimplePath<?> sqmPath) {
return withTreatRestriction(
prepareReusablePath( sqmPath, () -> EntityValuedPathInterpretation.from( sqmPath, this ) ),
prepareReusablePath( sqmPath, () -> EntityValuedPathInterpretation.from( sqmPath, getInferredValueMapping(), this ) ),
sqmPath
);
}
@ -4447,19 +4546,23 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
@Override
public MappingModelExpressible<?> determineValueMapping(SqmExpression<?> sqmExpression) {
return determineValueMapping( sqmExpression, fromClauseIndexStack.getCurrent() );
}
private MappingModelExpressible<?> determineValueMapping(SqmExpression<?> sqmExpression, FromClauseIndex fromClauseIndex) {
if ( sqmExpression instanceof SqmParameter ) {
return determineValueMapping( (SqmParameter<?>) sqmExpression );
}
else if ( sqmExpression instanceof SqmPath ) {
log.debugf( "Determining mapping-model type for SqmPath : %s ", sqmExpression );
prepareReusablePath( (SqmPath<?>) sqmExpression, () -> null );
prepareReusablePath( (SqmPath<?>) sqmExpression, fromClauseIndex, () -> null );
final MappingMetamodel domainModel = creationContext.getSessionFactory()
.getRuntimeMetamodels()
.getMappingMetamodel();
return SqmMappingModelHelper.resolveMappingModelExpressible(
sqmExpression,
domainModel,
getFromClauseAccess()::findTableGroup
fromClauseIndex::findTableGroup
);
}
// 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 NavigablePath navigablePath = sqmPath.getNavigablePath().getParent();
if ( navigablePath.getParent() != null ) {
final TableGroup parentTableGroup = findTableGroup( navigablePath.getParent() );
final TableGroup parentTableGroup = fromClauseIndex.findTableGroup( navigablePath.getParent() );
final PluralAttributeMapping pluralPart = (PluralAttributeMapping) parentTableGroup.getModelPart()
.findSubPart( navigablePath.getUnaliasedLocalName(), null );
return pluralPart.findSubPart( pathSource.getPathName(), null );
}
return findTableGroup( navigablePath ).getModelPart();
return fromClauseIndex.findTableGroup( navigablePath ).getModelPart();
}
}
else {
@ -4537,7 +4640,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
.getMappingMetamodel();
final MappingModelExpressible<?> valueMapping = domainModel.resolveMappingExpressible(
nodeType,
this::findTableGroupByPath
fromClauseIndex::getTableGroup
);
if ( valueMapping == null ) {
@ -4548,6 +4651,10 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
}
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 );
}
@ -4558,7 +4665,11 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
final MappingModelExpressible<?> inferredMapping = resolveInferredType();
if ( inferredMapping != null ) {
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 ) ) {
// 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;
}
}
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() );
@ -4653,7 +4773,8 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
sqmParameterMappingModelTypes.put( expression, valueMapping );
final Bindable bindable;
if ( valueMapping instanceof EntityAssociationMapping ) {
bindable = ( (EntityAssociationMapping) valueMapping ).getKeyTargetMatchPart();
final EntityAssociationMapping mapping = (EntityAssociationMapping) valueMapping;
bindable = mapping.getForeignKeyDescriptor().getPart( mapping.getSideNature() );
}
else if ( valueMapping instanceof EntityMappingType ) {
bindable = ( (EntityMappingType) valueMapping ).getIdentifierMapping();
@ -4905,10 +5026,11 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
}
else {
// 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 ) );
inferrableTypeAccessStack.pop();
inferrableTypeAccessStack.push( () -> determineValueMapping( leftOperand ) );
inferrableTypeAccessStack.push( () -> determineValueMapping( leftOperand, fromClauseIndex ) );
final Expression rhs = toSqlExpression( rightOperand.accept( this ) );
inferrableTypeAccessStack.pop();
@ -5435,7 +5557,8 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
}
private <X> X visitWithInferredType(SqmExpression<?> expression, SqmExpression<?> inferred) {
inferrableTypeAccessStack.push( () -> determineValueMapping( inferred ) );
final FromClauseIndex fromClauseIndex = fromClauseIndexStack.getCurrent();
inferrableTypeAccessStack.push( () -> determineValueMapping( inferred, fromClauseIndex ) );
try {
return (X) expression.accept( this );
}
@ -5762,7 +5885,8 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
@Override
public ComparisonPredicate visitComparisonPredicate(SqmComparisonPredicate predicate) {
inferrableTypeAccessStack.push( () -> determineValueMapping( predicate.getRightHandExpression() ) );
final FromClauseIndex fromClauseIndex = fromClauseIndexStack.getCurrent();
inferrableTypeAccessStack.push( () -> determineValueMapping( predicate.getRightHandExpression(), fromClauseIndex ) );
final Expression lhs;
try {
@ -5772,7 +5896,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
inferrableTypeAccessStack.pop();
}
inferrableTypeAccessStack.push( () -> determineValueMapping( predicate.getLeftHandExpression() ) );
inferrableTypeAccessStack.push( () -> determineValueMapping( predicate.getLeftHandExpression(), fromClauseIndex ) );
final Expression rhs;
try {
@ -5862,14 +5986,15 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
@Override
public BetweenPredicate visitBetweenPredicate(SqmBetweenPredicate predicate) {
final FromClauseIndex fromClauseIndex = fromClauseIndexStack.getCurrent();
final Expression expression;
final Expression lowerBound;
final Expression upperBound;
inferrableTypeAccessStack.push(
() -> coalesceSuppliedValues(
() -> determineValueMapping( predicate.getLowerBound() ),
() -> determineValueMapping( predicate.getUpperBound() )
() -> determineValueMapping( predicate.getLowerBound(), fromClauseIndex ),
() -> determineValueMapping( predicate.getUpperBound(), fromClauseIndex )
)
);
@ -5882,8 +6007,8 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
inferrableTypeAccessStack.push(
() -> coalesceSuppliedValues(
() -> determineValueMapping( predicate.getExpression() ),
() -> determineValueMapping( predicate.getUpperBound() )
() -> determineValueMapping( predicate.getExpression(), fromClauseIndex ),
() -> determineValueMapping( predicate.getUpperBound(), fromClauseIndex )
)
);
try {
@ -5895,8 +6020,8 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
inferrableTypeAccessStack.push(
() -> coalesceSuppliedValues(
() -> determineValueMapping( predicate.getExpression() ),
() -> determineValueMapping( predicate.getLowerBound() )
() -> determineValueMapping( predicate.getExpression(), fromClauseIndex ),
() -> determineValueMapping( predicate.getLowerBound(), fromClauseIndex )
)
);
try {
@ -5960,10 +6085,11 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
}
// otherwise - no special case...
final FromClauseIndex fromClauseIndex = fromClauseIndexStack.getCurrent();
inferrableTypeAccessStack.push(
() -> {
for ( SqmExpression<?> listExpression : predicate.getListExpressions() ) {
final MappingModelExpressible<?> mapping = determineValueMapping( listExpression );
final MappingModelExpressible<?> mapping = determineValueMapping( listExpression, fromClauseIndex );
if ( mapping != null ) {
return mapping;
}
@ -5985,7 +6111,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
getBooleanType()
);
inferrableTypeAccessStack.push( () -> determineValueMapping( predicate.getTestExpression() ) );
inferrableTypeAccessStack.push( () -> determineValueMapping( predicate.getTestExpression(), fromClauseIndex ) );
try {
for ( SqmExpression<?> expression : predicate.getListExpressions() ) {
@ -6052,8 +6178,9 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
return inListPredicate;
}
final FromClauseIndex fromClauseIndex = fromClauseIndexStack.getCurrent();
inferrableTypeAccessStack.push(
() -> determineValueMapping( sqmPredicate.getTestExpression() )
() -> determineValueMapping( sqmPredicate.getTestExpression(), fromClauseIndex )
);
try {

View File

@ -12,13 +12,13 @@ import java.util.List;
import java.util.function.Consumer;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.mapping.BasicEntityIdentifierMapping;
import org.hibernate.metamodel.mapping.BasicValuedModelPart;
import org.hibernate.metamodel.mapping.EntityAssociationMapping;
import org.hibernate.metamodel.mapping.EntityDiscriminatorMapping;
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.EntityValuedModelPart;
import org.hibernate.metamodel.mapping.MappingModelExpressible;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.SelectableConsumer;
import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping;
@ -46,22 +46,67 @@ public class EntityValuedPathInterpretation<T> extends AbstractSqmPathInterpreta
public static <T> EntityValuedPathInterpretation<T> from(
SqmEntityValuedSimplePath<T> sqmPath,
MappingModelExpressible<?> inferredMapping,
SqmToSqlAstConverter sqlAstCreationState) {
final TableGroup tableGroup = sqlAstCreationState
.getFromClauseAccess()
.findTableGroup( sqmPath.getLhs().getNavigablePath() );
final EntityValuedModelPart mapping = (EntityValuedModelPart) sqlAstCreationState
final EntityValuedModelPart pathMapping = (EntityValuedModelPart) sqlAstCreationState
.getFromClauseAccess()
.findTableGroup( sqmPath.getLhs().getNavigablePath() )
.getModelPart()
.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(
sqmPath.getNavigablePath(),
tableGroup,
resultModelPart,
mapping,
mapping,
false,
sqlAstCreationState
);
}
@ -69,15 +114,15 @@ public class EntityValuedPathInterpretation<T> extends AbstractSqmPathInterpreta
public static <T> EntityValuedPathInterpretation<T> from(
NavigablePath navigablePath,
TableGroup tableGroup,
ModelPart resultModelPart,
EntityValuedModelPart mapping,
EntityValuedModelPart treatedMapping,
boolean expandToAllColumns,
SqmToSqlAstConverter sqlAstCreationState) {
final SqlExpressionResolver sqlExprResolver = sqlAstCreationState.getSqlExpressionResolver();
final SessionFactoryImplementor sessionFactory = sqlAstCreationState.getCreationContext().getSessionFactory();
final Expression sqlExpression;
if ( expandToAllColumns ) {
if ( resultModelPart == null ) {
final EntityMappingType entityMappingType = mapping.getEntityMappingType();
final EntityIdentifierMapping identifierMapping = entityMappingType.getIdentifierMapping();
final EntityDiscriminatorMapping discriminatorMapping = entityMappingType.getDiscriminatorMapping();
@ -113,19 +158,9 @@ public class EntityValuedPathInterpretation<T> extends AbstractSqmPathInterpreta
entityMappingType.forEachSelectable( selectableConsumer );
sqlExpression = new SqlTuple( expressions, entityMappingType );
}
else if ( mapping instanceof EntityAssociationMapping ) {
final EntityAssociationMapping associationMapping = (EntityAssociationMapping) mapping;
final ModelPart keyTargetMatchPart = associationMapping.getKeyTargetMatchPart();
final ModelPart lhsPart;
if ( keyTargetMatchPart instanceof ToOneAttributeMapping ) {
lhsPart = ( (ToOneAttributeMapping) keyTargetMatchPart ).getKeyTargetMatchPart();
}
else {
lhsPart = keyTargetMatchPart;
}
if ( lhsPart instanceof BasicValuedModelPart ) {
final BasicValuedModelPart basicValuedModelPart = (BasicValuedModelPart) lhsPart;
else {
if ( resultModelPart instanceof BasicValuedModelPart ) {
final BasicValuedModelPart basicValuedModelPart = (BasicValuedModelPart) resultModelPart;
final TableReference tableReference = tableGroup.resolveTableReference(
navigablePath,
basicValuedModelPart.getContainingTableExpression()
@ -140,8 +175,8 @@ public class EntityValuedPathInterpretation<T> extends AbstractSqmPathInterpreta
);
}
else {
final List<Expression> expressions = new ArrayList<>( lhsPart.getJdbcTypeCount() );
lhsPart.forEachSelectable(
final List<Expression> expressions = new ArrayList<>( resultModelPart.getJdbcTypeCount() );
resultModelPart.forEachSelectable(
(selectionIndex, selectableMapping) -> {
final TableReference tableReference = tableGroup.resolveTableReference(
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<>(
sqlExpression,
navigablePath,

View File

@ -12,6 +12,7 @@ import java.util.function.Function;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.mapping.DiscriminatedAssociationModelPart;
import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart;
import org.hibernate.metamodel.mapping.EntityAssociationMapping;
import org.hibernate.metamodel.mapping.EntityValuedModelPart;
import org.hibernate.metamodel.mapping.MappingModelExpressible;
import org.hibernate.query.BindableType;
@ -51,7 +52,11 @@ public class SqmParameterInterpretation implements Expression, DomainResultProdu
this.queryParameter = queryParameter;
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();
}
else {

View File

@ -153,7 +153,8 @@ public abstract class AbstractSqmFrom<O,T> extends AbstractSqmPath<T> implements
SqmPath<?> resolvedPath = null;
ModelPartContainer modelPartContainer = null;
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() ) ) {
final SqmAttributeJoin<?, ?> attributeJoin = (SqmAttributeJoin<?, ?>) sqmJoin;
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) {
super(
correlationParent.getLhs(),
correlationParent.getNavigablePath(),
correlationParent.getAttribute(),
null,
correlationParent.getExplicitAlias(),
SqmJoinType.INNER,
false,
correlationParent.nodeBuilder()

View File

@ -23,8 +23,9 @@ public class SqmCorrelatedCrossJoin<T> extends SqmCrossJoin<T> implements SqmCor
public SqmCorrelatedCrossJoin(SqmCrossJoin<T> correlationParent) {
super(
correlationParent.getNavigablePath(),
correlationParent.getReferencedPathSource(),
null,
correlationParent.getExplicitAlias(),
correlationParent.getRoot()
);
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) {
super(
correlationParent.getNavigablePath(),
correlationParent.getReferencedPathSource(),
null,
correlationParent.getExplicitAlias(),
SqmJoinType.INNER,
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) {
super(
correlationParent.getLhs(),
correlationParent.getNavigablePath(),
correlationParent.getAttribute(),
null,
correlationParent.getExplicitAlias(),
SqmJoinType.INNER,
false,
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) {
super(
correlationParent.getLhs(),
correlationParent.getNavigablePath(),
correlationParent.getAttribute(),
null,
correlationParent.getExplicitAlias(),
SqmJoinType.INNER,
false,
correlationParent.nodeBuilder()

View File

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

View File

@ -22,7 +22,7 @@ public class SqmCorrelatedRoot<T> extends SqmRoot<T> implements SqmPathWrapper<T
super(
correlationParent.getNavigablePath(),
correlationParent.getReferencedPathSource(),
null,
correlationParent.getExplicitAlias(),
correlationParent.nodeBuilder()
);
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) {
super(
correlationParent.getLhs(),
correlationParent.getNavigablePath(),
correlationParent.getAttribute(),
null,
correlationParent.getExplicitAlias(),
SqmJoinType.INNER,
false,
correlationParent.nodeBuilder()

View File

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

View File

@ -24,51 +24,49 @@ import java.util.function.Consumer;
import java.util.function.Function;
import org.hibernate.LockMode;
import org.hibernate.LockOptions;
import org.hibernate.QueryException;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.RowLockStrategy;
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.spi.AbstractDelegatingWrapperOptions;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.internal.FilterJdbcParameter;
import org.hibernate.internal.util.MathHelper;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.internal.util.collections.Stack;
import org.hibernate.internal.util.collections.StandardStack;
import org.hibernate.metamodel.mapping.AttributeMapping;
import org.hibernate.metamodel.mapping.EntityAssociationMapping;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.ModelPartContainer;
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.metamodel.mapping.SqlExpressible;
import org.hibernate.persister.entity.AbstractEntityPersister;
import org.hibernate.persister.entity.EntityPersister;
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.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.SetOperator;
import org.hibernate.query.sqm.SortOrder;
import org.hibernate.query.sqm.UnaryArithmeticOperator;
import org.hibernate.query.spi.QueryOptions;
import org.hibernate.query.sqm.function.AbstractSqmSelfRenderingFunctionDescriptor;
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.sql.ast.Clause;
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.cte.CteColumn;
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.SearchClauseSpecification;
import org.hibernate.sql.ast.tree.delete.DeleteStatement;
@ -3608,9 +3608,12 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
clauseStack.push( Clause.FROM );
String separator = NO_SEPARATOR;
for ( TableGroup root : fromClause.getRoots() ) {
appendSql( separator );
renderRootTableGroup( root, null );
separator = COMA_SEPARATOR;
// Skip virtual table group roots which we use for simple correlations
if ( !( root instanceof VirtualTableGroup ) ) {
appendSql( separator );
renderRootTableGroup( root, null );
separator = COMA_SEPARATOR;
}
}
}
finally {

View File

@ -20,7 +20,12 @@ import org.hibernate.sql.ast.tree.from.TableGroup;
*/
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

View File

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

View File

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

View File

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

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")
public static class Order {
private String id;
@ -115,7 +124,7 @@ public class NonPkAssociationEqualityPredicateTest {
}
}
@Entity
@Entity(name = "Customer")
@Table(name = "CUSTOMER_TABLE")
public static class Customer {
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
protected void prepareTestData(SessionFactoryScope scope) {
scope.inTransaction( (session) -> {