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:
parent
075cc8d108
commit
335ed19821
|
@ -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() );
|
||||
}
|
||||
;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 ) {
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 ) {
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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()
|
||||
);
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() );
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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) -> {
|
||||
|
|
Loading…
Reference in New Issue