@@ -1161,6 +1161,59 @@ void directImplicitAssociationUpdateThroughDeleteWithAlreadyLoadedAssociation_in
11611161 backendMock .verifyExpectationsMet ();
11621162 }
11631163
1164+ /**
1165+ * This test attempts to remove the entity after both sides of the association were set to null,
1166+ * and we expect that even after such steps we can identify what entities have to be re-indexed.
1167+ */
1168+ @ Test
1169+ @ TestForIssue (jiraKey = "HSEARCH-5170" )
1170+ void directAssociationSetNullThenRemoved_indexedEmbedded () {
1171+ assumeTrue (
1172+ !( isAssociationMultiValuedOnContainedSide () || isAssociationMultiValuedOnContainingSide () ),
1173+ "This test only makes sense if there's a one-to-one association."
1174+ );
1175+ PropertyAccessor <TContaining , TContained > containingAssociation = _containing ().containedIndexedEmbedded ();
1176+ PropertyAccessor <TContained , TContaining > containedAssociation = _contained ().containingAsIndexedEmbedded ();
1177+ PropertyAccessor <TContained , String > field = _contained ().indexedField ();
1178+
1179+ with ( sessionFactory ).runInTransaction ( session -> {
1180+ TIndexed entity1 = _indexed ().newInstance ( 1 );
1181+ TContained containedEntity = _contained ().newInstance ( 2 );
1182+ field .set ( containedEntity , "initialValue" );
1183+
1184+ session .persist ( entity1 );
1185+ session .persist ( containedEntity );
1186+
1187+ containingAssociation .set ( entity1 , containedEntity );
1188+ containedAssociation .set ( containedEntity , entity1 );
1189+
1190+ backendMock .expectWorks ( _indexed ().indexName () )
1191+ .add ( "1" , b -> b
1192+ .objectField ( "containedIndexedEmbedded" , b2 -> b2
1193+ .field ( "indexedField" , "initialValue" )
1194+ ) );
1195+ } );
1196+ backendMock .verifyExpectationsMet ();
1197+
1198+ with ( sessionFactory ).runInTransaction ( session -> {
1199+ TIndexed entity1 = session .get ( _indexed ().entityClass (), 1 );
1200+ // Make sure we initialize the association from indexed to contained;
1201+ // the magic is in the fact that Hibernate doesn't index the contained entity
1202+ // even though it's referenced by the Java representation of the association.
1203+ TContained containedEntity = containingAssociation .get ( entity1 );
1204+
1205+ // We update both sides of the association which would mean that
1206+ // the deleted entity has no information to which indexed entity it was associated to.
1207+ containingAssociation .set ( entity1 , null );
1208+ containedAssociation .set ( containedEntity , null );
1209+ session .remove ( containedEntity );
1210+
1211+ backendMock .expectWorks ( _indexed ().indexName () )
1212+ .addOrUpdate ( "1" , b -> {} );
1213+ } );
1214+ backendMock .verifyExpectationsMet ();
1215+ }
1216+
11641217 @ Test
11651218 @ TestForIssue (jiraKey = "HSEARCH-4708" )
11661219 void directEmbeddedAssociationReplace_embeddedAssociationsIndexedEmbedded () {
@@ -3775,6 +3828,66 @@ void indirectImplicitAssociationUpdateThroughDeleteWithAlreadyLoadedAssociation_
37753828 backendMock .verifyExpectationsMet ();
37763829 }
37773830
3831+ /**
3832+ * Same as {@link #directAssociationSetNullThenRemoved_indexedEmbedded()} ,
3833+ * but with an additional association: indexedEntity -> containingEntity -> containedEntity.
3834+ */
3835+ @ Test
3836+ @ TestForIssue (jiraKey = "HSEARCH-5170" )
3837+ void indirectAssociationSetNullThenRemoved_indexedEmbedded () {
3838+ assumeTrue (
3839+ !( isAssociationMultiValuedOnContainedSide () || isAssociationMultiValuedOnContainingSide () ),
3840+ "This test only makes sense if there's a one-to-one association."
3841+ );
3842+
3843+ PropertyAccessor <TContaining , TContained > containingAssociation = _containing ().containedIndexedEmbedded ();
3844+ PropertyAccessor <TContained , TContaining > containedAssociation = _contained ().containingAsIndexedEmbedded ();
3845+ PropertyAccessor <TContained , String > field = _contained ().indexedField ();
3846+
3847+ with ( sessionFactory ).runInTransaction ( session -> {
3848+ TIndexed entity1 = _indexed ().newInstance ( 1 );
3849+ TContaining containingEntity1 = _containing ().newInstance ( 2 );
3850+ _containing ().child ().set ( entity1 , containingEntity1 );
3851+ _containing ().parent ().set ( containingEntity1 , entity1 );
3852+ TContained containedEntity = _contained ().newInstance ( 2 );
3853+ field .set ( containedEntity , "initialValue" );
3854+
3855+ session .persist ( containingEntity1 );
3856+ session .persist ( entity1 );
3857+ session .persist ( containedEntity );
3858+
3859+ containingAssociation .set ( containingEntity1 , containedEntity );
3860+ containedAssociation .set ( containedEntity , containingEntity1 );
3861+
3862+ backendMock .expectWorks ( _indexed ().indexName () )
3863+ .add ( "1" , b -> b
3864+ .objectField ( "child" , b2 -> b2
3865+ .objectField ( "containedIndexedEmbedded" , b3 -> b3
3866+ .field ( "indexedField" , "initialValue" )
3867+ ) ) );
3868+ } );
3869+ backendMock .verifyExpectationsMet ();
3870+
3871+ with ( sessionFactory ).runInTransaction ( session -> {
3872+ TContaining containing = session .get ( _containing ().entityClass (), 2 );
3873+ // Make sure we initialize the association from containing to contained;
3874+ // the magic is in the fact that Hibernate doesn't index the contained entity
3875+ // even though it's referenced by the Java representation of the association.
3876+ TContained containedEntity = containingAssociation .get ( containing );
3877+
3878+ // Do *update* the association on both side and set them to null; that's on purpose.
3879+ containedAssociation .set ( containedEntity , null );
3880+ containingAssociation .set ( containing , null );
3881+
3882+ session .remove ( containedEntity );
3883+
3884+ backendMock .expectWorks ( _indexed ().indexName () )
3885+ .addOrUpdate ( "1" , b -> b
3886+ .objectField ( "child" , b2 -> {} ) );
3887+ } );
3888+ backendMock .verifyExpectationsMet ();
3889+ }
3890+
37783891 @ Test
37793892 void indirectValueUpdate_compose_singleValue_indexed () {
37803893 PropertyAccessor <TContaining , TContained > containingAssociation = _containing ().containedIndexedEmbedded ();
0 commit comments