From e65c2649277916f1fa25068ff5620b4ae4c45ab4 Mon Sep 17 00:00:00 2001 From: James Agnew Date: Tue, 23 Jun 2020 17:58:47 -0400 Subject: [PATCH] Support double _has expressions (#1939) * Support nested _has queries * Add changelog --- .../5_1_0/1939-double-has-expressions.yaml | 5 ++ .../predicate/PredicateBuilderReference.java | 59 ++++++++++++------- .../r4/FhirResourceDaoR4SearchNoFtTest.java | 50 ++++++++++++++++ 3 files changed, 93 insertions(+), 21 deletions(-) create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_1_0/1939-double-has-expressions.yaml diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_1_0/1939-double-has-expressions.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_1_0/1939-double-has-expressions.yaml new file mode 100644 index 00000000000..d669397291a --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_1_0/1939-double-has-expressions.yaml @@ -0,0 +1,5 @@ +--- +type: add +issue: 1939 +title: "The JPA server is now able to support _has queries where the linked search expression on the right hand side of the + _has parameter is a second _has query." diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderReference.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderReference.java index e6236013476..3703a360fa4 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderReference.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderReference.java @@ -62,6 +62,7 @@ import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId; import ca.uhn.fhir.rest.param.CompositeParam; import ca.uhn.fhir.rest.param.DateParam; +import ca.uhn.fhir.rest.param.HasOrListParam; import ca.uhn.fhir.rest.param.HasParam; import ca.uhn.fhir.rest.param.NumberParam; import ca.uhn.fhir.rest.param.ParameterUtil; @@ -944,30 +945,46 @@ class PredicateBuilderReference extends BasePredicateBuilder { throw new InvalidRequestException("Invalid resource type: " + targetResourceType); } - //Ensure that the name of the search param - // (e.g. the `code` in Patient?_has:Observation:subject:code=sys|val) - // exists on the target resource type. - RuntimeSearchParam owningParameterDef = mySearchParamRegistry.getSearchParamByName(targetResourceDefinition, paramName); - if (owningParameterDef == null) { - throw new InvalidRequestException("Unknown parameter name: " + targetResourceType + ':' + parameterName); - } - - //Ensure that the name of the back-referenced search param on the target (e.g. the `subject` in Patient?_has:Observation:subject:code=sys|val) - //exists on the target resource. - owningParameterDef = mySearchParamRegistry.getSearchParamByName(targetResourceDefinition, paramReference); - if (owningParameterDef == null) { - throw new InvalidRequestException("Unknown parameter name: " + targetResourceType + ':' + paramReference); - } - - RuntimeSearchParam paramDef = mySearchParamRegistry.getSearchParamByName(targetResourceDefinition, paramName); - - IQueryParameterAnd> parsedParam = (IQueryParameterAnd>) ParameterUtil.parseQueryParams(myContext, paramDef, paramName, parameters); - ArrayList orValues = Lists.newArrayList(); - for (IQueryParameterOr next : parsedParam.getValuesAsQueryTokens()) { - orValues.addAll(next.getValuesAsQueryTokens()); + if (paramName.startsWith("_has:")) { + + ourLog.trace("Handing double _has query: {}", paramName); + + String qualifier = paramName.substring(4); + paramName = Constants.PARAM_HAS; + for (IQueryParameterType next : nextOrList) { + HasParam nextHasParam = new HasParam(); + nextHasParam.setValueAsQueryToken(myContext, Constants.PARAM_HAS, qualifier, next.getValueAsQueryToken(myContext)); + orValues.add(nextHasParam); + } + + } else { + + //Ensure that the name of the search param + // (e.g. the `code` in Patient?_has:Observation:subject:code=sys|val) + // exists on the target resource type. + RuntimeSearchParam owningParameterDef = mySearchParamRegistry.getSearchParamByName(targetResourceDefinition, paramName); + if (owningParameterDef == null) { + throw new InvalidRequestException("Unknown parameter name: " + targetResourceType + ':' + parameterName); + } + + //Ensure that the name of the back-referenced search param on the target (e.g. the `subject` in Patient?_has:Observation:subject:code=sys|val) + //exists on the target resource. + owningParameterDef = mySearchParamRegistry.getSearchParamByName(targetResourceDefinition, paramReference); + if (owningParameterDef == null) { + throw new InvalidRequestException("Unknown parameter name: " + targetResourceType + ':' + paramReference); + } + + RuntimeSearchParam paramDef = mySearchParamRegistry.getSearchParamByName(targetResourceDefinition, paramName); + IQueryParameterAnd> parsedParam = (IQueryParameterAnd>) ParameterUtil.parseQueryParams(myContext, paramDef, paramName, parameters); + + for (IQueryParameterOr next : parsedParam.getValuesAsQueryTokens()) { + orValues.addAll(next.getValuesAsQueryTokens()); + } + } + //Handle internal chain inside the has. if (parameterName.contains(".")) { String chainedPartOfParameter = getChainedPart(parameterName); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoFtTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoFtTest.java index e35bc418ff5..d17a9b91613 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoFtTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoFtTest.java @@ -876,6 +876,11 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test { Observation obs = new Observation(); obs.addIdentifier().setSystem("urn:system").setValue("NOLINK"); obs.setDevice(new Reference(devId)); + IIdType obsId = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); + + DiagnosticReport dr = new DiagnosticReport(); + dr.addResult().setReference(obsId.getValue()); + dr.setStatus(DiagnosticReport.DiagnosticReportStatus.FINAL); myObservationDao.create(obs, mySrd); } @@ -896,6 +901,51 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test { assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(params)), empty()); } + @Test + public void testHasParameterDouble() { + // Matching + IIdType pid0; + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("00"); + patient.addName().setFamily("Tester").addGiven("Joe"); + pid0 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + + Observation obs = new Observation(); + obs.addIdentifier().setSystem("urn:system").setValue("NOLINK"); + obs.setSubject(new Reference(pid0)); + IIdType obsId = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); + + DiagnosticReport dr = new DiagnosticReport(); + dr.addResult().setReference(obsId.getValue()); + dr.setStatus(DiagnosticReport.DiagnosticReportStatus.FINAL); + myDiagnosticReportDao.create(dr, mySrd); + } + + // Matching + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("001"); + patient.addName().setFamily("Tester").addGiven("Joe"); + IIdType pid1 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + + Observation obs = new Observation(); + obs.addIdentifier().setSystem("urn:system").setValue("NOLINK"); + obs.setSubject(new Reference(pid1)); + myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); + + } + + SearchParameterMap params = SearchParameterMap.newSynchronous(); + + // Double _has + params = new SearchParameterMap(); + params.add("_has", new HasParam("Observation", "subject", "_has:DiagnosticReport:result:status", "final")); + assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(params)), containsInAnyOrder(pid0.getValue())); + + } + + @Test public void testHasParameterChained() { IIdType pid0;