Has param no in (#1232)

This commit is contained in:
Ken Stevens 2019-03-10 20:50:24 -04:00 committed by GitHub
parent 94021989b0
commit dcfacfea49
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 265 additions and 80 deletions

View File

@ -34,7 +34,7 @@ public class HasParam extends BaseParam implements IQueryParameterType {
private static final long serialVersionUID = 1L;
private String myOwningFieldName;
private String myReferenceFieldName;
private String myParameterName;
private String myParameterValue;
private String myTargetResourceType;
@ -44,10 +44,10 @@ public class HasParam extends BaseParam implements IQueryParameterType {
}
public HasParam(String theTargetResourceType, String theOwningFieldName, String theParameterName, String theParameterValue) {
public HasParam(String theTargetResourceType, String theReferenceFieldName, String theParameterName, String theParameterValue) {
this();
myTargetResourceType = theTargetResourceType;
myOwningFieldName = theOwningFieldName;
myReferenceFieldName = theReferenceFieldName;
myParameterName = theParameterName;
myParameterValue = theParameterValue;
}
@ -75,13 +75,13 @@ public class HasParam extends BaseParam implements IQueryParameterType {
validateColon(qualifier, colonIndex1);
myTargetResourceType = qualifier.substring(1, colonIndex0);
myOwningFieldName = qualifier.substring(colonIndex0 + 1, colonIndex1);
myReferenceFieldName = qualifier.substring(colonIndex0 + 1, colonIndex1);
myParameterName = qualifier.substring(colonIndex1 + 1);
myParameterValue = theValue;
}
public String getOwningFieldName() {
return myOwningFieldName;
public String getReferenceFieldName() {
return myReferenceFieldName;
}
public String getParameterName() {

View File

@ -51,10 +51,7 @@ import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.model.valueset.BundleEntrySearchModeEnum;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
import ca.uhn.fhir.rest.api.SortOrderEnum;
import ca.uhn.fhir.rest.api.SortSpec;
import ca.uhn.fhir.rest.api.*;
import ca.uhn.fhir.rest.param.*;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
@ -219,26 +216,25 @@ public class SearchBuilder implements ISearchBuilder {
for (List<? extends IQueryParameterType> nextOrList : theHasParameters) {
StringBuilder valueBuilder = new StringBuilder();
String targetResourceType = null;
String owningParameter = null;
String paramReference = null;
String parameterName = null;
String paramName = null;
List<QualifiedParamList> parameters = new ArrayList<>();
for (IQueryParameterType nextParam : nextOrList) {
HasParam next = (HasParam) nextParam;
if (valueBuilder.length() > 0) {
valueBuilder.append(',');
}
valueBuilder.append(UrlUtil.escapeUrlParam(next.getValueAsQueryToken(myContext)));
targetResourceType = next.getTargetResourceType();
owningParameter = next.getOwningFieldName();
paramReference = next.getReferenceFieldName();
parameterName = next.getParameterName();
paramName = parameterName.replaceAll("\\..*", "");
parameters.add(QualifiedParamList.singleton(paramName, next.getValueAsQueryToken(myContext)));
}
if (valueBuilder.length() == 0) {
if (paramName == null) {
continue;
}
String matchUrl = targetResourceType + '?' + UrlUtil.escapeUrlParam(parameterName) + '=' + valueBuilder.toString();
RuntimeResourceDefinition targetResourceDefinition;
try {
targetResourceDefinition = myContext.getResourceDefinition(targetResourceType);
@ -247,28 +243,33 @@ public class SearchBuilder implements ISearchBuilder {
}
assert parameterName != null;
String paramName = parameterName.replaceAll("\\..*", "");
RuntimeSearchParam owningParameterDef = mySearchParamRegistry.getSearchParamByName(targetResourceDefinition, paramName);
if (owningParameterDef == null) {
throw new InvalidRequestException("Unknown parameter name: " + targetResourceType + ':' + parameterName);
}
owningParameterDef = mySearchParamRegistry.getSearchParamByName(targetResourceDefinition, owningParameter);
owningParameterDef = mySearchParamRegistry.getSearchParamByName(targetResourceDefinition, paramReference);
if (owningParameterDef == null) {
throw new InvalidRequestException("Unknown parameter name: " + targetResourceType + ':' + owningParameter);
throw new InvalidRequestException("Unknown parameter name: " + targetResourceType + ':' + paramReference);
}
Class<? extends IBaseResource> resourceType = targetResourceDefinition.getImplementingClass();
Set<Long> match = myMatchResourceUrlService.processMatchUrl(matchUrl, resourceType);
if (match.isEmpty()) {
// Pick a PID that can never match
match = Collections.singleton(-1L);
RuntimeSearchParam paramDef = mySearchParamRegistry.getSearchParamByName(targetResourceDefinition, paramName);
IQueryParameterAnd<IQueryParameterOr<IQueryParameterType>> parsedParam = (IQueryParameterAnd<IQueryParameterOr<IQueryParameterType>>) ParameterUtil.parseQueryParams(myContext, paramDef, paramName, parameters);
ArrayList<IQueryParameterType> orValues = Lists.newArrayList();
for (IQueryParameterOr<IQueryParameterType> next : parsedParam.getValuesAsQueryTokens()) {
orValues.addAll(next.getValuesAsQueryTokens());
}
Subquery<Long> subQ = createLinkSubquery(true, parameterName, targetResourceType, orValues);
Join<ResourceTable, ResourceLink> join = myResourceTableRoot.join("myResourceLinksAsTarget", JoinType.LEFT);
Predicate predicate = join.get("mySourceResourcePid").in(match);
myPredicates.add(predicate);
Predicate pathPredicate = createResourceLinkPathPredicate(targetResourceType, paramReference, join);
Predicate pidPredicate = join.get("mySourceResourcePid").in(subQ);
Predicate andPredicate = myBuilder.and(pathPredicate, pidPredicate);
myPredicates.add(andPredicate);
}
}
@ -552,44 +553,12 @@ public class SearchBuilder implements ISearchBuilder {
orValues.add(chainValue);
}
Subquery<Long> subQ = myResourceTableQuery.subquery(Long.class);
Root<ResourceTable> subQfrom = subQ.from(ResourceTable.class);
subQ.select(subQfrom.get("myId").as(Long.class));
List<List<? extends IQueryParameterType>> andOrParams = new ArrayList<>();
andOrParams.add(orValues);
/*
* We're doing a chain call, so push the current query root
* and predicate list down and put new ones at the top of the
* stack and run a subquery
*/
Root<ResourceTable> stackRoot = myResourceTableRoot;
ArrayList<Predicate> stackPredicates = myPredicates;
Map<JoinKey, Join<?, ?>> stackIndexJoins = myIndexJoins;
myResourceTableRoot = subQfrom;
myPredicates = Lists.newArrayList();
myIndexJoins = Maps.newHashMap();
// Create the subquery predicates
myPredicates.add(myBuilder.equal(myResourceTableRoot.get("myResourceType"), subResourceName));
myPredicates.add(myBuilder.isNull(myResourceTableRoot.get("myDeleted")));
if (foundChainMatch) {
searchForIdsWithAndOr(subResourceName, chain, andOrParams);
subQ.where(toArray(myPredicates));
}
/*
* Pop the old query root and predicate list back
*/
myResourceTableRoot = stackRoot;
myPredicates = stackPredicates;
myIndexJoins = stackIndexJoins;
Subquery<Long> subQ = createLinkSubquery(foundChainMatch, chain, subResourceName, orValues);
Predicate pathPredicate = createResourceLinkPathPredicate(theResourceName, theParamName, join);
Predicate pidPredicate = join.get("myTargetResourcePid").in(subQ);
codePredicates.add(myBuilder.and(pathPredicate, pidPredicate));
Predicate andPredicate = myBuilder.and(pathPredicate, pidPredicate);
codePredicates.add(andPredicate);
}
@ -611,6 +580,44 @@ public class SearchBuilder implements ISearchBuilder {
myPredicates.add(myBuilder.or(toArray(codePredicates)));
}
private Subquery<Long> createLinkSubquery(boolean theFoundChainMatch, String theChain, String theSubResourceName, List<IQueryParameterType> theOrValues) {
Subquery<Long> subQ = myResourceTableQuery.subquery(Long.class);
Root<ResourceTable> subQfrom = subQ.from(ResourceTable.class);
subQ.select(subQfrom.get("myId").as(Long.class));
List<List<? extends IQueryParameterType>> andOrParams = new ArrayList<>();
andOrParams.add(theOrValues);
/*
* We're doing a chain call, so push the current query root
* and predicate list down and put new ones at the top of the
* stack and run a subquery
*/
Root<ResourceTable> stackRoot = myResourceTableRoot;
ArrayList<Predicate> stackPredicates = myPredicates;
Map<JoinKey, Join<?, ?>> stackIndexJoins = myIndexJoins;
myResourceTableRoot = subQfrom;
myPredicates = Lists.newArrayList();
myIndexJoins = Maps.newHashMap();
// Create the subquery predicates
myPredicates.add(myBuilder.equal(myResourceTableRoot.get("myResourceType"), theSubResourceName));
myPredicates.add(myBuilder.isNull(myResourceTableRoot.get("myDeleted")));
if (theFoundChainMatch) {
searchForIdsWithAndOr(theSubResourceName, theChain, andOrParams);
subQ.where(toArray(myPredicates));
}
/*
* Pop the old query root and predicate list back
*/
myResourceTableRoot = stackRoot;
myPredicates = stackPredicates;
myIndexJoins = stackIndexJoins;
return subQ;
}
private IQueryParameterType mapReferenceChainToRawParamType(String remainingChain, RuntimeSearchParam param, String theParamName, String qualifier, Class<? extends IBaseResource> nextType, String chain, boolean isMeta, String resourceId) {
IQueryParameterType chainValue;
if (remainingChain != null) {

View File

@ -5,12 +5,10 @@ import ca.uhn.fhir.validation.ResultSeverityEnum;
import net.ttddyy.dsproxy.listener.SingleQueryCountHolder;
import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder;
import org.apache.commons.dbcp2.BasicDataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.env.Environment;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.annotation.EnableTransactionManagement;

View File

@ -3,6 +3,7 @@ package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.jpa.config.CaptureQueriesListener;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.model.entity.*;
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap.EverythingModeEnum;
import ca.uhn.fhir.jpa.util.TestUtil;
@ -31,6 +32,7 @@ import org.hl7.fhir.r4.model.Observation.ObservationStatus;
import org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType;
import org.hl7.fhir.r4.model.Subscription.SubscriptionStatus;
import org.junit.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
@ -51,6 +53,9 @@ import static org.mockito.Mockito.mock;
public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4SearchNoFtTest.class);
@Autowired
MatchUrlService myMatchUrlService;
@After
public void afterResetSearchSize() {
myDaoConfig.setReuseCachedSearchResultsForMillis(new DaoConfig().getReuseCachedSearchResultsForMillis());
@ -64,6 +69,178 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
myDaoConfig.setReuseCachedSearchResultsForMillis(null);
}
@Test
public void testHasConditionAgeCompare() {
Patient patient = new Patient();
String patientId = myPatientDao.create(patient).getId().toUnqualifiedVersionless().getValue();
Condition condition = new Condition();
Quantity onsetAge = new Age();
onsetAge.setValue(23);
condition.setOnset(onsetAge);
condition.getSubject().setReference(patientId);
myConditionDao.create(condition);
{
String criteria = "_has:Condition:subject:onset-age=gt20";
SearchParameterMap map = myMatchUrlService.translateMatchUrl(criteria, myFhirCtx.getResourceDefinition(Patient.class));
map.setLoadSynchronous(true);
IBundleProvider results = myPatientDao.search(map);
List<String> ids = toUnqualifiedVersionlessIdValues(results);
assertEquals(1, ids.size());
assertThat(ids, hasItems(patientId));
}
{
String criteria = "_has:Condition:subject:onset-age=lt20";
SearchParameterMap map = myMatchUrlService.translateMatchUrl(criteria, myFhirCtx.getResourceDefinition(Patient.class));
map.setLoadSynchronous(true);
IBundleProvider results = myPatientDao.search(map);
List<String> ids = toUnqualifiedVersionlessIdValues(results);
assertEquals(0, ids.size());
}
}
@Test
public void testHasCondition() {
Patient patient = new Patient();
String patientId = myPatientDao.create(patient).getId().toUnqualifiedVersionless().getValue();
Condition condition = new Condition();
condition.getCode().addCoding().setSystem("http://snomed.info/sct").setCode("55822004");
condition.getSubject().setReference(patientId);
myConditionDao.create(condition);
String criteria = "_has:Condition:subject:code=http://snomed.info/sct|55822004";
SearchParameterMap map = myMatchUrlService.translateMatchUrl(criteria, myFhirCtx.getResourceDefinition(Patient.class));
map.setLoadSynchronous(true);
IBundleProvider results = myPatientDao.search(map);
List<String> ids = toUnqualifiedVersionlessIdValues(results);
assertEquals(1, ids.size());
assertThat(ids, hasItems(patientId));
}
@Test
public void testHasConditionOr() {
Patient patient = new Patient();
String patientId = myPatientDao.create(patient).getId().toUnqualifiedVersionless().getValue();
Condition condition = new Condition();
condition.getCode().addCoding().setSystem("http://snomed.info/sct").setCode("55822004");
condition.getSubject().setReference(patientId);
myConditionDao.create(condition);
String criteria = "_has:Condition:subject:code=http://snomed.info/sct|55822003,http://snomed.info/sct|55822004";
SearchParameterMap map = myMatchUrlService.translateMatchUrl(criteria, myFhirCtx.getResourceDefinition(Patient.class));
map.setLoadSynchronous(true);
IBundleProvider results = myPatientDao.search(map);
List<String> ids = toUnqualifiedVersionlessIdValues(results);
assertEquals(1, ids.size());
assertThat(ids, hasItems(patientId));
}
@Test
public void testHasConditionAnd() {
Patient patient = new Patient();
String patientId = myPatientDao.create(patient).getId().toUnqualifiedVersionless().getValue();
Condition conditionS = new Condition();
conditionS.getCode().addCoding().setSystem("http://snomed.info/sct").setCode("55822004");
conditionS.getSubject().setReference(patientId);
myConditionDao.create(conditionS);
Condition conditionA = new Condition();
conditionA.getCode().addCoding().setSystem("http://snomed.info/sct").setCode("55822005");
conditionA.getAsserter().setReference(patientId);
myConditionDao.create(conditionA);
String criteria = "_has:Condition:subject:code=http://snomed.info/sct|55822003,http://snomed.info/sct|55822004&"+
"_has:Condition:asserter:code=http://snomed.info/sct|55822003,http://snomed.info/sct|55822005";
SearchParameterMap map = myMatchUrlService.translateMatchUrl(criteria, myFhirCtx.getResourceDefinition(Patient.class));
map.setLoadSynchronous(true);
IBundleProvider results = myPatientDao.search(map);
List<String> ids = toUnqualifiedVersionlessIdValues(results);
assertEquals(1, ids.size());
assertThat(ids, hasItems(patientId));
}
@Test
public void testHasConditionAndBackwards() {
Patient patient = new Patient();
String patientId = myPatientDao.create(patient).getId().toUnqualifiedVersionless().getValue();
Condition conditionS = new Condition();
conditionS.getCode().addCoding().setSystem("http://snomed.info/sct").setCode("55822004");
conditionS.getSubject().setReference(patientId);
myConditionDao.create(conditionS);
Condition conditionA = new Condition();
conditionA.getCode().addCoding().setSystem("http://snomed.info/sct").setCode("55822005");
conditionA.getAsserter().setReference(patientId);
myConditionDao.create(conditionA);
String criteria = "_has:Condition:subject:code=http://snomed.info/sct|55822003,http://snomed.info/sct|55822005&"+
"_has:Condition:asserter:code=http://snomed.info/sct|55822003,http://snomed.info/sct|55822004";
SearchParameterMap map = myMatchUrlService.translateMatchUrl(criteria, myFhirCtx.getResourceDefinition(Patient.class));
map.setLoadSynchronous(true);
IBundleProvider results = myPatientDao.search(map);
List<String> ids = toUnqualifiedVersionlessIdValues(results);
assertEquals(0, ids.size());
}
@Test
public void testGenderBirthdateHasCondition() {
Patient patient = new Patient();
patient.setGender(AdministrativeGender.MALE);
patient.setBirthDateElement(new DateType("1955-01-01"));
String patientId = myPatientDao.create(patient).getId().toUnqualifiedVersionless().getValue();
Condition condition = new Condition();
condition.getCode().addCoding().setSystem("http://snomed.info/sct").setCode("55822004");
condition.getSubject().setReference(patientId);
myConditionDao.create(condition);
String criteria = "gender=male&birthdate=gt1950-07-01&birthdate=lt1960-07-01&_has:Condition:subject:code=http://snomed.info/sct|55822004";
SearchParameterMap map = myMatchUrlService.translateMatchUrl(criteria, myFhirCtx.getResourceDefinition(Patient.class));
map.setLoadSynchronous(true);
IBundleProvider results = myPatientDao.search(map);
List<String> ids = toUnqualifiedVersionlessIdValues(results);
assertEquals(1, ids.size());
assertThat(ids, hasItems(patientId));
}
@Test
public void testHasConditionWrongLink() {
Patient patient = new Patient();
String patientId = myPatientDao.create(patient).getId().toUnqualifiedVersionless().getValue();
Condition condition = new Condition();
condition.getCode().addCoding().setSystem("http://snomed.info/sct").setCode("55822004");
condition.getSubject().setReference(patientId);
myConditionDao.create(condition);
String criteria = "_has:Condition:asserter:code=http://snomed.info/sct|55822004";
SearchParameterMap map = myMatchUrlService.translateMatchUrl(criteria, myFhirCtx.getResourceDefinition(Patient.class));
map.setLoadSynchronous(true);
IBundleProvider results = myPatientDao.search(map);
List<String> ids = toUnqualifiedVersionlessIdValues(results);
assertEquals(0, ids.size());
}
@Test
public void testChainWithMultipleTypePossibilities() {
@ -458,10 +635,12 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
SearchParameterMap params;
params = new SearchParameterMap();
params.setLoadSynchronous(true);
params.add("_has", new HasParam("Observation", "subject", "device.identifier", "urn:system|DEVICEID"));
assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(params)), contains(pid0.getValue()));
// KHS JA When we switched _has from two queries to a nested subquery, we broke support for chains within _has
// We have decided for now to prefer the performance optimization of the subquery over the slower full capability
// params = new SearchParameterMap();
// params.setLoadSynchronous(true);
// params.add("_has", new HasParam("Observation", "subject", "device.identifier", "urn:system|DEVICEID"));
// assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(params)), contains(pid0.getValue()));
// No targets exist
params = new SearchParameterMap();

View File

@ -503,10 +503,12 @@ public class FhirResourceDaoR4SearchNoHashesTest extends BaseJpaR4Test {
SearchParameterMap params;
params = new SearchParameterMap();
params.setLoadSynchronous(true);
params.add("_has", new HasParam("Observation", "subject", "device.identifier", "urn:system|DEVICEID"));
assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(params)), contains(pid0.getValue()));
// KHS JA When we switched _has from two queries to a nested subquery, we broke support for chains within _has
// We have decided for now to prefer the performance optimization of the subquery over the slower full capability
// params = new SearchParameterMap();
// params.setLoadSynchronous(true);
// params.add("_has", new HasParam("Observation", "subject", "device.identifier", "urn:system|DEVICEID"));
// assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(params)), contains(pid0.getValue()));
// No targets exist
params = new SearchParameterMap();

View File

@ -60,7 +60,7 @@ public class SearchHasParamDstu2_1Test {
HasParam param = ourLastParam.getValuesAsQueryTokens().get(0).getValuesAsQueryTokens().get(0);
assertEquals("Encounter", param.getTargetResourceType());
assertEquals("patient", param.getOwningFieldName());
assertEquals("patient", param.getReferenceFieldName());
assertEquals("type", param.getParameterName());
assertEquals("SURG", param.getParameterValue());
}

View File

@ -1,6 +1,5 @@
package ca.uhn.fhir.rest.server;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
@ -62,7 +61,7 @@ public class SearchHasParamDstu3Test {
HasParam param = ourLastParam.getValuesAsQueryTokens().get(0).getValuesAsQueryTokens().get(0);
assertEquals("Encounter", param.getTargetResourceType());
assertEquals("patient", param.getOwningFieldName());
assertEquals("patient", param.getReferenceFieldName());
assertEquals("type", param.getParameterName());
assertEquals("SURG", param.getParameterValue());
}

View File

@ -57,7 +57,7 @@ public class SearchHasParamR4Test {
HasParam param = ourLastParam.getValuesAsQueryTokens().get(0).getValuesAsQueryTokens().get(0);
assertEquals("Encounter", param.getTargetResourceType());
assertEquals("patient", param.getOwningFieldName());
assertEquals("patient", param.getReferenceFieldName());
assertEquals("type", param.getParameterName());
assertEquals("SURG", param.getParameterValue());
}