Skip to content

Commit e369015

Browse files
mbelladebeikov
authored andcommitted
HHH-17384 Fix @NotFound to-one association nullness handling
1 parent 3e96ddc commit e369015

File tree

11 files changed

+747
-57
lines changed

11 files changed

+747
-57
lines changed

hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/DerbyLegacySqlAstTranslator.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,11 @@ private boolean supportsOffsetFetchClause() {
263263
return getDialect().getVersion().isSameOrAfter( 10, 5 );
264264
}
265265

266+
@Override
267+
protected boolean supportsJoinInMutationStatementSubquery() {
268+
return false;
269+
}
270+
266271
@Override
267272
public void visitBinaryArithmeticExpression(BinaryArithmeticExpression arithmeticExpression) {
268273
final BinaryArithmeticOperator operator = arithmeticExpression.getOperator();

hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/H2LegacySqlAstTranslator.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,4 +302,9 @@ private boolean supportsOffsetFetchClausePercentWithTies() {
302302
// Introduction of PERCENT support https://github.com/h2database/h2database/commit/f45913302e5f6ad149155a73763c0c59d8205849
303303
return getDialect().getVersion().isSameOrAfter( 1, 4, 198 );
304304
}
305+
306+
@Override
307+
protected boolean supportsJoinInMutationStatementSubquery() {
308+
return false;
309+
}
305310
}

hibernate-core/src/main/java/org/hibernate/dialect/DerbySqlAstTranslator.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,11 @@ private boolean supportsOffsetFetchClause() {
261261
return true;
262262
}
263263

264+
@Override
265+
protected boolean supportsJoinInMutationStatementSubquery() {
266+
return false;
267+
}
268+
264269
@Override
265270
public void visitBinaryArithmeticExpression(BinaryArithmeticExpression arithmeticExpression) {
266271
final BinaryArithmeticOperator operator = arithmeticExpression.getOperator();

hibernate-core/src/main/java/org/hibernate/dialect/H2SqlAstTranslator.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,4 +307,9 @@ private boolean supportsOffsetFetchClausePercentWithTies() {
307307
// Introduction of PERCENT support https://github.com/h2database/h2database/commit/f45913302e5f6ad149155a73763c0c59d8205849
308308
return getDialect().getVersion().isSameOrAfter( 1, 4, 198 );
309309
}
310+
311+
@Override
312+
protected boolean supportsJoinInMutationStatementSubquery() {
313+
return false;
314+
}
310315
}

hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ToOneAttributeMapping.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1521,7 +1521,7 @@ public static class EntityB {
15211521
if ( sideNature == ForeignKeyDescriptor.Nature.KEY ) {
15221522
// If the key side is non-nullable we also need to add the keyResult
15231523
// to be able to manually check invalid foreign key references
1524-
if ( notFoundAction != null || !isInternalLoadNullable ) {
1524+
if ( hasNotFoundAction() || !isInternalLoadNullable ) {
15251525
keyResult = foreignKeyDescriptor.createKeyDomainResult(
15261526
fetchablePath,
15271527
tableGroup,

hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java

Lines changed: 82 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3530,10 +3530,14 @@ private TableGroup getPluralPartTableGroup(PluralTableGroup pluralTableGroup, Sq
35303530
}
35313531

35323532
private <X> X prepareReusablePath(SqmPath<?> sqmPath, Supplier<X> supplier) {
3533-
return prepareReusablePath( sqmPath, fromClauseIndexStack.getCurrent(), supplier );
3533+
return prepareReusablePath( sqmPath, fromClauseIndexStack.getCurrent(), supplier, false );
35343534
}
35353535

3536-
private <X> X prepareReusablePath(SqmPath<?> sqmPath, FromClauseIndex fromClauseIndex, Supplier<X> supplier) {
3536+
private <X> X prepareReusablePath(
3537+
SqmPath<?> sqmPath,
3538+
FromClauseIndex fromClauseIndex,
3539+
Supplier<X> supplier,
3540+
boolean allowLeftJoins) {
35373541
final Consumer<TableGroup> implicitJoinChecker;
35383542
if ( getCurrentClauseStack().getCurrent() != Clause.SET_EXPRESSION ) {
35393543
implicitJoinChecker = tg -> {};
@@ -3556,7 +3560,8 @@ private <X> X prepareReusablePath(SqmPath<?> sqmPath, FromClauseIndex fromClause
35563560
fromClauseIndex.getTableGroup( sqmPath.getLhs().getNavigablePath() ),
35573561
sqmPath
35583562
),
3559-
sqmPath
3563+
sqmPath,
3564+
allowLeftJoins
35603565
);
35613566
if ( createdTableGroup != null ) {
35623567
if ( sqmPath instanceof SqmTreatedPath<?, ?> ) {
@@ -3610,7 +3615,7 @@ else if ( createdParentTableGroup instanceof PluralTableGroup ) {
36103615
}
36113616
else {
36123617
newTableGroup = getActualTableGroup(
3613-
createTableGroup( createdParentTableGroup, parentPath ),
3618+
createTableGroup( createdParentTableGroup, parentPath, false ),
36143619
sqmPath
36153620
);
36163621
}
@@ -3624,9 +3629,21 @@ else if ( sqmPath instanceof SqmTreatedPath<?, ?> ) {
36243629
fromClauseIndex.register( sqmPath, parentTableGroup );
36253630
}
36263631

3627-
if ( parentPath instanceof SqmSimplePath<?>
3632+
upgradeToInnerJoinIfNeeded( parentTableGroup, sqmPath, parentPath, fromClauseIndex );
3633+
3634+
registerPathAttributeEntityNameUsage( sqmPath, parentTableGroup );
3635+
3636+
return parentTableGroup;
3637+
}
3638+
3639+
private void upgradeToInnerJoinIfNeeded(
3640+
TableGroup parentTableGroup,
3641+
SqmPath<?> sqmPath,
3642+
SqmPath<?> parentPath,
3643+
FromClauseIndex fromClauseIndex) {
3644+
if ( getCurrentClauseStack().getCurrent() != Clause.SELECT
3645+
&& parentPath instanceof SqmSimplePath<?>
36283646
&& CollectionPart.Nature.fromName( parentPath.getNavigablePath().getLocalName() ) == null
3629-
&& getCurrentClauseStack().getCurrent() != Clause.SELECT
36303647
&& parentPath.getParentPath() != null
36313648
&& parentTableGroup.getModelPart() instanceof ToOneAttributeMapping ) {
36323649
// we need to handle the case of an implicit path involving a to-one
@@ -3650,9 +3667,6 @@ && getCurrentClauseStack().getCurrent() != Clause.SELECT
36503667
}
36513668
}
36523669
}
3653-
registerPathAttributeEntityNameUsage( sqmPath, parentTableGroup );
3654-
3655-
return parentTableGroup;
36563670
}
36573671

36583672
private void prepareForSelection(SqmPath<?> selectionPath) {
@@ -3678,7 +3692,8 @@ private void prepareForSelection(SqmPath<?> selectionPath) {
36783692
// But only create it for paths that are not handled by #prepareReusablePath anyway
36793693
final TableGroup createdTableGroup = createTableGroup(
36803694
getActualTableGroup( fromClauseIndex.getTableGroup( path.getLhs().getNavigablePath() ), path ),
3681-
path
3695+
path,
3696+
false
36823697
);
36833698
if ( createdTableGroup != null ) {
36843699
registerEntityNameProjectionUsage( path, createdTableGroup );
@@ -3699,7 +3714,7 @@ private void prepareForSelection(SqmPath<?> selectionPath) {
36993714
}
37003715
}
37013716

3702-
private TableGroup createTableGroup(TableGroup parentTableGroup, SqmPath<?> joinedPath) {
3717+
private TableGroup createTableGroup(TableGroup parentTableGroup, SqmPath<?> joinedPath, boolean allowLeftJoins) {
37033718
final SqmPath<?> lhsPath = joinedPath.getLhs();
37043719
final FromClauseIndex fromClauseIndex = getFromClauseIndex();
37053720
final ModelPart subPart = parentTableGroup.getModelPart().findSubPart(
@@ -3731,18 +3746,30 @@ private TableGroup createTableGroup(TableGroup parentTableGroup, SqmPath<?> join
37313746
querySpec.getFromClause().addRoot( tableGroup );
37323747
}
37333748
else {
3734-
// Check if we can reuse a table group join of the parent
3735-
final TableGroup compatibleTableGroup = parentTableGroup.findCompatibleJoinedGroup(
3736-
joinProducer,
3737-
SqlAstJoinType.INNER
3738-
);
3749+
final TableGroupJoin compatibleLeftJoin;
3750+
final SqlAstJoinType sqlAstJoinType;
3751+
if ( isMappedByOrNotFoundToOne( joinProducer ) ) {
3752+
compatibleLeftJoin = parentTableGroup.findCompatibleJoin(
3753+
joinProducer,
3754+
SqlAstJoinType.LEFT
3755+
);
3756+
sqlAstJoinType = SqlAstJoinType.LEFT;
3757+
}
3758+
else {
3759+
compatibleLeftJoin = null;
3760+
sqlAstJoinType = null;
3761+
}
3762+
3763+
final TableGroup compatibleTableGroup = compatibleLeftJoin != null ?
3764+
compatibleLeftJoin.getJoinedGroup() :
3765+
parentTableGroup.findCompatibleJoinedGroup( joinProducer, SqlAstJoinType.INNER );
37393766
if ( compatibleTableGroup == null ) {
37403767
final TableGroupJoin tableGroupJoin = joinProducer.createTableGroupJoin(
37413768
joinedPath.getNavigablePath(),
37423769
parentTableGroup,
37433770
null,
37443771
null,
3745-
null,
3772+
allowLeftJoins ? sqlAstJoinType : null,
37463773
false,
37473774
false,
37483775
this
@@ -3762,6 +3789,10 @@ private TableGroup createTableGroup(TableGroup parentTableGroup, SqmPath<?> join
37623789
// Also register the table group under its original navigable path, which possibly contains an alias
37633790
// This is important, as otherwise we might create new joins in subqueries which are unnecessary
37643791
fromClauseIndex.registerTableGroup( tableGroup.getNavigablePath(), tableGroup );
3792+
// Upgrade the join type to inner if the context doesn't allow left joins
3793+
if ( compatibleLeftJoin != null && !allowLeftJoins ) {
3794+
compatibleLeftJoin.setJoinType( SqlAstJoinType.INNER );
3795+
}
37653796
}
37663797
}
37673798

@@ -3774,6 +3805,16 @@ private TableGroup createTableGroup(TableGroup parentTableGroup, SqmPath<?> join
37743805
return tableGroup;
37753806
}
37763807

3808+
private boolean isMappedByOrNotFoundToOne(TableGroupJoinProducer joinProducer) {
3809+
if ( joinProducer instanceof ToOneAttributeMapping ) {
3810+
final ToOneAttributeMapping toOne = (ToOneAttributeMapping) joinProducer;
3811+
if ( toOne.hasNotFoundAction() || toOne.getReferencedPropertyName() != null ) {
3812+
return true;
3813+
}
3814+
}
3815+
return false;
3816+
}
3817+
37773818
private boolean isRecursiveCte(TableGroup tableGroup) {
37783819
if ( tableGroup instanceof CteTableGroup ) {
37793820
final CteTableGroup cteTableGroup = (CteTableGroup) tableGroup;
@@ -7530,11 +7571,30 @@ public LikePredicate visitLikePredicate(SqmLikePredicate predicate) {
75307571

75317572
@Override
75327573
public NullnessPredicate visitIsNullPredicate(SqmNullnessPredicate predicate) {
7533-
return new NullnessPredicate(
7534-
(Expression) visitWithInferredType( predicate.getExpression(), () -> basicType( Object.class )),
7535-
predicate.isNegated(),
7536-
getBooleanType()
7537-
);
7574+
final SqmExpression<?> sqmExpression = predicate.getExpression();
7575+
final Expression expression;
7576+
if ( sqmExpression instanceof SqmEntityValuedSimplePath<?> ) {
7577+
final SqmEntityValuedSimplePath<?> entityValuedPath = (SqmEntityValuedSimplePath<?>) sqmExpression;
7578+
inferrableTypeAccessStack.push( () -> basicType( Object.class ) );
7579+
expression = withTreatRestriction( prepareReusablePath(
7580+
entityValuedPath,
7581+
fromClauseIndexStack.getCurrent(),
7582+
() -> EntityValuedPathInterpretation.from(
7583+
entityValuedPath,
7584+
getInferredValueMapping(),
7585+
this
7586+
),
7587+
true
7588+
), entityValuedPath );
7589+
inferrableTypeAccessStack.pop();
7590+
}
7591+
else {
7592+
expression = (Expression) visitWithInferredType(
7593+
predicate.getExpression(),
7594+
() -> basicType( Object.class )
7595+
);
7596+
}
7597+
return new NullnessPredicate( expression, predicate.isNegated(), getBooleanType() );
75387598
}
75397599

75407600
@Override

hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/EntityValuedPathInterpretation.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ && hasNotFound( mapping )
217217
}
218218
else {
219219
// If the mapping is an inverse association, use the PK and disallow FK optimizations
220-
resultModelPart = ( (EntityAssociationMapping) mapping ).getAssociatedEntityMappingType().getIdentifierMapping();
220+
resultModelPart = associationMapping.getAssociatedEntityMappingType().getIdentifierMapping();
221221
resultTableGroup = tableGroup;
222222

223223
// todo (not-found) : in the case of not-found=ignore, we want to do the join, however -

0 commit comments

Comments
 (0)