Skip to content

Commit 63b8dbc

Browse files
authored
enforce_referential_integrity_on_delete.disable_for_paths ignored for expunge (#7142)
* [7139] fix delete expunge with disableForPaths being set * [7139] update changelog
1 parent ab49be5 commit 63b8dbc

File tree

4 files changed

+86
-19
lines changed

4 files changed

+86
-19
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
type: fix
3+
issue: 7139
4+
jira: SMILE-9692
5+
title: "This change allows resources to be deleted using the Delete with Expunge operation when the property
6+
`enforceReferentialIntegrityOnDeleteDisableForPaths` is set to one or more FHIRPath expressions that link other
7+
resources to the target resources."

hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/delete/batch2/DeleteExpungeSqlBuilder.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,12 @@ public void validateOkToDeleteAndExpunge(Set<JpaPid> thePids, boolean theCascade
132132
break;
133133
}
134134
}
135+
} else {
136+
// check if the user has configured any paths to ignore
137+
Set<String> pathsToIgnore = myStorageSettings.getEnforceReferentialIntegrityOnDeleteDisableForPaths();
138+
if (conflictResourceLinks.stream().anyMatch(link -> pathsToIgnore.contains(link.getSourcePath()))) {
139+
return;
140+
}
135141
}
136142

137143
ResourceLink firstConflict = conflictResourceLinks.get(0);

hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/batch2/DeleteExpungeSqlBuilderTest.java

Lines changed: 45 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,41 +7,42 @@
77
import ca.uhn.fhir.jpa.delete.batch2.DeleteExpungeSqlBuilder;
88
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
99
import ca.uhn.fhir.jpa.model.dao.JpaPid;
10+
import ca.uhn.fhir.jpa.model.entity.ResourceLink;
11+
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
1012
import ca.uhn.fhir.jpa.util.QueryChunker;
11-
import org.junit.jupiter.api.BeforeEach;
1213
import org.junit.jupiter.api.Test;
14+
import org.junit.jupiter.api.extension.ExtendWith;
15+
import org.mockito.InjectMocks;
1316
import org.mockito.Mock;
1417
import org.mockito.MockedStatic;
1518
import org.mockito.Mockito;
16-
import org.springframework.beans.factory.annotation.Autowired;
19+
import org.mockito.junit.jupiter.MockitoExtension;
1720

1821
import java.util.List;
22+
import java.util.Set;
1923

24+
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
2025
import static org.mockito.ArgumentMatchers.any;
2126
import static org.mockito.ArgumentMatchers.anyCollection;
27+
import static org.mockito.ArgumentMatchers.anyList;
2228
import static org.mockito.Mockito.times;
29+
import static org.mockito.Mockito.when;
2330

