From 47262f5bd504f93e9bcfaf92ba834ce860097000 Mon Sep 17 00:00:00 2001 From: Ken Stevens Date: Sat, 25 Mar 2023 17:50:12 -0400 Subject: [PATCH] Ks 20230324 include prov (#4685) * fix test * fixed * changelog * add another test * improve docs * fix change log * fix test * fix test --------- Co-authored-by: Ken Stevens --- ...tient-everything-skips-other-patients.yaml | 5 ++ .../jpa/search/builder/SearchBuilder.java | 6 +- .../dstu3/PatientEverythingDstu3Test.java | 22 +++--- .../provider/r4/ResourceProviderR4Test.java | 69 +++++++++++++++++++ 4 files changed, 91 insertions(+), 11 deletions(-) create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_6_0/4685-patient-everything-skips-other-patients.yaml diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_6_0/4685-patient-everything-skips-other-patients.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_6_0/4685-patient-everything-skips-other-patients.yaml new file mode 100644 index 00000000000..c5081b08757 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_6_0/4685-patient-everything-skips-other-patients.yaml @@ -0,0 +1,5 @@ +--- +type: change +issue: 4685 +title: "When calling $everything on a Patient instance, the jpa server no longer traverses into other patients. +Previously it was possible to pull in data from another patient for example, via a Provenance resource." diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/SearchBuilder.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/SearchBuilder.java index d2be770574a..867b828ee15 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/SearchBuilder.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/SearchBuilder.java @@ -125,7 +125,6 @@ import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; -import static org.apache.commons.lang3.StringUtils.countMatches; import static org.apache.commons.lang3.StringUtils.defaultString; import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank; @@ -1173,6 +1172,11 @@ public class SearchBuilder implements ISearchBuilder { } else { wantResourceType = null; } + // When calling $everything on a Patient instance, we don't want to recurse into new Patient resources + // (e.g. via Provenance, List, or Group) when in an $everything operation + if (myParams != null && myParams.getEverythingMode() == SearchParameterMap.EverythingModeEnum.PATIENT_INSTANCE) { + sqlBuilder.append(" AND r.myTargetResourceType != 'Patient'"); + } String sql = sqlBuilder.toString(); List> partitions = partition(nextRoundMatches, getMaximumPageSize()); diff --git a/hapi-fhir-jpaserver-test-dstu3/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/PatientEverythingDstu3Test.java b/hapi-fhir-jpaserver-test-dstu3/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/PatientEverythingDstu3Test.java index f8d8bf143e8..4beabbcf33e 100644 --- a/hapi-fhir-jpaserver-test-dstu3/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/PatientEverythingDstu3Test.java +++ b/hapi-fhir-jpaserver-test-dstu3/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/PatientEverythingDstu3Test.java @@ -10,6 +10,7 @@ import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.hl7.fhir.dstu3.model.Bundle; import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent; +import org.hl7.fhir.dstu3.model.CarePlan; import org.hl7.fhir.dstu3.model.Encounter; import org.hl7.fhir.dstu3.model.Encounter.EncounterStatus; import org.hl7.fhir.dstu3.model.Observation; @@ -142,16 +143,17 @@ public class PatientEverythingDstu3Test extends BaseResourceProviderDstu3Test { @Test public void testEverythingHandlesCircularReferences() throws Exception { - Patient linkedPatient1 = new Patient(); - linkedPatient1.addLink().setOther(new Reference(myPatientId)); - String linkedPatient1Id = myClient.create().resource(linkedPatient1).execute().getId().toUnqualifiedVersionless().getValue(); + CarePlan cp1 = new CarePlan(); + cp1.setSubject(new Reference(myPatientId)); + String cp1Id = myClient.create().resource(cp1).execute().getId().toUnqualifiedVersionless().getValue(); - Patient linkedPatient2 = new Patient(); - linkedPatient2.addLink().setOther(new Reference(linkedPatient1Id)); - String linkedPatient2Id = myClient.create().resource(linkedPatient2).execute().getId().toUnqualifiedVersionless().getValue(); + CarePlan cp2 = new CarePlan(); + cp2.addBasedOn(new Reference(cp1Id)); + String cp2Id = myClient.create().resource(cp2).execute().getId().toUnqualifiedVersionless().getValue(); - myPatient.addLink().setOther(new Reference(linkedPatient2Id)); - myClient.update().resource(myPatient).execute(); + cp1.addBasedOn(new Reference(cp2Id)); + cp1.setId(cp1Id); + myClient.update().resource(cp1).execute(); Bundle bundle = fetchBundle(myServerBase + "/" + myPatientId + "/$everything?_format=json&_count=100", EncodingEnum.JSON); @@ -165,8 +167,8 @@ public class PatientEverythingDstu3Test extends BaseResourceProviderDstu3Test { ourLog.info("Found IDs: {}", actual); assertThat(actual, hasItem(myPatientId)); - assertThat(actual, hasItem(linkedPatient1Id)); - assertThat(actual, hasItem(linkedPatient2Id)); + assertThat(actual, hasItem(cp1Id)); + assertThat(actual, hasItem(cp2Id)); assertThat(actual, hasItem(encId1)); assertThat(actual, hasItem(encId2)); assertThat(actual, hasItem(myOrgId)); diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java index ae178b3ac25..ea04a400259 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java @@ -16,6 +16,7 @@ import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.term.ZipCollectionBuilder; import ca.uhn.fhir.jpa.test.config.TestR4Config; import ca.uhn.fhir.jpa.util.QueryParameterUtils; +import ca.uhn.fhir.model.api.Include; import ca.uhn.fhir.model.api.StorageResponseCodeEnum; import ca.uhn.fhir.model.api.TemporalPrecisionEnum; import ca.uhn.fhir.model.primitive.IdDt; @@ -138,6 +139,7 @@ import org.hl7.fhir.r4.model.Patient; import org.hl7.fhir.r4.model.Period; import org.hl7.fhir.r4.model.Practitioner; import org.hl7.fhir.r4.model.Procedure; +import org.hl7.fhir.r4.model.Provenance; import org.hl7.fhir.r4.model.Quantity; import org.hl7.fhir.r4.model.Questionnaire; import org.hl7.fhir.r4.model.Questionnaire.QuestionnaireItemType; @@ -3763,6 +3765,73 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { assertEquals(null, response.getLink("next")); } + + @Test + public void testEverythingDoesNotEnterSecondPatient() { + Patient goodPatient = new Patient(); + goodPatient.setActive(true); + String goodPid = myPatientDao.create(goodPatient, mySrd).getId().toUnqualifiedVersionless().getValue(); + + Patient badPatient = new Patient(); + badPatient.setActive(true); + String badPid = myPatientDao.create(badPatient, mySrd).getId().toUnqualifiedVersionless().getValue(); + + Observation o = new Observation(); + o.getSubject().setReference(goodPid); + o.addIdentifier().setSystem("foo").setValue("1"); + String oid = myObservationDao.create(o, mySrd).getId().toUnqualifiedVersionless().getValue(); + + Provenance prov = new Provenance(); + prov.addTarget().setReference(goodPid); + prov.addTarget().setReference(badPid); + String provid = myProvenanceDao.create(prov, mySrd).getId().toUnqualifiedVersionless().getValue(); + + Bundle response = myClient + .operation() + .onInstance(new IdType(goodPid)) + .named("everything") + .withNoParameters(Parameters.class) + .returnResourceType(Bundle.class) + .execute(); + + List ids = toUnqualifiedVersionlessIdValues(response); + // We should not pick up other resources via the provenance + assertThat(ids, containsInAnyOrder(goodPid, oid, provid)); + } + + @Test + public void testIncludeRecurseFromProvenanceDoesTraverse() { + Patient goodPatient = new Patient(); + goodPatient.setActive(true); + String goodPid = myPatientDao.create(goodPatient, mySrd).getId().toUnqualifiedVersionless().getValue(); + + Practitioner prac = new Practitioner(); + prac.addName().setFamily("FAM"); + String pracid = myPractitionerDao.create(prac, mySrd).getId().toUnqualifiedVersionless().getValue(); + + Patient otherPatient = new Patient(); + otherPatient.setActive(true); + otherPatient.addGeneralPractitioner().setReference(pracid); + String otherPid = myPatientDao.create(otherPatient, mySrd).getId().toUnqualifiedVersionless().getValue(); + + Provenance prov = new Provenance(); + prov.addTarget().setReference(goodPid); + prov.addTarget().setReference(otherPid); + String provid = myProvenanceDao.create(prov, mySrd).getId().toUnqualifiedVersionless().getValue(); + + Bundle response = myClient + .search() + .forResource("Provenance") + .where(Provenance.TARGET.hasId(goodPid)) + .include(new Include("*", true)) + .returnBundle(Bundle.class) + .execute(); + + List ids = toUnqualifiedVersionlessIdValues(response); + // We should not pick up other resources via the provenance + assertThat(ids, containsInAnyOrder(goodPid, otherPid, pracid, provid)); + } + @Test public void testParseAndEncodeExtensionWithValueWithExtension() throws IOException { String input = "\n" +