Allow chained searches in Bundles where the fullUrl is fully qualified (#5529)

* Allow chained searches in Bundles where the fullUrl is fully qualified

* Add changelog

* Spotless
This commit is contained in:
James Agnew 2023-12-01 15:19:51 -05:00 committed by GitHub
parent ec5402abd4
commit d597cf2763
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 62 additions and 26 deletions

View File

@ -0,0 +1,4 @@
---
type: fix
issue: 5529
title: "When using a chained SearchParameter to search within a Bundle as [described here](https://smilecdr.com/docs/fhir_storage_relational/chained_searches_and_sorts.html#document-and-message-search-parameters), if the `Bundle.entry.fullUrl` was fully qualified but the reference was not, the search did not work. This has been corrected."

View File

@ -63,6 +63,7 @@ import org.hl7.fhir.instance.model.api.IBaseReference;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.r4.model.IdType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
@ -2010,17 +2011,31 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
* references within a Bundle
*/
if (theAppContext instanceof IBaseBundle && isNotBlank(theUrl) && !theUrl.startsWith("#")) {
String unqualifiedVersionlessReference;
boolean isPlaceholderReference;
if (theUrl.startsWith("urn:")) {
isPlaceholderReference = true;
unqualifiedVersionlessReference = null;
} else {
isPlaceholderReference = false;
unqualifiedVersionlessReference =
new IdType(theUrl).toUnqualifiedVersionless().getValue();
}
List<BundleEntryParts> entries = BundleUtil.toListOfEntries(getContext(), (IBaseBundle) theAppContext);
for (BundleEntryParts next : entries) {
if (next.getResource() != null) {
if (theUrl.startsWith("urn:uuid:")) {
if (isPlaceholderReference) {
if (theUrl.equals(next.getUrl())
|| theUrl.equals(
next.getResource().getIdElement().getValue())) {
return (T) next.getResource();
}
} else {
if (theUrl.equals(next.getResource().getIdElement().getValue())) {
if (unqualifiedVersionlessReference.equals(next.getResource()
.getIdElement()
.toUnqualifiedVersionless()
.getValue())) {
return (T) next.getResource();
}
}

View File

@ -5793,8 +5793,15 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
* [base]/Bundle?composition.patient.identifier=foo
*/
@ParameterizedTest
@CsvSource({"urn:uuid:5c34dc2c-9b5d-4ec1-b30b-3e2d4371508b", "Patient/ABC"})
public void testCreateAndSearchForFullyChainedSearchParameter(String thePatientId) {
@CsvSource({
"true , urn:uuid:5c34dc2c-9b5d-4ec1-b30b-3e2d4371508b , urn:uuid:5c34dc2c-9b5d-4ec1-b30b-3e2d4371508b",
"false, urn:uuid:5c34dc2c-9b5d-4ec1-b30b-3e2d4371508b , urn:uuid:5c34dc2c-9b5d-4ec1-b30b-3e2d4371508b",
"true , Patient/ABC , Patient/ABC ",
"false, Patient/ABC , Patient/ABC ",
"true , Patient/ABC , http://example.com/fhir/Patient/ABC ",
"false, Patient/ABC , http://example.com/fhir/Patient/ABC ",
})
public void testCreateAndSearchForFullyChainedSearchParameter(boolean theUseFullChainInName, String thePatientId, String theFullUrl) {
// Setup 1
myStorageSettings.setIndexMissingFields(JpaStorageSettings.IndexEnabledEnum.DISABLED);
@ -5819,13 +5826,18 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
composition.setSubject(new Reference(thePatientId));
Patient patient = new Patient();
patient.setId(new IdType(thePatientId));
patient.setId(new IdType(theFullUrl));
patient.addIdentifier().setSystem("http://foo").setValue("bar");
Bundle bundle = new Bundle();
bundle.setType(Bundle.BundleType.DOCUMENT);
bundle.addEntry().setResource(composition);
bundle.addEntry().setResource(patient);
bundle
.addEntry()
.setResource(composition);
bundle
.addEntry()
.setFullUrl(theFullUrl)
.setResource(patient);
myBundleDao.create(bundle, mySrd);
@ -5833,35 +5845,40 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
bundle2.setType(Bundle.BundleType.DOCUMENT);
myBundleDao.create(bundle2, mySrd);
// Verify 1
runInTransaction(() -> {
// Test
SearchParameterMap map;
if (theUseFullChainInName) {
map = SearchParameterMap.newSynchronous("composition.patient.identifier", new TokenParam("http://foo", "bar"));
} else {
map = SearchParameterMap.newSynchronous("composition", new ReferenceParam("patient.identifier", "http://foo|bar"));
}
IBundleProvider outcome = myBundleDao.search(map, mySrd);
// Verify
List<String> params = extractAllTokenIndexes();
assertThat(params.toString(), params, containsInAnyOrder(
"composition.patient.identifier http://foo|bar"
));
assertEquals(1, outcome.size());
}
private List<String> extractAllTokenIndexes() {
List<String> params = runInTransaction(() -> {
logAllTokenIndexes();
List<String> params = myResourceIndexedSearchParamTokenDao
return myResourceIndexedSearchParamTokenDao
.findAll()
.stream()
.filter(t -> t.getParamName().contains("."))
.map(t -> t.getParamName() + " " + t.getSystem() + "|" + t.getValue())
.toList();
assertThat(params.toString(), params, containsInAnyOrder(
"composition.patient.identifier http://foo|bar"
));
});
// Test 2
IBundleProvider outcome;
SearchParameterMap map = SearchParameterMap
.newSynchronous("composition.patient.identifier", new TokenParam("http://foo", "bar"));
outcome = myBundleDao.search(map, mySrd);
assertEquals(1, outcome.size());
map = SearchParameterMap
.newSynchronous("composition", new ReferenceParam("patient.identifier", "http://foo|bar"));
outcome = myBundleDao.search(map, mySrd);
assertEquals(1, outcome.size());
return params;
}
@Nested
public class TagBelowTests {