24-
public class DeleteExpungeSqlBuilderTest {
25-
@Autowired
26-
private ResourceTableFKProvider theResourceTableFKProvider;
27-
private JpaStorageSettings myStorageSettings = new JpaStorageSettings();
31+
@ExtendWith(MockitoExtension.class)
32+
class DeleteExpungeSqlBuilderTest {
33+
@Mock
34+
private ResourceTableFKProvider myResourceTableFKProvider;
35+
@Mock
36+
private JpaStorageSettings myStorageSettings;
37+
@Mock
2838
private IIdHelperService<JpaPid> myIdHelper;
2939
@Mock
30-
private IResourceLinkDao theResourceLinkDao;
31-
private PartitionSettings thePartitionSettings = new PartitionSettings();
40+
private IResourceLinkDao myResourceLinkDao;
41+
@Mock
42+
private PartitionSettings myPartitionSettings;
43+
@InjectMocks
3244
private DeleteExpungeSqlBuilder myDeleteExpungeSqlBuilderTest;
3345

34-
@BeforeEach
35-
public void beforeEach() {
36-
myDeleteExpungeSqlBuilderTest = new DeleteExpungeSqlBuilder(
37-
theResourceTableFKProvider,
38-
myStorageSettings,
39-
myIdHelper,
40-
theResourceLinkDao,
41-
thePartitionSettings
42-
);
43-
}
44-
4546
@Test
4647
public void testQueryChunkingWhenFindingChildResources(){
4748
//Given: We have a list of pids.
@@ -57,4 +58,29 @@ public void testQueryChunkingWhenFindingChildResources(){
5758
queryChunker.verify(() -> QueryChunker.chunk(anyCollection(), any()), times(1));
5859
}
5960
}
61+
62+
@Test
63+
void testValidateOkToDeleteAndExpunge_whenEnforceReferentialIntegrityDisableForPaths_noException() {
64+
// Set up
65+
String path = "Encounter.subject";
66+
when(myStorageSettings.getEnforceReferentialIntegrityOnDeleteDisableForPaths()).thenReturn(Set.of(path));
67+
when(myStorageSettings.isEnforceReferentialIntegrityOnDelete()).thenReturn(true);
68+
69+
ResourceTable sourceRes = new ResourceTable();
70+
sourceRes.setId(JpaPid.fromIdAndResourceType(20L, "Encounter"));
71+
72+
ResourceLink resLink = new ResourceLink();
73+
resLink.setSourceResource(sourceRes);
74+
resLink.setSourcePath(path);
75+
76+
List<ResourceLink> conflictResourceLinks = List.of(resLink);
77+
when(myResourceLinkDao.findWithTargetPidIn(anyList())).thenReturn(conflictResourceLinks);
78+
79+
Set<JpaPid> pids = Set.of(JpaPid.fromIdAndResourceType(10L, "Patient"));
80+
81+
// Execute & Verify
82+
assertDoesNotThrow(() -> {
83+
myDeleteExpungeSqlBuilderTest.validateOkToDeleteAndExpunge(pids, false, null);
84+
});
85+
}
6086
}

hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/api/config/JpaStorageSettings.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ public class JpaStorageSettings extends StorageSettings {
165165

166166
private boolean myDeleteStaleSearches = true;
167167
private boolean myEnforceReferentialIntegrityOnDelete = true;
168+
private Set<String> myEnforceReferentialIntegrityOnDeleteDisableForPaths = Collections.emptySet();
168169
private boolean myUniqueIndexesEnabled = true;
169170
private boolean myUniqueIndexesCheckedBeforeSave = true;
170171
private boolean myEnforceReferentialIntegrityOnWrite = true;
@@ -1423,6 +1424,33 @@ public void setEnforceReferentialIntegrityOnDelete(boolean theEnforceReferential
14231424
myEnforceReferentialIntegrityOnDelete = theEnforceReferentialIntegrityOnDelete;
14241425
}
14251426

1427+
/**
1428+
* When {@link #setEnforceReferentialIntegrityOnDelete(boolean)} is set to <code>true</code>, this setting may
1429+
* be used to selectively disable the referential integrity checking only for specific paths. It applies to
1430+
* both Delete and Delete with Expunge operations.
1431+
* <p>
1432+
* For example, if the property contains the FHIR path expression <code>Encounter.subject</code> , deleting
1433+
* the Patient referenced by an Encounter's subject is allowed without deleting the Encounter first.
1434+
* </p>
1435+
*/
1436+
public Set<String> getEnforceReferentialIntegrityOnDeleteDisableForPaths() {
1437+
return myEnforceReferentialIntegrityOnDeleteDisableForPaths;
1438+
}
1439+
1440+
/**
1441+
* When {@link #setEnforceReferentialIntegrityOnDelete(boolean)} is set to <code>true</code>, this setting
1442+
* allows you to selectively disable integrity checks for specific paths. It applies to both Delete and
1443+
* Delete with Expunge operations.
1444+
* <p>
1445+
* For example, if the property contains the FHIR path expression <code>Encounter.subject</code> , deleting
1446+
* the Patient referenced by an Encounter's subject is allowed without deleting the Encounter first.
1447+
* </p>
1448+
*/
1449+
public void setEnforceReferentialIntegrityOnDeleteDisableForPaths(
1450+
Set<String> theEnforceReferentialIntegrityOnDeleteDisableForPaths) {
1451+
myEnforceReferentialIntegrityOnDeleteDisableForPaths = theEnforceReferentialIntegrityOnDeleteDisableForPaths;
1452+
}
1453+
14261454
/**
14271455
* If set to <code>false</code> (default is <code>true</code>) resources will be permitted to be
14281456
* created or updated even if they contain references to local resources that do not exist.

0 commit comments

Comments
 (0)