Merge pull request #2955 from hapifhir/jr-20210831-contained-chained-search
Jr 20210831 contained chained search
This commit is contained in:
commit
5302af8808
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
type: fix
|
||||
issue: 2967
|
||||
jira: SMILE-2899
|
||||
title: "Previously, the system would only traverse references to discrete resources while performing a chained search.
|
||||
This fix adds support for traversing references to contained resources as well, with the limitation that the reference
|
||||
to the contained resource must be the last reference in the chain."
|
|
@ -729,10 +729,10 @@ public class QueryStack {
|
|||
return predicateBuilder.createPredicate(theRequest, theResourceName, theParamName, theList, theOperation, theRequestPartitionId);
|
||||
}
|
||||
|
||||
private Condition createPredicateReferenceForContainedResource(@Nullable DbColumn theSourceJoinColumn,
|
||||
String theResourceName, String theParamName, RuntimeSearchParam theSearchParam,
|
||||
List<? extends IQueryParameterType> theList, SearchFilterParser.CompareOperation theOperation,
|
||||
RequestDetails theRequest, RequestPartitionId theRequestPartitionId) {
|
||||
public Condition createPredicateReferenceForContainedResource(@Nullable DbColumn theSourceJoinColumn,
|
||||
String theResourceName, String theParamName, RuntimeSearchParam theSearchParam,
|
||||
List<? extends IQueryParameterType> theList, SearchFilterParser.CompareOperation theOperation,
|
||||
RequestDetails theRequest, RequestPartitionId theRequestPartitionId) {
|
||||
|
||||
String spnamePrefix = theParamName;
|
||||
|
||||
|
@ -794,31 +794,31 @@ public class QueryStack {
|
|||
|
||||
switch (targetParamDefinition.getParamType()) {
|
||||
case DATE:
|
||||
containedCondition = createPredicateDate(null, theResourceName, spnamePrefix, targetParamDefinition,
|
||||
containedCondition = createPredicateDate(theSourceJoinColumn, theResourceName, spnamePrefix, targetParamDefinition,
|
||||
orValues, theOperation, theRequestPartitionId);
|
||||
break;
|
||||
case NUMBER:
|
||||
containedCondition = createPredicateNumber(null, theResourceName, spnamePrefix, targetParamDefinition,
|
||||
containedCondition = createPredicateNumber(theSourceJoinColumn, theResourceName, spnamePrefix, targetParamDefinition,
|
||||
orValues, theOperation, theRequestPartitionId);
|
||||
break;
|
||||
case QUANTITY:
|
||||
containedCondition = createPredicateQuantity(null, theResourceName, spnamePrefix, targetParamDefinition,
|
||||
containedCondition = createPredicateQuantity(theSourceJoinColumn, theResourceName, spnamePrefix, targetParamDefinition,
|
||||
orValues, theOperation, theRequestPartitionId);
|
||||
break;
|
||||
case STRING:
|
||||
containedCondition = createPredicateString(null, theResourceName, spnamePrefix, targetParamDefinition,
|
||||
containedCondition = createPredicateString(theSourceJoinColumn, theResourceName, spnamePrefix, targetParamDefinition,
|
||||
orValues, theOperation, theRequestPartitionId);
|
||||
break;
|
||||
case TOKEN:
|
||||
containedCondition = createPredicateToken(null, theResourceName, spnamePrefix, targetParamDefinition,
|
||||
containedCondition = createPredicateToken(theSourceJoinColumn, theResourceName, spnamePrefix, targetParamDefinition,
|
||||
orValues, theOperation, theRequestPartitionId);
|
||||
break;
|
||||
case COMPOSITE:
|
||||
containedCondition = createPredicateComposite(null, theResourceName, spnamePrefix, targetParamDefinition,
|
||||
containedCondition = createPredicateComposite(theSourceJoinColumn, theResourceName, spnamePrefix, targetParamDefinition,
|
||||
orValues, theRequestPartitionId);
|
||||
break;
|
||||
case URI:
|
||||
containedCondition = createPredicateUri(null, theResourceName, spnamePrefix, targetParamDefinition,
|
||||
containedCondition = createPredicateUri(theSourceJoinColumn, theResourceName, spnamePrefix, targetParamDefinition,
|
||||
orValues, theOperation, theRequest, theRequestPartitionId);
|
||||
break;
|
||||
case HAS:
|
||||
|
@ -1162,10 +1162,24 @@ public class QueryStack {
|
|||
break;
|
||||
case REFERENCE:
|
||||
for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) {
|
||||
if (theSearchContainedMode.equals(SearchContainedModeEnum.TRUE))
|
||||
andPredicates.add(createPredicateReferenceForContainedResource(theSourceJoinColumn, theResourceName, theParamName, nextParamDef, nextAnd, null, theRequest, theRequestPartitionId));
|
||||
else
|
||||
if (theSearchContainedMode.equals(SearchContainedModeEnum.TRUE)) {
|
||||
// TODO: The _contained parameter is not intended to control search chain interpretation like this.
|
||||
// See SMILE-2898 for details.
|
||||
// For now, leave the incorrect implementation alone, just in case someone is relying on it,
|
||||
// until the complete fix is available.
|
||||
andPredicates.add(createPredicateReferenceForContainedResource(null, theResourceName, theParamName, nextParamDef, nextAnd, null, theRequest, theRequestPartitionId));
|
||||
} else if (isEligibleForContainedResourceSearch(nextAnd)) {
|
||||
// TODO for now, restrict contained reference traversal to the last reference in the chain
|
||||
// We don't seem to be indexing the outbound references of a contained resource, so we can't
|
||||
// include them in search chains.
|
||||
// It would be nice to eventually relax this constraint, but no client seems to be asking for it.
|
||||
andPredicates.add(toOrPredicate(
|
||||
createPredicateReference(theSourceJoinColumn, theResourceName, theParamName, nextAnd, null, theRequest, theRequestPartitionId),
|
||||
createPredicateReferenceForContainedResource(theSourceJoinColumn, theResourceName, theParamName, nextParamDef, nextAnd, null, theRequest, theRequestPartitionId)
|
||||
));
|
||||
} else {
|
||||
andPredicates.add(createPredicateReference(theSourceJoinColumn, theResourceName, theParamName, nextAnd, null, theRequest, theRequestPartitionId));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case STRING:
|
||||
|
@ -1243,6 +1257,14 @@ public class QueryStack {
|
|||
return toAndPredicate(andPredicates);
|
||||
}
|
||||
|
||||
private boolean isEligibleForContainedResourceSearch(List<? extends IQueryParameterType> nextAnd) {
|
||||
return myModelConfig.isIndexOnContainedResources() &&
|
||||
nextAnd.stream()
|
||||
.filter(t -> t instanceof ReferenceParam)
|
||||
.map(t -> (ReferenceParam) t)
|
||||
.noneMatch(t -> t.getChain().contains("."));
|
||||
}
|
||||
|
||||
public void addPredicateCompositeUnique(String theIndexString, RequestPartitionId theRequestPartitionId) {
|
||||
ComboUniqueSearchParameterPredicateBuilder predicateBuilder = mySqlBuilder.addComboUniquePredicateBuilder();
|
||||
Condition predicate = predicateBuilder.createPredicateIndexString(theRequestPartitionId, theIndexString);
|
||||
|
|
|
@ -377,7 +377,7 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
SearchQueryBuilder sqlBuilder = new SearchQueryBuilder(myContext, myDaoConfig.getModelConfig(), myPartitionSettings, myRequestPartitionId, sqlBuilderResourceName, mySqlBuilderFactory, myDialectProvider, theCount);
|
||||
QueryStack queryStack3 = new QueryStack(theParams, myDaoConfig, myDaoConfig.getModelConfig(), myContext, sqlBuilder, mySearchParamRegistry, myPartitionSettings);
|
||||
|
||||
if (theParams.keySet().size() > 1 || theParams.getSort() != null || theParams.keySet().contains(Constants.PARAM_HAS)) {
|
||||
if (theParams.keySet().size() > 1 || theParams.getSort() != null || theParams.keySet().contains(Constants.PARAM_HAS) || isPotentiallyContainedReferenceParameterExistsAtRoot(theParams)) {
|
||||
List<RuntimeSearchParam> activeComboParams = mySearchParamRegistry.getActiveComboSearchParams(myResourceName, theParams.keySet());
|
||||
if (activeComboParams.isEmpty()) {
|
||||
sqlBuilder.setNeedResourceTableRoot(true);
|
||||
|
@ -487,6 +487,13 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
return Optional.of(executor);
|
||||
}
|
||||
|
||||
private boolean isPotentiallyContainedReferenceParameterExistsAtRoot(SearchParameterMap theParams) {
|
||||
return myModelConfig.isIndexOnContainedResources() && theParams.values().stream()
|
||||
.flatMap(Collection::stream)
|
||||
.flatMap(Collection::stream)
|
||||
.anyMatch(t -> t instanceof ReferenceParam);
|
||||
}
|
||||
|
||||
private List<Long> normalizeIdListForLastNInClause(List<Long> lastnResourceIds) {
|
||||
/*
|
||||
The following is a workaround to a known issue involving Hibernate. If queries are used with "in" clauses with large and varying
|
||||
|
|
|
@ -38,20 +38,18 @@ import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao;
|
|||
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
|
||||
import ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderReference;
|
||||
import ca.uhn.fhir.jpa.dao.predicate.SearchFilterParser;
|
||||
import ca.uhn.fhir.jpa.search.builder.QueryStack;
|
||||
import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage;
|
||||
import ca.uhn.fhir.jpa.search.builder.QueryStack;
|
||||
import ca.uhn.fhir.jpa.search.builder.sql.SearchQueryBuilder;
|
||||
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
|
||||
import ca.uhn.fhir.jpa.searchparam.ResourceMetaParams;
|
||||
import ca.uhn.fhir.jpa.searchparam.util.JpaParamUtil;
|
||||
import ca.uhn.fhir.rest.api.SearchContainedModeEnum;
|
||||
import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
|
||||
import ca.uhn.fhir.rest.server.util.CompositeInterceptorBroadcaster;
|
||||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
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.SearchContainedModeEnum;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
|
||||
import ca.uhn.fhir.rest.param.CompositeParam;
|
||||
|
@ -66,6 +64,8 @@ import ca.uhn.fhir.rest.param.TokenParamModifier;
|
|||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||
import ca.uhn.fhir.rest.server.util.CompositeInterceptorBroadcaster;
|
||||
import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.healthmarketscience.sqlbuilder.BinaryCondition;
|
||||
import com.healthmarketscience.sqlbuilder.ComboCondition;
|
||||
|
@ -341,15 +341,26 @@ public class ResourceLinkPredicateBuilder extends BaseJoiningPredicateBuilder {
|
|||
List<Condition> orPredicates = new ArrayList<>();
|
||||
boolean paramInverted = false;
|
||||
QueryStack childQueryFactory = myQueryStack.newChildQueryFactoryWithFullBuilderReuse();
|
||||
for (String nextType : resourceTypes) {
|
||||
String chain = theReferenceParam.getChain();
|
||||
|
||||
String remainingChain = null;
|
||||
int chainDotIndex = chain.indexOf('.');
|
||||
if (chainDotIndex != -1) {
|
||||
remainingChain = chain.substring(chainDotIndex + 1);
|
||||
chain = chain.substring(0, chainDotIndex);
|
||||
}
|
||||
String chain = theReferenceParam.getChain();
|
||||
|
||||
String remainingChain = null;
|
||||
int chainDotIndex = chain.indexOf('.');
|
||||
if (chainDotIndex != -1) {
|
||||
remainingChain = chain.substring(chainDotIndex + 1);
|
||||
chain = chain.substring(0, chainDotIndex);
|
||||
}
|
||||
|
||||
int qualifierIndex = chain.indexOf(':');
|
||||
String qualifier = null;
|
||||
if (qualifierIndex != -1) {
|
||||
qualifier = chain.substring(qualifierIndex);
|
||||
chain = chain.substring(0, qualifierIndex);
|
||||
}
|
||||
|
||||
boolean isMeta = ResourceMetaParams.RESOURCE_META_PARAMS.containsKey(chain);
|
||||
|
||||
for (String nextType : resourceTypes) {
|
||||
|
||||
RuntimeResourceDefinition typeDef = getFhirContext().getResourceDefinition(nextType);
|
||||
String subResourceName = typeDef.getName();
|
||||
|
@ -360,14 +371,6 @@ public class ResourceLinkPredicateBuilder extends BaseJoiningPredicateBuilder {
|
|||
continue;
|
||||
}
|
||||
|
||||
int qualifierIndex = chain.indexOf(':');
|
||||
String qualifier = null;
|
||||
if (qualifierIndex != -1) {
|
||||
qualifier = chain.substring(qualifierIndex);
|
||||
chain = chain.substring(0, qualifierIndex);
|
||||
}
|
||||
|
||||
boolean isMeta = ResourceMetaParams.RESOURCE_META_PARAMS.containsKey(chain);
|
||||
RuntimeSearchParam param = null;
|
||||
if (!isMeta) {
|
||||
param = mySearchParamRegistry.getActiveSearchParam(nextType, chain);
|
||||
|
@ -408,7 +411,6 @@ public class ResourceLinkPredicateBuilder extends BaseJoiningPredicateBuilder {
|
|||
andPredicates.add(childQueryFactory.searchForIdsWithAndOr(myColumnTargetResourceId, subResourceName, chain, chainParamValues, theRequest, theRequestPartitionId, SearchContainedModeEnum.FALSE));
|
||||
|
||||
orPredicates.add(toAndPredicate(andPredicates));
|
||||
|
||||
}
|
||||
|
||||
if (candidateTargetTypes.isEmpty()) {
|
||||
|
|
|
@ -126,5 +126,8 @@ public class SqlQuery {
|
|||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getSql(true, true);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,388 @@
|
|||
package ca.uhn.fhir.jpa.dao.r4;
|
||||
|
||||
import ca.uhn.fhir.jpa.api.config.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
|
||||
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
|
||||
import ca.uhn.fhir.jpa.searchparam.ResourceSearch;
|
||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||
import ca.uhn.fhir.parser.StrictErrorHandler;
|
||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.hl7.fhir.r4.model.IdType;
|
||||
import org.hl7.fhir.r4.model.Observation;
|
||||
import org.hl7.fhir.r4.model.Organization;
|
||||
import org.hl7.fhir.r4.model.Patient;
|
||||
import org.hl7.fhir.r4.model.StringType;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
|
||||
public class ChainingR4SearchTest extends BaseJpaR4Test {
|
||||
|
||||
@Autowired
|
||||
MatchUrlService myMatchUrlService;
|
||||
|
||||
@AfterEach
|
||||
public void after() throws Exception {
|
||||
|
||||
myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete());
|
||||
myDaoConfig.setAllowExternalReferences(new DaoConfig().isAllowExternalReferences());
|
||||
myDaoConfig.setReuseCachedSearchResultsForMillis(new DaoConfig().getReuseCachedSearchResultsForMillis());
|
||||
myDaoConfig.setCountSearchResultsUpTo(new DaoConfig().getCountSearchResultsUpTo());
|
||||
myDaoConfig.setSearchPreFetchThresholds(new DaoConfig().getSearchPreFetchThresholds());
|
||||
myDaoConfig.setAllowContainsSearches(new DaoConfig().isAllowContainsSearches());
|
||||
myDaoConfig.setIndexMissingFields(new DaoConfig().getIndexMissingFields());
|
||||
|
||||
myModelConfig.setIndexOnContainedResources(false);
|
||||
myModelConfig.setIndexOnContainedResources(new ModelConfig().isIndexOnContainedResources());
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
public void before() throws Exception {
|
||||
myFhirCtx.setParserErrorHandler(new StrictErrorHandler());
|
||||
|
||||
myDaoConfig.setAllowMultipleDelete(true);
|
||||
myDaoConfig.setSearchPreFetchThresholds(new DaoConfig().getSearchPreFetchThresholds());
|
||||
myModelConfig.setIndexOnContainedResources(true);
|
||||
myDaoConfig.setReuseCachedSearchResultsForMillis(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShouldResolveATwoLinkChainWithStandAloneResources() throws Exception {
|
||||
|
||||
// setup
|
||||
IIdType oid1;
|
||||
|
||||
{
|
||||
Patient p = new Patient();
|
||||
p.setId(IdType.newRandomUuid());
|
||||
p.addName().setFamily("Smith").addGiven("John");
|
||||
myPatientDao.create(p, mySrd);
|
||||
|
||||
Observation obs = new Observation();
|
||||
obs.getCode().setText("Observation 1");
|
||||
obs.getSubject().setReference(p.getId());
|
||||
|
||||
oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||
}
|
||||
|
||||
String url = "/Observation?subject.name=Smith";
|
||||
|
||||
// execute
|
||||
List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(url);
|
||||
|
||||
// validate
|
||||
assertEquals(1L, oids.size());
|
||||
assertThat(oids, contains(oid1.getIdPart()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShouldResolveATwoLinkChainWithAContainedResource() throws Exception {
|
||||
// setup
|
||||
IIdType oid1;
|
||||
|
||||
{
|
||||
Patient p = new Patient();
|
||||
p.setId("pat");
|
||||
p.addName().setFamily("Smith").addGiven("John");
|
||||
|
||||
Observation obs = new Observation();
|
||||
obs.getContained().add(p);
|
||||
obs.getCode().setText("Observation 1");
|
||||
obs.setValue(new StringType("Test"));
|
||||
obs.getSubject().setReference("#pat");
|
||||
|
||||
oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||
}
|
||||
|
||||
String url = "/Observation?subject.name=Smith";
|
||||
|
||||
// execute
|
||||
List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(url);
|
||||
|
||||
// validate
|
||||
assertEquals(1L, oids.size());
|
||||
assertThat(oids, contains(oid1.getIdPart()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShouldResolveAThreeLinkChainWhereAllResourcesStandAlone() throws Exception {
|
||||
|
||||
// setup
|
||||
IIdType oid1;
|
||||
|
||||
{
|
||||
Organization org = new Organization();
|
||||
org.setId(IdType.newRandomUuid());
|
||||
org.setName("HealthCo");
|
||||
myOrganizationDao.create(org, mySrd);
|
||||
|
||||
Patient p = new Patient();
|
||||
p.setId(IdType.newRandomUuid());
|
||||
p.addName().setFamily("Smith").addGiven("John");
|
||||
p.getManagingOrganization().setReference(org.getId());
|
||||
myPatientDao.create(p, mySrd);
|
||||
|
||||
Observation obs = new Observation();
|
||||
obs.getCode().setText("Observation 1");
|
||||
obs.getSubject().setReference(p.getId());
|
||||
|
||||
oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||
}
|
||||
|
||||
String url = "/Observation?subject.organization.name=HealthCo";
|
||||
|
||||
// execute
|
||||
List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(url);
|
||||
|
||||
// validate
|
||||
assertEquals(1L, oids.size());
|
||||
assertThat(oids, contains(oid1.getIdPart()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShouldResolveAThreeLinkChainWithAContainedResourceAtTheEndOfTheChain() throws Exception {
|
||||
// This is the case that is most relevant to SMILE-2899
|
||||
|
||||
// setup
|
||||
IIdType oid1;
|
||||
|
||||
{
|
||||
Organization org = new Organization();
|
||||
org.setId("org");
|
||||
org.setName("HealthCo");
|
||||
|
||||
Patient p = new Patient();
|
||||
p.setId(IdType.newRandomUuid());
|
||||
p.getContained().add(org);
|
||||
p.addName().setFamily("Smith").addGiven("John");
|
||||
p.getManagingOrganization().setReference("#org");
|
||||
myPatientDao.create(p, mySrd);
|
||||
|
||||
Observation obs = new Observation();
|
||||
obs.getCode().setText("Observation 1");
|
||||
obs.getSubject().setReference(p.getId());
|
||||
|
||||
oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||
}
|
||||
|
||||
String url = "/Observation?subject.organization.name=HealthCo";
|
||||
|
||||
// execute
|
||||
List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(url);
|
||||
|
||||
// validate
|
||||
assertEquals(1L, oids.size());
|
||||
assertThat(oids, contains(oid1.getIdPart()));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void testShouldResolveAThreeLinkChainWithAContainedResourceAtTheBeginningOfTheChain() throws Exception {
|
||||
// We do not currently support this case - we may not be indexing the references of contained resources
|
||||
|
||||
// setup
|
||||
IIdType oid1;
|
||||
|
||||
{
|
||||
Organization org = new Organization();
|
||||
org.setId(IdType.newRandomUuid());
|
||||
org.setName("HealthCo");
|
||||
myOrganizationDao.create(org, mySrd);
|
||||
|
||||
Patient p = new Patient();
|
||||
p.setId("pat");
|
||||
p.addName().setFamily("Smith").addGiven("John");
|
||||
p.getManagingOrganization().setReference(org.getId());
|
||||
|
||||
Observation obs = new Observation();
|
||||
obs.getContained().add(p);
|
||||
obs.getCode().setText("Observation 1");
|
||||
obs.getSubject().setReference("#pat");
|
||||
|
||||
oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||
}
|
||||
|
||||
String url = "/Observation?subject.organization.name=HealthCo";
|
||||
|
||||
// execute
|
||||
List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(url);
|
||||
|
||||
// validate
|
||||
assertEquals(1L, oids.size());
|
||||
assertThat(oids, contains(oid1.getIdPart()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShouldResolveAThreeLinkChainWithQualifiersWhereAllResourcesStandAlone() throws Exception {
|
||||
|
||||
// setup
|
||||
IIdType oid1;
|
||||
|
||||
{
|
||||
Organization org = new Organization();
|
||||
org.setId(IdType.newRandomUuid());
|
||||
org.setName("HealthCo");
|
||||
myOrganizationDao.create(org, mySrd);
|
||||
|
||||
Patient p = new Patient();
|
||||
p.setId(IdType.newRandomUuid());
|
||||
p.addName().setFamily("Smith").addGiven("John");
|
||||
p.getManagingOrganization().setReference(org.getId());
|
||||
myPatientDao.create(p, mySrd);
|
||||
|
||||
Observation obs = new Observation();
|
||||
obs.getCode().setText("Observation 1");
|
||||
obs.getSubject().setReference(p.getId());
|
||||
|
||||
oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||
}
|
||||
|
||||
String url = "/Observation?subject:Patient.organization:Organization.name=HealthCo";
|
||||
|
||||
// execute
|
||||
List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(url);
|
||||
|
||||
// validate
|
||||
assertEquals(1L, oids.size());
|
||||
assertThat(oids, contains(oid1.getIdPart()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShouldResolveAThreeLinkChainWithQualifiersWithAContainedResourceAtTheEndOfTheChain() throws Exception {
|
||||
// This is the case that is most relevant to SMILE-2899
|
||||
|
||||
// setup
|
||||
IIdType oid1;
|
||||
|
||||
{
|
||||
Organization org = new Organization();
|
||||
org.setId("org");
|
||||
org.setName("HealthCo");
|
||||
|
||||
Patient p = new Patient();
|
||||
p.setId(IdType.newRandomUuid());
|
||||
p.getContained().add(org);
|
||||
p.addName().setFamily("Smith").addGiven("John");
|
||||
p.getManagingOrganization().setReference("#org");
|
||||
myPatientDao.create(p, mySrd);
|
||||
|
||||
Observation obs = new Observation();
|
||||
obs.getCode().setText("Observation 1");
|
||||
obs.getSubject().setReference(p.getId());
|
||||
|
||||
oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||
}
|
||||
|
||||
String url = "/Observation?subject:Patient.organization:Organization.name=HealthCo";
|
||||
|
||||
// execute
|
||||
List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(url);
|
||||
|
||||
// validate
|
||||
assertEquals(1L, oids.size());
|
||||
assertThat(oids, contains(oid1.getIdPart()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShouldResolveAFourLinkChainWhereAllResourcesStandAlone() throws Exception {
|
||||
|
||||
// setup
|
||||
IIdType oid1;
|
||||
|
||||
{
|
||||
Organization org = new Organization();
|
||||
org.setId(IdType.newRandomUuid());
|
||||
org.setName("HealthCo");
|
||||
myOrganizationDao.create(org, mySrd);
|
||||
|
||||
Organization partOfOrg = new Organization();
|
||||
partOfOrg.setId(IdType.newRandomUuid());
|
||||
partOfOrg.getPartOf().setReference(org.getId());
|
||||
myOrganizationDao.create(partOfOrg, mySrd);
|
||||
|
||||
Patient p = new Patient();
|
||||
p.setId(IdType.newRandomUuid());
|
||||
p.addName().setFamily("Smith").addGiven("John");
|
||||
p.getManagingOrganization().setReference(partOfOrg.getId());
|
||||
myPatientDao.create(p, mySrd);
|
||||
|
||||
Observation obs = new Observation();
|
||||
obs.getCode().setText("Observation 1");
|
||||
obs.getSubject().setReference(p.getId());
|
||||
|
||||
oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||
}
|
||||
|
||||
String url = "/Observation?subject.organization.partof.name=HealthCo";
|
||||
|
||||
// execute
|
||||
List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(url);
|
||||
|
||||
// validate
|
||||
assertEquals(1L, oids.size());
|
||||
assertThat(oids, contains(oid1.getIdPart()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShouldResolveAFourLinkChainWhereTheLastReferenceIsContained() throws Exception {
|
||||
|
||||
// setup
|
||||
IIdType oid1;
|
||||
|
||||
{
|
||||
Organization org = new Organization();
|
||||
org.setId("parent");
|
||||
org.setName("HealthCo");
|
||||
|
||||
Organization partOfOrg = new Organization();
|
||||
partOfOrg.setId(IdType.newRandomUuid());
|
||||
partOfOrg.getContained().add(org);
|
||||
partOfOrg.getPartOf().setReference("#parent");
|
||||
myOrganizationDao.create(partOfOrg, mySrd);
|
||||
|
||||
Patient p = new Patient();
|
||||
p.setId(IdType.newRandomUuid());
|
||||
p.addName().setFamily("Smith").addGiven("John");
|
||||
p.getManagingOrganization().setReference(partOfOrg.getId());
|
||||
myPatientDao.create(p, mySrd);
|
||||
|
||||
Observation obs = new Observation();
|
||||
obs.getCode().setText("Observation 1");
|
||||
obs.getSubject().setReference(p.getId());
|
||||
|
||||
oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||
}
|
||||
|
||||
String url = "/Observation?subject.organization.partof.name=HealthCo";
|
||||
|
||||
// execute
|
||||
List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(url);
|
||||
|
||||
// validate
|
||||
assertEquals(1L, oids.size());
|
||||
assertThat(oids, contains(oid1.getIdPart()));
|
||||
}
|
||||
|
||||
private List<String> searchAndReturnUnqualifiedVersionlessIdValues(String theUrl) throws IOException {
|
||||
List<String> ids = new ArrayList<>();
|
||||
|
||||
ResourceSearch search = myMatchUrlService.getResourceSearch(theUrl);
|
||||
SearchParameterMap map = search.getSearchParameterMap();
|
||||
map.setLoadSynchronous(true);
|
||||
IBundleProvider result = myObservationDao.search(map);
|
||||
return result.getAllResourceIds();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,10 +1,10 @@
|
|||
package ca.uhn.fhir.jpa.dao.r4;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||
import ca.uhn.fhir.rest.api.SearchContainedModeEnum;
|
||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||
import ca.uhn.fhir.rest.param.ReferenceParam;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.hl7.fhir.r4.model.Address;
|
||||
import org.hl7.fhir.r4.model.Address.AddressUse;
|
||||
|
@ -26,15 +26,13 @@ import org.junit.jupiter.api.AfterEach;
|
|||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import ca.uhn.fhir.rest.api.SearchContainedModeEnum;
|
||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||
import ca.uhn.fhir.rest.param.ReferenceParam;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
|
||||
import java.util.stream.Collector;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
public class FhirResourceDaoR4ContainedTest extends BaseJpaR4Test {
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4ContainedTest.class);
|
||||
|
||||
|
@ -80,7 +78,6 @@ public class FhirResourceDaoR4ContainedTest extends BaseJpaR4Test {
|
|||
map = new SearchParameterMap();
|
||||
map.add("subject", new ReferenceParam("name", "Smith"));
|
||||
map.setSearchContainedMode(SearchContainedModeEnum.TRUE);
|
||||
|
||||
assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(map)), containsInAnyOrder(toValues(id)));
|
||||
}
|
||||
|
||||
|
@ -110,14 +107,16 @@ public class FhirResourceDaoR4ContainedTest extends BaseJpaR4Test {
|
|||
.getSingleResult();
|
||||
assertEquals(1L, i.longValue());
|
||||
});
|
||||
|
||||
|
||||
SearchParameterMap map;
|
||||
|
||||
map = new SearchParameterMap();
|
||||
map.add("subject", new ReferenceParam("name", "Smith"));
|
||||
map.setSearchContainedMode(SearchContainedModeEnum.TRUE);
|
||||
|
||||
map.setLoadSynchronous(true);
|
||||
|
||||
assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(map)), containsInAnyOrder(toValues(id)));
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue