Supported contained resource search (#2441)

* POC for indexing on contained resource - test case may failed

* Test contained url

* Add a spt to handle contained flag

* Added search option for contained resource

* Impl contained resource search

* fixed typo

* Reworked on creating index based on the review comments

* Added changelog

* Added more test cases

Co-authored-by: jamesagnew <jamesagnew@gmail.com>
This commit is contained in:
Frank Tao 2021-03-09 06:50:23 -05:00 committed by GitHub
parent 6cc8a5ce4d
commit bf8e890a0c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 1749 additions and 173 deletions

View File

@ -0,0 +1,4 @@
---
type: add
issue: 2403
title: "Optionally support '_contained' resource search by enabling the indexing on the contained resources in the ModelConfig."

View File

@ -54,6 +54,7 @@ import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider;
import ca.uhn.fhir.jpa.search.cache.SearchCacheStatusEnum;
import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc;
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
import ca.uhn.fhir.jpa.searchparam.SearchContainedEnum;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster;
import ca.uhn.fhir.model.api.IQueryParameterType;
@ -1291,6 +1292,18 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
@Override
public IBundleProvider search(final SearchParameterMap theParams, RequestDetails theRequest, HttpServletResponse theServletResponse) {
if (theRequest != null) {
String[] contained = theRequest.getParameters().get(Constants.PARAM_CONTAINED);
if (contained != null && contained.length > 0) {
if (contained[0].equals("true")) {
theParams.setSearchContainedMode(SearchContainedEnum.TRUE);
ourLog.info("Search on contained resources only");
} else if (contained[0].equals("both")) {
ourLog.warn("Search on both normal resources and contained resources are not support. set to default search on normal resources");
}
}
}
if (myDaoConfig.getIndexMissingFields() == DaoConfig.IndexEnabledEnum.DISABLED) {
for (List<List<IQueryParameterType>> nextAnds : theParams.values()) {
for (List<? extends IQueryParameterType> nextOrs : nextAnds) {

View File

@ -52,6 +52,7 @@ import ca.uhn.fhir.jpa.search.builder.predicate.TokenPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.UriPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.sql.SearchQueryBuilder;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.searchparam.SearchContainedEnum;
import ca.uhn.fhir.jpa.searchparam.extractor.BaseSearchParamExtractor;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
import ca.uhn.fhir.jpa.searchparam.util.SourceParam;
@ -109,6 +110,7 @@ import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
@ -286,7 +288,7 @@ public class QueryStack {
return new PredicateBuilderCacheLookupResult<>(cacheHit, (T) retVal);
}
private Condition createPredicateComposite(@Nullable DbColumn theSourceJoinColumn, String theResourceName, RuntimeSearchParam theParamDef, List<? extends IQueryParameterType> theNextAnd, RequestPartitionId theRequestPartitionId) {
private Condition createPredicateComposite(@Nullable DbColumn theSourceJoinColumn, String theResourceName, String theSpnamePrefix, RuntimeSearchParam theParamDef, List<? extends IQueryParameterType> theNextAnd, RequestPartitionId theRequestPartitionId) {
Condition orCondidtion = null;
for (IQueryParameterType next : theNextAnd) {
@ -298,11 +300,11 @@ public class QueryStack {
RuntimeSearchParam left = theParamDef.getCompositeOf().get(0);
IQueryParameterType leftValue = cp.getLeftValue();
Condition leftPredicate = createPredicateCompositePart(theSourceJoinColumn, theResourceName, left, leftValue, theRequestPartitionId);
Condition leftPredicate = createPredicateCompositePart(theSourceJoinColumn, theResourceName, theSpnamePrefix, left, leftValue, theRequestPartitionId);
RuntimeSearchParam right = theParamDef.getCompositeOf().get(1);
IQueryParameterType rightValue = cp.getRightValue();
Condition rightPredicate = createPredicateCompositePart(theSourceJoinColumn, theResourceName, right, rightValue, theRequestPartitionId);
Condition rightPredicate = createPredicateCompositePart(theSourceJoinColumn, theResourceName, theSpnamePrefix, right, rightValue, theRequestPartitionId);
Condition andCondition = toAndPredicate(leftPredicate, rightPredicate);
@ -316,20 +318,20 @@ public class QueryStack {
return orCondidtion;
}
private Condition createPredicateCompositePart(@Nullable DbColumn theSourceJoinColumn, String theResourceName, RuntimeSearchParam theParam, IQueryParameterType theParamValue, RequestPartitionId theRequestPartitionId) {
private Condition createPredicateCompositePart(@Nullable DbColumn theSourceJoinColumn, String theResourceName, String theSpnamePrefix, RuntimeSearchParam theParam, IQueryParameterType theParamValue, RequestPartitionId theRequestPartitionId) {
switch (theParam.getParamType()) {
case STRING: {
return createPredicateString(theSourceJoinColumn, theResourceName, theParam, Collections.singletonList(theParamValue), null, theRequestPartitionId);
return createPredicateString(theSourceJoinColumn, theResourceName, theSpnamePrefix, theParam, Collections.singletonList(theParamValue), null, theRequestPartitionId);
}
case TOKEN: {
return createPredicateToken(theSourceJoinColumn, theResourceName, theParam, Collections.singletonList(theParamValue), null, theRequestPartitionId);
return createPredicateToken(theSourceJoinColumn, theResourceName, theSpnamePrefix, theParam, Collections.singletonList(theParamValue), null, theRequestPartitionId);
}
case DATE: {
return createPredicateDate(theSourceJoinColumn, theResourceName, theParam, Collections.singletonList(theParamValue), toOperation(((DateParam) theParamValue).getPrefix()), theRequestPartitionId);
return createPredicateDate(theSourceJoinColumn, theResourceName, theSpnamePrefix, theParam, Collections.singletonList(theParamValue), toOperation(((DateParam) theParamValue).getPrefix()), theRequestPartitionId);
}
case QUANTITY: {
return createPredicateQuantity(theSourceJoinColumn, theResourceName, theParam, Collections.singletonList(theParamValue), null, theRequestPartitionId);
return createPredicateQuantity(theSourceJoinColumn, theResourceName, theSpnamePrefix, theParam, Collections.singletonList(theParamValue), null, theRequestPartitionId);
}
}
@ -357,15 +359,12 @@ public class QueryStack {
return predicateBuilder.combineWithRequestPartitionIdPredicate(theRequestPartitionId, ComboCondition.or(codePredicates.toArray(new Condition[0])));
}
public Condition createPredicateDate(@Nullable DbColumn theSourceJoinColumn,
String theResourceName,
RuntimeSearchParam theSearchParam,
List<? extends IQueryParameterType> theList,
SearchFilterParser.CompareOperation theOperation,
RequestPartitionId theRequestPartitionId) {
String paramName = theSearchParam.getName();
public Condition createPredicateDate(@Nullable DbColumn theSourceJoinColumn, String theResourceName,
String theSpnamePrefix, RuntimeSearchParam theSearchParam, List<? extends IQueryParameterType> theList,
SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) {
String paramName = getParamNameWithPrefix(theSpnamePrefix, theSearchParam.getName());
PredicateBuilderCacheLookupResult<DatePredicateBuilder> predicateBuilderLookupResult = createOrReusePredicateBuilder(PredicateBuilderTypeEnum.DATE, theSourceJoinColumn, paramName, () -> mySqlBuilder.addDatePredicateBuilder(theSourceJoinColumn));
DatePredicateBuilder predicateBuilder = predicateBuilderLookupResult.getResult();
boolean cacheHit = predicateBuilderLookupResult.isCacheHit();
@ -440,13 +439,13 @@ public class QueryStack {
}
RestSearchParameterTypeEnum typeEnum = searchParam.getParamType();
if (typeEnum == RestSearchParameterTypeEnum.URI) {
return theQueryStack3.createPredicateUri(null, theResourceName, searchParam, Collections.singletonList(new UriParam(theFilter.getValue())), theFilter.getOperation(), theRequest, theRequestPartitionId);
return theQueryStack3.createPredicateUri(null, theResourceName, null, searchParam, Collections.singletonList(new UriParam(theFilter.getValue())), theFilter.getOperation(), theRequest, theRequestPartitionId);
} else if (typeEnum == RestSearchParameterTypeEnum.STRING) {
return theQueryStack3.createPredicateString(null, theResourceName, searchParam, Collections.singletonList(new StringParam(theFilter.getValue())), theFilter.getOperation(), theRequestPartitionId);
return theQueryStack3.createPredicateString(null, theResourceName, null, searchParam, Collections.singletonList(new StringParam(theFilter.getValue())), theFilter.getOperation(), theRequestPartitionId);
} else if (typeEnum == RestSearchParameterTypeEnum.DATE) {
return theQueryStack3.createPredicateDate(null, theResourceName, searchParam, Collections.singletonList(new DateParam(fromOperation(theFilter.getOperation()), theFilter.getValue())), theFilter.getOperation(), theRequestPartitionId);
return theQueryStack3.createPredicateDate(null, theResourceName, null, searchParam, Collections.singletonList(new DateParam(fromOperation(theFilter.getOperation()), theFilter.getValue())), theFilter.getOperation(), theRequestPartitionId);
} else if (typeEnum == RestSearchParameterTypeEnum.NUMBER) {
return theQueryStack3.createPredicateNumber(null, theResourceName, searchParam, Collections.singletonList(new NumberParam(theFilter.getValue())), theFilter.getOperation(), theRequestPartitionId);
return theQueryStack3.createPredicateNumber(null, theResourceName, null, searchParam, Collections.singletonList(new NumberParam(theFilter.getValue())), theFilter.getOperation(), theRequestPartitionId);
} else if (typeEnum == RestSearchParameterTypeEnum.REFERENCE) {
SearchFilterParser.CompareOperation operation = theFilter.getOperation();
String resourceType = null; // The value can either have (Patient/123) or not have (123) a resource type, either way it's not needed here
@ -455,7 +454,7 @@ public class QueryStack {
ReferenceParam referenceParam = new ReferenceParam(resourceType, chain, value);
return theQueryStack3.createPredicateReference(null, theResourceName, paramName, Collections.singletonList(referenceParam), operation, theRequest, theRequestPartitionId);
} else if (typeEnum == RestSearchParameterTypeEnum.QUANTITY) {
return theQueryStack3.createPredicateQuantity(null, theResourceName, searchParam, Collections.singletonList(new QuantityParam(theFilter.getValue())), theFilter.getOperation(), theRequestPartitionId);
return theQueryStack3.createPredicateQuantity(null, theResourceName, null, searchParam, Collections.singletonList(new QuantityParam(theFilter.getValue())), theFilter.getOperation(), theRequestPartitionId);
} else if (typeEnum == RestSearchParameterTypeEnum.COMPOSITE) {
throw new InvalidRequestException("Composite search parameters not currently supported with _filter clauses");
} else if (typeEnum == RestSearchParameterTypeEnum.TOKEN) {
@ -464,7 +463,7 @@ public class QueryStack {
null,
null,
theFilter.getValue());
return theQueryStack3.createPredicateToken(null, theResourceName, searchParam, Collections.singletonList(param), theFilter.getOperation(), theRequestPartitionId);
return theQueryStack3.createPredicateToken(null, theResourceName, null, searchParam, Collections.singletonList(param), theFilter.getOperation(), theRequestPartitionId);
}
}
return null;
@ -562,7 +561,7 @@ public class QueryStack {
List<String> paths = join.createResourceLinkPaths(targetResourceType, paramReference);
Condition typePredicate = BinaryCondition.equalTo(join.getColumnTargetResourceType(), mySqlBuilder.generatePlaceholder(theResourceType));
Condition pathPredicate = toEqualToOrInPredicate(join.getColumnSourcePath(), mySqlBuilder.generatePlaceholders(paths));
Condition linkedPredicate = searchForIdsWithAndOr(join.getColumnSrcResourceId(), targetResourceType, parameterName, Collections.singletonList(orValues), theRequest, theRequestPartitionId);
Condition linkedPredicate = searchForIdsWithAndOr(join.getColumnSrcResourceId(), targetResourceType, parameterName, Collections.singletonList(orValues), theRequest, theRequestPartitionId, SearchContainedEnum.FALSE);
andPredicates.add(toAndPredicate(partitionPredicate, pathPredicate, typePredicate, linkedPredicate));
}
@ -607,17 +606,16 @@ public class QueryStack {
return toAndPredicate(predicates);
}
public Condition createPredicateNumber(@Nullable DbColumn theSourceJoinColumn,
String theResourceName,
RuntimeSearchParam theSearchParam,
List<? extends IQueryParameterType> theList,
SearchFilterParser.CompareOperation theOperation,
RequestPartitionId theRequestPartitionId) {
public Condition createPredicateNumber(@Nullable DbColumn theSourceJoinColumn, String theResourceName,
String theSpnamePrefix, RuntimeSearchParam theSearchParam, List<? extends IQueryParameterType> theList,
SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) {
NumberPredicateBuilder join = createOrReusePredicateBuilder(PredicateBuilderTypeEnum.NUMBER, theSourceJoinColumn, theSearchParam.getName(), () -> mySqlBuilder.addNumberPredicateBuilder(theSourceJoinColumn)).getResult();
String paramName = getParamNameWithPrefix(theSpnamePrefix, theSearchParam.getName());
NumberPredicateBuilder join = createOrReusePredicateBuilder(PredicateBuilderTypeEnum.NUMBER, theSourceJoinColumn, paramName, () -> mySqlBuilder.addNumberPredicateBuilder(theSourceJoinColumn)).getResult();
if (theList.get(0).getMissing() != null) {
return join.createPredicateParamMissingForNonReference(theResourceName, theSearchParam.getName(), theList.get(0).getMissing(), theRequestPartitionId);
return join.createPredicateParamMissingForNonReference(theResourceName, paramName, theList.get(0).getMissing(), theRequestPartitionId);
}
List<Condition> codePredicates = new ArrayList<>();
@ -636,7 +634,8 @@ public class QueryStack {
operation = toOperation(param.getPrefix());
}
Condition predicate = join.createPredicateNumeric(theResourceName, theSearchParam.getName(), operation, value, theRequestPartitionId, nextOr);
Condition predicate = join.createPredicateNumeric(theResourceName, paramName, operation, value, theRequestPartitionId, nextOr);
codePredicates.add(predicate);
} else {
@ -648,16 +647,15 @@ public class QueryStack {
return join.combineWithRequestPartitionIdPredicate(theRequestPartitionId, ComboCondition.or(codePredicates.toArray(new Condition[0])));
}
public Condition createPredicateQuantity(@Nullable DbColumn theSourceJoinColumn,
String theResourceName,
RuntimeSearchParam theSearchParam,
List<? extends IQueryParameterType> theList,
SearchFilterParser.CompareOperation theOperation,
RequestPartitionId theRequestPartitionId) {
public Condition createPredicateQuantity(@Nullable DbColumn theSourceJoinColumn, String theResourceName,
String theSpnamePrefix, RuntimeSearchParam theSearchParam, List<? extends IQueryParameterType> theList,
SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) {
String paramName = getParamNameWithPrefix(theSpnamePrefix, theSearchParam.getName());
if (theList.get(0).getMissing() != null) {
QuantityBasePredicateBuilder join = createOrReusePredicateBuilder(PredicateBuilderTypeEnum.QUANTITY, theSourceJoinColumn, theSearchParam.getName(), () -> mySqlBuilder.addQuantityPredicateBuilder(theSourceJoinColumn)).getResult();
return join.createPredicateParamMissingForNonReference(theResourceName, theSearchParam.getName(), theList.get(0).getMissing(), theRequestPartitionId);
return join.createPredicateParamMissingForNonReference(theResourceName, paramName, theList.get(0).getMissing(), theRequestPartitionId);
}
List<QuantityParam> quantityParams = theList
@ -675,18 +673,18 @@ public class QueryStack {
.collect(Collectors.toList());
if (normalizedQuantityParams.size() == quantityParams.size()) {
join = createOrReusePredicateBuilder(PredicateBuilderTypeEnum.QUANTITY, theSourceJoinColumn, theSearchParam.getName(), () -> mySqlBuilder.addQuantityNormalizedPredicateBuilder(theSourceJoinColumn)).getResult();
join = createOrReusePredicateBuilder(PredicateBuilderTypeEnum.QUANTITY, theSourceJoinColumn, paramName, () -> mySqlBuilder.addQuantityNormalizedPredicateBuilder(theSourceJoinColumn)).getResult();
quantityParams = normalizedQuantityParams;
}
}
if (join == null) {
join = createOrReusePredicateBuilder(PredicateBuilderTypeEnum.QUANTITY, theSourceJoinColumn, theSearchParam.getName(), () -> mySqlBuilder.addQuantityPredicateBuilder(theSourceJoinColumn)).getResult();
join = createOrReusePredicateBuilder(PredicateBuilderTypeEnum.QUANTITY, theSourceJoinColumn, paramName, () -> mySqlBuilder.addQuantityPredicateBuilder(theSourceJoinColumn)).getResult();
}
List<Condition> codePredicates = new ArrayList<>();
for (QuantityParam nextOr : quantityParams) {
Condition singleCode = join.createPredicateQuantity(nextOr, theResourceName, theSearchParam.getName(), null, join, theOperation, theRequestPartitionId);
Condition singleCode = join.createPredicateQuantity(nextOr, theResourceName, paramName, null, join, theOperation, theRequestPartitionId);
codePredicates.add(singleCode);
}
@ -720,6 +718,106 @@ 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) {
String spnamePrefix = theParamName;
String targetChain = null;
String targetParamName = null;
String targetQualifier = null;
String targetValue = null;
RuntimeSearchParam targetParamDefinition = null;
ArrayList<IQueryParameterType> orValues = Lists.newArrayList();
IQueryParameterType qp = null;
for (int orIdx = 0; orIdx < theList.size(); orIdx++) {
IQueryParameterType nextOr = theList.get(orIdx);
if (nextOr instanceof ReferenceParam) {
ReferenceParam referenceParam = (ReferenceParam) nextOr;
// 1. Find out the parameter, qualifier and the value
targetChain = referenceParam.getChain();
targetParamName = targetChain;
targetValue = nextOr.getValueAsQueryToken(myFhirContext);
int qualifierIndex = targetChain.indexOf(':');
if (qualifierIndex != -1) {
targetParamName = targetChain.substring(0, qualifierIndex);
targetQualifier = targetChain.substring(qualifierIndex);
}
// 2. find out the data type
if (targetParamDefinition == null) {
Iterator<String> it = theSearchParam.getTargets().iterator();
while (it.hasNext()) {
targetParamDefinition = mySearchParamRegistry.getActiveSearchParam(it.next(), targetParamName);
if (targetParamDefinition != null)
break;
}
}
if (targetParamDefinition == null) {
throw new InvalidRequestException("Unknown search parameter name: " + theSearchParam.getName() + '.' + targetParamName + ".");
}
qp = toParameterType(targetParamDefinition);
qp.setValueAsQueryToken(myFhirContext, targetParamName, targetQualifier, targetValue);
orValues.add(qp);
}
}
if (targetParamDefinition == null) {
throw new InvalidRequestException("Unknown search parameter name: " + theSearchParam.getName() + ".");
}
// 3. create the query
Condition containedCondition = null;
switch (targetParamDefinition.getParamType()) {
case DATE:
containedCondition = createPredicateDate(null, theResourceName, spnamePrefix, targetParamDefinition,
orValues, theOperation, theRequestPartitionId);
break;
case NUMBER:
containedCondition = createPredicateNumber(null, theResourceName, spnamePrefix, targetParamDefinition,
orValues, theOperation, theRequestPartitionId);
break;
case QUANTITY:
containedCondition = createPredicateQuantity(null, theResourceName, spnamePrefix, targetParamDefinition,
orValues, theOperation, theRequestPartitionId);
break;
case STRING:
containedCondition = createPredicateString(null, theResourceName, spnamePrefix, targetParamDefinition,
orValues, theOperation, theRequestPartitionId);
break;
case TOKEN:
containedCondition = createPredicateToken(null, theResourceName, spnamePrefix, targetParamDefinition,
orValues, theOperation, theRequestPartitionId);
break;
case COMPOSITE:
containedCondition = createPredicateComposite(null, theResourceName, spnamePrefix, targetParamDefinition,
orValues, theRequestPartitionId);
break;
case URI:
containedCondition = createPredicateUri(null, theResourceName, spnamePrefix, targetParamDefinition,
orValues, theOperation, theRequest, theRequestPartitionId);
break;
default:
throw new InvalidRequestException(
"The search type:" + targetParamDefinition.getParamType() + " is not supported.");
}
return containedCondition;
}
@Nullable
public Condition createPredicateResourceId(@Nullable DbColumn theSourceJoinColumn, List<List<IQueryParameterType>> theValues, String theResourceName, SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) {
ResourceIdPredicateBuilder builder = mySqlBuilder.newResourceIdBuilder();
@ -762,22 +860,21 @@ public class QueryStack {
return toOrPredicate(orPredicates);
}
public Condition createPredicateString(@Nullable DbColumn theSourceJoinColumn,
String theResourceName,
RuntimeSearchParam theSearchParam,
List<? extends IQueryParameterType> theList,
SearchFilterParser.CompareOperation theOperation,
RequestPartitionId theRequestPartitionId) {
public Condition createPredicateString(@Nullable DbColumn theSourceJoinColumn, String theResourceName,
String theSpnamePrefix, RuntimeSearchParam theSearchParam, List<? extends IQueryParameterType> theList,
SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) {
StringPredicateBuilder join = createOrReusePredicateBuilder(PredicateBuilderTypeEnum.STRING, theSourceJoinColumn, theSearchParam.getName(), () -> mySqlBuilder.addStringPredicateBuilder(theSourceJoinColumn)).getResult();
String paramName = getParamNameWithPrefix(theSpnamePrefix, theSearchParam.getName());
StringPredicateBuilder join = createOrReusePredicateBuilder(PredicateBuilderTypeEnum.STRING, theSourceJoinColumn, paramName, () -> mySqlBuilder.addStringPredicateBuilder(theSourceJoinColumn)).getResult();
if (theList.get(0).getMissing() != null) {
return join.createPredicateParamMissingForNonReference(theResourceName, theSearchParam.getName(), theList.get(0).getMissing(), theRequestPartitionId);
return join.createPredicateParamMissingForNonReference(theResourceName, paramName, theList.get(0).getMissing(), theRequestPartitionId);
}
List<Condition> codePredicates = new ArrayList<>();
for (IQueryParameterType nextOr : theList) {
Condition singleCode = join.createPredicateString(nextOr, theResourceName, theSearchParam, join, theOperation);
Condition singleCode = join.createPredicateString(nextOr, theResourceName, theSpnamePrefix, theSearchParam, join, theOperation);
codePredicates.add(singleCode);
}
@ -873,12 +970,9 @@ public class QueryStack {
return toAndPredicate(andPredicates);
}
public Condition createPredicateToken(@Nullable DbColumn theSourceJoinColumn,
String theResourceName,
RuntimeSearchParam theSearchParam,
List<? extends IQueryParameterType> theList,
SearchFilterParser.CompareOperation theOperation,
RequestPartitionId theRequestPartitionId) {
public Condition createPredicateToken(@Nullable DbColumn theSourceJoinColumn, String theResourceName,
String theSpnamePrefix, RuntimeSearchParam theSearchParam, List<? extends IQueryParameterType> theList,
SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) {
List<IQueryParameterType> tokens = new ArrayList<>();
for (IQueryParameterType nextOr : theList) {
@ -900,7 +994,7 @@ public class QueryStack {
throw new MethodNotAllowedException(msg);
}
return createPredicateString(theSourceJoinColumn, theResourceName, theSearchParam, theList, null, theRequestPartitionId);
return createPredicateString(theSourceJoinColumn, theResourceName, theSpnamePrefix, theSearchParam, theList, null, theRequestPartitionId);
}
tokens.add(nextOr);
@ -917,26 +1011,26 @@ public class QueryStack {
if (tokens.isEmpty()) {
return null;
}
String paramName = getParamNameWithPrefix(theSpnamePrefix, theSearchParam.getName());
TokenPredicateBuilder join = createOrReusePredicateBuilder(PredicateBuilderTypeEnum.TOKEN, theSourceJoinColumn, theSearchParam.getName(), () -> mySqlBuilder.addTokenPredicateBuilder(theSourceJoinColumn)).getResult();
TokenPredicateBuilder join = createOrReusePredicateBuilder(PredicateBuilderTypeEnum.TOKEN, theSourceJoinColumn, paramName, () -> mySqlBuilder.addTokenPredicateBuilder(theSourceJoinColumn)).getResult();
if (theList.get(0).getMissing() != null) {
return join.createPredicateParamMissingForNonReference(theResourceName, theSearchParam.getName(), theList.get(0).getMissing(), theRequestPartitionId);
return join.createPredicateParamMissingForNonReference(theResourceName, paramName, theList.get(0).getMissing(), theRequestPartitionId);
}
Condition predicate = join.createPredicateToken(tokens, theResourceName, theSearchParam, theOperation, theRequestPartitionId);
Condition predicate = join.createPredicateToken(tokens, theResourceName, theSpnamePrefix, theSearchParam, theOperation, theRequestPartitionId);
return join.combineWithRequestPartitionIdPredicate(theRequestPartitionId, predicate);
}
public Condition createPredicateUri(@Nullable DbColumn theSourceJoinColumn,
String theResourceName,
RuntimeSearchParam theSearchParam,
List<? extends IQueryParameterType> theList,
SearchFilterParser.CompareOperation theOperation,
RequestDetails theRequestDetails,
RequestPartitionId theRequestPartitionId) {
public Condition createPredicateUri(@Nullable DbColumn theSourceJoinColumn, String theResourceName,
String theSpnamePrefix, RuntimeSearchParam theSearchParam, List<? extends IQueryParameterType> theList,
SearchFilterParser.CompareOperation theOperation, RequestDetails theRequestDetails,
RequestPartitionId theRequestPartitionId) {
String paramName = theSearchParam.getName();
String paramName = getParamNameWithPrefix(theSpnamePrefix, theSearchParam.getName());
UriPredicateBuilder join = mySqlBuilder.addUriPredicateBuilder(theSourceJoinColumn);
if (theList.get(0).getMissing() != null) {
@ -952,7 +1046,7 @@ public class QueryStack {
}
@Nullable
public Condition searchForIdsWithAndOr(@Nullable DbColumn theSourceJoinColumn, String theResourceName, String theParamName, List<List<IQueryParameterType>> theAndOrParams, RequestDetails theRequest, RequestPartitionId theRequestPartitionId) {
public Condition searchForIdsWithAndOr(@Nullable DbColumn theSourceJoinColumn, String theResourceName, String theParamName, List<List<IQueryParameterType>> theAndOrParams, RequestDetails theRequest, RequestPartitionId theRequestPartitionId, SearchContainedEnum theSearchContainedMode) {
if (theAndOrParams.isEmpty()) {
return null;
@ -998,7 +1092,7 @@ public class QueryStack {
DateParam param = (DateParam) nextAnd.get(0);
operation = toOperation(param.getPrefix());
}
andPredicates.add(createPredicateDate(theSourceJoinColumn, theResourceName, nextParamDef, nextAnd, operation, theRequestPartitionId));
andPredicates.add(createPredicateDate(theSourceJoinColumn, theResourceName, null, nextParamDef, nextAnd, operation, theRequestPartitionId));
//andPredicates.add(createPredicateDate(theSourceJoinColumn, theResourceName, nextParamDef, nextAnd, null, theRequestPartitionId));
}
break;
@ -1009,17 +1103,20 @@ public class QueryStack {
QuantityParam param = (QuantityParam) nextAnd.get(0);
operation = toOperation(param.getPrefix());
}
andPredicates.add(createPredicateQuantity(theSourceJoinColumn, theResourceName, nextParamDef, nextAnd, operation, theRequestPartitionId));
andPredicates.add(createPredicateQuantity(theSourceJoinColumn, theResourceName, null, nextParamDef, nextAnd, operation, theRequestPartitionId));
}
break;
case REFERENCE:
for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) {
andPredicates.add(createPredicateReference(theSourceJoinColumn, theResourceName, theParamName, nextAnd, null, theRequest, theRequestPartitionId));
if (theSearchContainedMode.equals(SearchContainedEnum.TRUE))
andPredicates.add(createPredicateReferenceForContainedResource(theSourceJoinColumn, theResourceName, theParamName, nextParamDef, nextAnd, null, theRequest, theRequestPartitionId));
else
andPredicates.add(createPredicateReference(theSourceJoinColumn, theResourceName, theParamName, nextAnd, null, theRequest, theRequestPartitionId));
}
break;
case STRING:
for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) {
andPredicates.add(createPredicateString(theSourceJoinColumn, theResourceName, nextParamDef, nextAnd, SearchFilterParser.CompareOperation.sw, theRequestPartitionId));
andPredicates.add(createPredicateString(theSourceJoinColumn, theResourceName, null, nextParamDef, nextAnd, SearchFilterParser.CompareOperation.sw, theRequestPartitionId));
}
break;
case TOKEN:
@ -1027,23 +1124,23 @@ public class QueryStack {
if ("Location.position".equals(nextParamDef.getPath())) {
andPredicates.add(createPredicateCoords(theSourceJoinColumn, theResourceName, nextParamDef, nextAnd, theRequestPartitionId));
} else {
andPredicates.add(createPredicateToken(theSourceJoinColumn, theResourceName, nextParamDef, nextAnd, null, theRequestPartitionId));
andPredicates.add(createPredicateToken(theSourceJoinColumn, theResourceName, null, nextParamDef, nextAnd, null, theRequestPartitionId));
}
}
break;
case NUMBER:
for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) {
andPredicates.add(createPredicateNumber(theSourceJoinColumn, theResourceName, nextParamDef, nextAnd, null, theRequestPartitionId));
andPredicates.add(createPredicateNumber(theSourceJoinColumn, theResourceName, null, nextParamDef, nextAnd, null, theRequestPartitionId));
}
break;
case COMPOSITE:
for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) {
andPredicates.add(createPredicateComposite(theSourceJoinColumn, theResourceName, nextParamDef, nextAnd, theRequestPartitionId));
andPredicates.add(createPredicateComposite(theSourceJoinColumn, theResourceName, null, nextParamDef, nextAnd, theRequestPartitionId));
}
break;
case URI:
for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) {
andPredicates.add(createPredicateUri(theSourceJoinColumn, theResourceName, nextParamDef, nextAnd, SearchFilterParser.CompareOperation.eq, theRequest, theRequestPartitionId));
andPredicates.add(createPredicateUri(theSourceJoinColumn, theResourceName, null, nextParamDef, nextAnd, SearchFilterParser.CompareOperation.eq, theRequest, theRequestPartitionId));
}
break;
case HAS:
@ -1246,4 +1343,48 @@ public class QueryStack {
return parameter.substring(parameter.indexOf(".") + 1);
}
private IQueryParameterType toParameterType(RuntimeSearchParam theParam) {
IQueryParameterType qp;
switch (theParam.getParamType()) {
case DATE:
qp = new DateParam();
break;
case NUMBER:
qp = new NumberParam();
break;
case QUANTITY:
qp = new QuantityParam();
break;
case STRING:
qp = new StringParam();
break;
case TOKEN:
qp = new TokenParam();
break;
case COMPOSITE:
List<RuntimeSearchParam> compositeOf = theParam.getCompositeOf();
if (compositeOf.size() != 2) {
throw new InternalErrorException("Parameter " + theParam.getName() + " has " + compositeOf.size() + " composite parts. Don't know how handlt this.");
}
IQueryParameterType leftParam = toParameterType(compositeOf.get(0));
IQueryParameterType rightParam = toParameterType(compositeOf.get(1));
qp = new CompositeParam<>(leftParam, rightParam);
break;
case URI:
qp = new UriParam();
break;
default:
throw new InvalidRequestException("The search type: " + theParam.getParamType() + " is not supported.");
}
return qp;
}
public static String getParamNameWithPrefix(String theSpnamePrefix, String theParamName) {
if (isBlank(theSpnamePrefix))
return theParamName;
return theSpnamePrefix + "." + theParamName;
}
}

View File

@ -60,6 +60,7 @@ import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
import ca.uhn.fhir.jpa.searchparam.util.Dstu3DistanceHelper;
import ca.uhn.fhir.jpa.searchparam.util.LastNParameterHelper;
import ca.uhn.fhir.jpa.searchparam.SearchContainedEnum;
import ca.uhn.fhir.jpa.util.BaseIterator;
import ca.uhn.fhir.jpa.util.CurrentThreadCaptureQueriesListener;
import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster;
@ -91,7 +92,6 @@ import com.healthmarketscience.sqlbuilder.Condition;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.IdType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@ -216,6 +216,8 @@ public class SearchBuilder implements ISearchBuilder {
attemptCompositeUniqueSpProcessing(theQueryStack, theParams, theRequest);
}
SearchContainedEnum searchContainedMode = theParams.getSearchContainedMode();
// Handle each parameter
List<String> paramNames = new ArrayList<>(myParams.keySet());
for (String nextParamName : paramNames) {
@ -224,7 +226,7 @@ public class SearchBuilder implements ISearchBuilder {
continue;
}
List<List<IQueryParameterType>> andOrParams = myParams.get(nextParamName);
Condition predicate = theQueryStack.searchForIdsWithAndOr(null, myResourceName, nextParamName, andOrParams, theRequest, myRequestPartitionId);
Condition predicate = theQueryStack.searchForIdsWithAndOr(null, myResourceName, nextParamName, andOrParams, theRequest, myRequestPartitionId, searchContainedMode);
if (predicate != null) {
theSearchSqlBuilder.addPredicate(predicate);
}

View File

@ -43,6 +43,7 @@ import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage;
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.SearchContainedEnum;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster;
import ca.uhn.fhir.model.api.IQueryParameterType;
@ -394,7 +395,7 @@ public class ResourceLinkPredicateBuilder extends BaseJoiningPredicateBuilder {
List<Condition> andPredicates = new ArrayList<>();
List<List<IQueryParameterType>> chainParamValues = Collections.singletonList(orValues);
andPredicates.add(childQueryFactory.searchForIdsWithAndOr(myColumnTargetResourceId, subResourceName, chain, chainParamValues, theRequest, theRequestPartitionId));
andPredicates.add(childQueryFactory.searchForIdsWithAndOr(myColumnTargetResourceId, subResourceName, chain, chainParamValues, theRequest, theRequestPartitionId, SearchContainedEnum.FALSE));
orPredicates.add(toAndPredicate(andPredicates));

View File

@ -25,6 +25,7 @@ import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.dao.predicate.SearchFilterParser;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
import ca.uhn.fhir.jpa.search.builder.sql.SearchQueryBuilder;
import ca.uhn.fhir.jpa.search.builder.QueryStack;
import ca.uhn.fhir.model.api.IPrimitiveDatatype;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.param.StringParam;
@ -75,11 +76,13 @@ public class StringPredicateBuilder extends BaseSearchParamPredicateBuilder {
public Condition createPredicateString(IQueryParameterType theParameter,
String theResourceName,
String theSpnamePrefix,
RuntimeSearchParam theSearchParam,
StringPredicateBuilder theFrom,
SearchFilterParser.CompareOperation operation) {
String rawSearchTerm;
String paramName = theSearchParam.getName();
String paramName = QueryStack.getParamNameWithPrefix(theSpnamePrefix, theSearchParam.getName());
if (theParameter instanceof TokenParam) {
TokenParam id = (TokenParam) theParameter;
if (!id.isText()) {

View File

@ -29,6 +29,7 @@ import ca.uhn.fhir.jpa.dao.LegacySearchBuilder;
import ca.uhn.fhir.jpa.dao.predicate.SearchFilterParser;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken;
import ca.uhn.fhir.jpa.search.builder.QueryStack;
import ca.uhn.fhir.jpa.search.builder.sql.SearchQueryBuilder;
import ca.uhn.fhir.jpa.term.api.ITermReadSvc;
import ca.uhn.fhir.model.api.IQueryParameterType;
@ -93,11 +94,13 @@ public class TokenPredicateBuilder extends BaseSearchParamPredicateBuilder {
public Condition createPredicateToken(Collection<IQueryParameterType> theParameters,
String theResourceName,
String theSpnamePrefix,
RuntimeSearchParam theSearchParam,
RequestPartitionId theRequestPartitionId) {
return createPredicateToken(
theParameters,
theResourceName,
theSpnamePrefix,
theSearchParam,
null,
theRequestPartitionId);
@ -105,11 +108,15 @@ public class TokenPredicateBuilder extends BaseSearchParamPredicateBuilder {
public Condition createPredicateToken(Collection<IQueryParameterType> theParameters,
String theResourceName,
String theSpnamePrefix,
RuntimeSearchParam theSearchParam,
SearchFilterParser.CompareOperation theOperation,
RequestPartitionId theRequestPartitionId) {
final List<FhirVersionIndependentConcept> codes = new ArrayList<>();
String paramName = theSearchParam.getName();
String paramName = QueryStack.getParamNameWithPrefix(theSpnamePrefix, theSearchParam.getName());
SearchFilterParser.CompareOperation operation = theOperation;
@ -197,12 +204,12 @@ public class TokenPredicateBuilder extends BaseSearchParamPredicateBuilder {
long hashIdentity = BaseResourceIndexedSearchParam.calculateHashIdentity(getPartitionSettings(), theRequestPartitionId, theResourceName, paramName);
Condition hashIdentityPredicate = BinaryCondition.equalTo(getColumnHashIdentity(), generatePlaceholder(hashIdentity));
Condition hashValuePredicate = createPredicateOrList(theResourceName, theSearchParam.getName(), sortedCodesList, false);
Condition hashValuePredicate = createPredicateOrList(theResourceName, paramName, sortedCodesList, false);
predicate = toAndPredicate(hashIdentityPredicate, hashValuePredicate);
} else {
predicate = createPredicateOrList(theResourceName, theSearchParam.getName(), sortedCodesList, true);
predicate = createPredicateOrList(theResourceName, paramName, sortedCodesList, true);
}

View File

@ -1,57 +1,310 @@
package ca.uhn.fhir.jpa.dao.r4;
import org.hl7.fhir.r4.model.Observation;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Reference;
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 org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.jupiter.api.AfterAll;
import org.hl7.fhir.r4.model.Address;
import org.hl7.fhir.r4.model.Address.AddressUse;
import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.DateTimeType;
import org.hl7.fhir.r4.model.Encounter;
import org.hl7.fhir.r4.model.Encounter.EncounterParticipantComponent;
import org.hl7.fhir.r4.model.Encounter.EncounterStatus;
import org.hl7.fhir.r4.model.Enumerations.AdministrativeGender;
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.Practitioner;
import org.hl7.fhir.r4.model.Reference;
import org.hl7.fhir.r4.model.ServiceRequest;
import org.hl7.fhir.r4.model.ServiceRequest.ServiceRequestIntent;
import org.hl7.fhir.r4.model.ServiceRequest.ServiceRequestStatus;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import ca.uhn.fhir.jpa.searchparam.SearchContainedEnum;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.util.TestUtil;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.ReferenceParam;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
public class FhirResourceDaoR4ContainedTest extends BaseJpaR4Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4ContainedTest.class);
@Test
public void before() {
myDaoConfig.setIndexContainedResources(true);
@BeforeEach
public void before() throws Exception {
myModelConfig.setIndexOnContainedResources(true);
}
@Test
public void testIndexContained() {
Patient p = new Patient();
p.setId("#some_patient");
p.addName().setFamily("MYFAMILY").addGiven("MYGIVEN");
Observation o1 = new Observation();
o1.getCode().setText("Some Observation");
o1.setSubject(new Reference(p));
IIdType oid1 = myObservationDao.create(o1, mySrd).getId().toUnqualifiedVersionless();
Observation o2 = new Observation();
o2.getCode().setText("Some Observation");
o2.setSubject(new Reference(p));
IIdType oid2 = myObservationDao.create(o2, mySrd).getId().toUnqualifiedVersionless();
Patient p2 = new Patient();
p2.addName().setFamily("MYFAMILY").addGiven("MYGIVEN");
IIdType pid2 = myPatientDao.create(p2, mySrd).getId().toUnqualifiedVersionless();
@AfterEach
public void after() throws Exception {
myModelConfig.setIndexOnContainedResources(false);
}
@Test
public void testCreateSimpleContainedResourceIndexWithGeneratedId() {
Patient p = new Patient();
p.addName().setFamily("Smith").addGiven("John");
ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(o2));
Observation obs = new Observation();
obs.getCode().setText("Some Observation");
obs.setSubject(new Reference(p));
ourLog.info("Input: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
IIdType id = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
Observation createdObs = myObservationDao.read(id);
ourLog.info("Output: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(createdObs));
runInTransaction(()->{
Long i = myEntityManager
.createQuery("SELECT count(s) FROM ResourceIndexedSearchParamString s WHERE s.myParamName = 'subject.family' AND s.myResourceType = 'Observation'", Long.class)
.getSingleResult();
assertEquals(1L, i.longValue());
});
SearchParameterMap map;
map = new SearchParameterMap();
map.add("subject", new ReferenceParam("name", "Smith"));
map.setSearchContainedMode(SearchContainedEnum.TRUE);
// map = new SearchParameterMap();
// map.add(Observation.SP_CODE, new TokenParam(null, "some observation").setModifier(TokenParamModifier.TEXT));
// assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(map)), containsInAnyOrder(toValues(id1, id2)));
assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(map)), containsInAnyOrder(toValues(id)));
}
@Test
public void testCreateSimpleContainedResourceIndexUserDefinedId() {
Patient p = new Patient();
p.setId("fooId");
p.addName().setFamily("Smith").addGiven("John");
Observation obs = new Observation();
obs.getCode().setText("Some Observation");
obs.getContained().add(p);
obs.getSubject().setReference("#fooId");
ourLog.info("Input: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
IIdType id = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
Observation createdObs = myObservationDao.read(id);
ourLog.info("Output: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(createdObs));
runInTransaction(()->{
Long i = myEntityManager
.createQuery("SELECT count(s) FROM ResourceIndexedSearchParamString s WHERE s.myParamName = 'subject.family' AND s.myResourceType = 'Observation'", Long.class)
.getSingleResult();
assertEquals(1L, i.longValue());
});
SearchParameterMap map;
map = new SearchParameterMap();
map.add("subject", new ReferenceParam("name", "Smith"));
map.setSearchContainedMode(SearchContainedEnum.TRUE);
assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(map)), containsInAnyOrder(toValues(id)));
}
// TODO: make sure match URLs don't delete
}
@Test
public void testCreateMultipleContainedResourceIndex() {
Practitioner prac1 = new Practitioner();
prac1.setId("prac1");
prac1.setActive(true);
prac1.setGender(AdministrativeGender.FEMALE);
prac1.addName().setFamily("Smith").addGiven("John");
Address address = prac1.addAddress();
address.setUse(AddressUse.WORK);
address.addLine("534 Erewhon St");
address.setCity("PleasantVille");
address.setState("NY");
address.setPostalCode("12345");
Organization org1 = new Organization();
org1.setId("org1");
org1.setActive(true);
org1.setName("org name 1");
Organization org2 = new Organization();
org2.setId("org2");
org2.setActive(false);
org2.setName("org name 2");
Patient patient = new Patient();
patient.getContained().add(prac1);
patient.getContained().add(org1);
patient.getContained().add(org2);
patient.addName().setFamily("Doe").addGiven("Jane");
patient.addGeneralPractitioner().setReference("#prac1");
patient.addGeneralPractitioner().setReference("#org1");
patient.getManagingOrganization().setReference("#org2");
ourLog.info("Input: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(patient));
IIdType id = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
Patient createdPatient = myPatientDao.read(id);
ourLog.info("Output: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(createdPatient));
runInTransaction(()->{
Long i = myEntityManager
.createQuery("SELECT count(s) FROM ResourceIndexedSearchParamString s WHERE s.myParamName = 'general-practitioner.family' AND s.myResourceType = 'Patient'", Long.class)
.getSingleResult();
assertEquals(1L, i.longValue());
i = myEntityManager
.createQuery("SELECT count(s) FROM ResourceIndexedSearchParamString s WHERE s.myParamName = 'general-practitioner.name' AND s.myResourceType = 'Patient'", Long.class)
.getSingleResult();
assertEquals(3L, i.longValue());
i = myEntityManager
.createQuery("SELECT count(s) FROM ResourceIndexedSearchParamString s WHERE s.myParamName = 'organization.name' AND s.myResourceType = 'Patient'", Long.class)
.getSingleResult();
assertEquals(1L, i.longValue());
});
SearchParameterMap map;
map = new SearchParameterMap();
map.add("general-practitioner", new ReferenceParam("family", "Smith"));
map.setSearchContainedMode(SearchContainedEnum.TRUE);
assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(map)), containsInAnyOrder(toValues(id)));
}
@Test
public void testCreateComplexContainedResourceIndex() {
Encounter encounter = new Encounter();
encounter.setStatus(EncounterStatus.ARRIVED);
Patient patient = new Patient();
patient.setId("patient1");
patient.addName().setFamily("Doe").addGiven("Jane");
encounter.getSubject().setReference("#patient1");
encounter.getContained().add(patient);
ServiceRequest serviceRequest = new ServiceRequest();
serviceRequest.setId("serviceRequest1");
serviceRequest.setStatus(ServiceRequestStatus.ACTIVE);
serviceRequest.setIntent(ServiceRequestIntent.ORDER);
serviceRequest.setAuthoredOnElement(new DateTimeType("2021-02-23"));
encounter.addBasedOn().setReference("#serviceRequest1");
encounter.getContained().add(serviceRequest);
Practitioner prac1 = new Practitioner();
prac1.setId("prac1");
prac1.setActive(true);
prac1.setGender(AdministrativeGender.FEMALE);
prac1.addName().setFamily("Smith").addGiven("John");
EncounterParticipantComponent participient = encounter.addParticipant();
participient.getIndividual().setReference("#prac1");
encounter.getContained().add(prac1);
Observation obs = new Observation();
obs.setId("obs1");
obs.addIdentifier().setSystem("urn:system").setValue("FOO");
obs.getSubject().setReference("#patient1");
CodeableConcept cc = obs.getCode();
cc.addCoding().setCode("2345-7").setSystem("http://loinc.org");
encounter.addReasonReference().setReference("#obs1");
encounter.getContained().add(obs);
ourLog.info("Input: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(encounter));
IIdType id = myEncounterDao.create(encounter, mySrd).getId().toUnqualifiedVersionless();
Encounter createdEncounter = myEncounterDao.read(id);
ourLog.info("Output: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(createdEncounter));
runInTransaction(()->{
// The practitioner
Long i = myEntityManager
.createQuery("SELECT count(s) FROM ResourceIndexedSearchParamString s WHERE s.myParamName = 'participant.family' AND s.myResourceType = 'Encounter'", Long.class)
.getSingleResult();
assertEquals(1L, i.longValue());
// The Patient
i = myEntityManager
.createQuery("SELECT count(s) FROM ResourceIndexedSearchParamString s WHERE s.myParamName = 'subject.family' AND s.myResourceType = 'Encounter'", Long.class)
.getSingleResult();
assertEquals(1L, i.longValue());
// The Observation
i = myEntityManager
.createQuery("SELECT count(s) FROM ResourceIndexedSearchParamToken s WHERE s.myParamName = 'reason-reference.code' AND s.myResourceType = 'Encounter'", Long.class)
.getSingleResult();
assertEquals(1L, i.longValue());
i = myEntityManager
.createQuery("SELECT count(s) FROM ResourceIndexedSearchParamToken s WHERE s.myParamName = 'reason-reference.combo-code' AND s.myResourceType = 'Encounter'", Long.class)
.getSingleResult();
assertEquals(1L, i.longValue());
// The ServiceRequest
i = myEntityManager
.createQuery("SELECT count(s) FROM ResourceIndexedSearchParamDate s WHERE s.myParamName = 'based-on.authored' AND s.myResourceType = 'Encounter'", Long.class)
.getSingleResult();
assertEquals(1L, i.longValue());
});
SearchParameterMap map;
map = new SearchParameterMap();
map.add("based-on", new ReferenceParam("authored", "2021-02-23"));
map.setSearchContainedMode(SearchContainedEnum.TRUE);
assertThat(toUnqualifiedVersionlessIdValues(myEncounterDao.search(map)), containsInAnyOrder(toValues(id)));
}
@Test
public void testSearchWithNotSupportedSearchType() {
SearchParameterMap map;
map = new SearchParameterMap();
map.add("subject", new ReferenceParam("near", "toronto"));
map.setSearchContainedMode(SearchContainedEnum.TRUE);
try {
IBundleProvider outcome = myObservationDao.search(map);
outcome.getResources(0, 1).get(0);
fail();
} catch (InvalidRequestException e) {
assertEquals(e.getMessage(), "The search type: SPECIAL is not supported.");
}
}
@Test
public void testSearchWithNotSupportedSearchParameter() {
SearchParameterMap map;
map = new SearchParameterMap();
map.add("subject", new ReferenceParam("marital-status", "M"));
map.setSearchContainedMode(SearchContainedEnum.TRUE);
try {
IBundleProvider outcome = myObservationDao.search(map);
outcome.getResources(0, 1).get(0);
fail();
} catch (InvalidRequestException e) {
assertEquals(e.getMessage(), "Unknown search parameter name: subject.marital-status.");
}
}
}

View File

@ -284,7 +284,7 @@ public class SearchParamExtractorR4Test {
SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), new PartitionSettings(), ourCtx, mySearchParamRegistry);
RuntimeSearchParam param = mySearchParamRegistry.getActiveSearchParam("Encounter", "location");
assertNotNull(param);
ISearchParamExtractor.SearchParamSet<PathAndRef> links = extractor.extractResourceLinks(enc);
ISearchParamExtractor.SearchParamSet<PathAndRef> links = extractor.extractResourceLinks(enc, false);
assertEquals(1, links.size());
assertEquals("location", links.iterator().next().getSearchParamName());
assertEquals("Encounter.location.location", links.iterator().next().getPath());
@ -299,7 +299,7 @@ public class SearchParamExtractorR4Test {
SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), new PartitionSettings(), ourCtx, mySearchParamRegistry);
RuntimeSearchParam param = mySearchParamRegistry.getActiveSearchParam("Consent", Consent.SP_SOURCE_REFERENCE);
assertNotNull(param);
ISearchParamExtractor.SearchParamSet<PathAndRef> links = extractor.extractResourceLinks(consent);
ISearchParamExtractor.SearchParamSet<PathAndRef> links = extractor.extractResourceLinks(consent, false);
assertEquals(1, links.size());
assertEquals("Consent.source", links.iterator().next().getPath());
assertEquals("Consent/999", ((Reference) links.iterator().next().getRef()).getReference());
@ -334,7 +334,7 @@ public class SearchParamExtractorR4Test {
patient.addExtension("http://patext", new Reference("Organization/AAA"));
SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), new PartitionSettings(), ourCtx, mySearchParamRegistry);
ISearchParamExtractor.SearchParamSet<PathAndRef> links = extractor.extractResourceLinks(patient);
ISearchParamExtractor.SearchParamSet<PathAndRef> links = extractor.extractResourceLinks(patient, false);
assertEquals(1, links.size());
}

View File

@ -108,12 +108,14 @@ public class ResourceProviderHasParamR4Test extends BaseResourceProviderR4Test {
obs.addIdentifier().setSystem("urn:system").setValue("NOLINK");
obs.setDevice(new Reference(devId));
myObservationDao.create(obs, mySrd);
ourLog.info("Observation: \n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
}
String uri = ourServerBase + "/Patient?_has:Observation:subject:identifier=" + UrlUtil.escapeUrlParam("urn:system|FOO");
List<String> ids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
assertThat(ids, contains(pid0.getValue()));
}
@Test

View File

@ -0,0 +1,978 @@
package ca.uhn.fhir.jpa.provider.r4;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;
import org.apache.commons.io.IOUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.CarePlan;
import org.hl7.fhir.r4.model.CarePlan.CarePlanIntent;
import org.hl7.fhir.r4.model.CarePlan.CarePlanStatus;
import org.hl7.fhir.r4.model.ClinicalImpression;
import org.hl7.fhir.r4.model.ClinicalImpression.ClinicalImpressionStatus;
import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.DecimalType;
import org.hl7.fhir.r4.model.Encounter;
import org.hl7.fhir.r4.model.Encounter.EncounterStatus;
import org.hl7.fhir.r4.model.HumanName;
import org.hl7.fhir.r4.model.Observation;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Quantity;
import org.hl7.fhir.r4.model.Resource;
import org.hl7.fhir.r4.model.RiskAssessment;
import org.hl7.fhir.r4.model.RiskAssessment.RiskAssessmentStatus;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.parser.StrictErrorHandler;
import ca.uhn.fhir.rest.client.interceptor.CapturingInterceptor;
import ca.uhn.fhir.util.UrlUtil;
public class ResourceProviderR4SearchContainedTest extends BaseResourceProviderR4Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResourceProviderR4SearchContainedTest.class);
private CapturingInterceptor myCapturingInterceptor = new CapturingInterceptor();
@Autowired
@Qualifier("myClinicalImpressionDaoR4")
protected IFhirResourceDao<ClinicalImpression> myClinicalImpressionDao;
@Override
@AfterEach
public void after() throws Exception {
super.after();
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());
myClient.unregisterInterceptor(myCapturingInterceptor);
myModelConfig.setIndexOnContainedResources(false);
}
@BeforeEach
@Override
public void before() throws Exception {
super.before();
myFhirCtx.setParserErrorHandler(new StrictErrorHandler());
myDaoConfig.setAllowMultipleDelete(true);
myClient.registerInterceptor(myCapturingInterceptor);
myDaoConfig.setSearchPreFetchThresholds(new DaoConfig().getSearchPreFetchThresholds());
myModelConfig.setIndexOnContainedResources(true);
}
@BeforeEach
public void beforeDisableResultReuse() {
myDaoConfig.setReuseCachedSearchResultsForMillis(null);
}
@Test
public void testContainedSearchByName() throws Exception {
IIdType oid1;
{
Patient p = new Patient();
p.setId("patient1");
p.addName().setFamily("Smith").addGiven("John");
Observation obs = new Observation();
obs.getCode().setText("Observation 1");
obs.getContained().add(p);
obs.getSubject().setReference("#patient1");
oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
ourLog.info("Input: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
}
{
Patient p = new Patient();
p.setId("patient1");
p.addName().setFamily("Doe").addGiven("Jane");
Observation obs = new Observation();
obs.getCode().setText("Observation 2");
obs.getContained().add(p);
obs.getSubject().setReference("#patient1");
myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
ourLog.info("Input: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
}
{
Patient p = new Patient();
p.setId("patient1");
p.addName().setFamily("Jones").addGiven("Peter");
Observation obs = new Observation();
obs.getCode().setText("Observation 2");
obs.getContained().add(p);
obs.getSubject().setReference("#patient1");
myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
ourLog.info("Input: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
}
//-- Simple name match
String uri = ourServerBase + "/Observation?subject.name=Smith&_contained=true";
List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
assertEquals(1L, oids.size());
assertThat(oids, contains(oid1.getValue()));
//-- Simple name match with or
uri = ourServerBase + "/Observation?subject.name=Smith,Jane&_contained=true";
oids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
assertEquals(2L, oids.size());
//assertEquals(oids.toString(), "[Observation/1, Observation/2]");
//-- Simple name match with qualifier
uri = ourServerBase + "/Observation?subject.name:exact=Smith&_contained=true";
oids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
assertEquals(1L, oids.size());
assertThat(oids, contains(oid1.getValue()));
//-- Simple name match with and
uri = ourServerBase + "/Observation?subject.family=Smith&subject.given=John&_contained=true";
oids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
assertEquals(1L, oids.size());
assertThat(oids, contains(oid1.getValue()));
//-- Simple name match with both, default to normal search, found 0
uri = ourServerBase + "/Observation?subject.name=Smith&_contained=both";
oids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
assertEquals(0L, oids.size());
}
@Test
public void testContainedSearchByDate() throws Exception {
IIdType oid1;
IIdType oid3;
{
Patient p = new Patient();
p.setId("patient1");
p.addName().setFamily("Smith").addGiven("John");
p.getBirthDateElement().setValueAsString("2000-01-01");
Observation obs = new Observation();
obs.getCode().setText("Observation 1");
obs.getContained().add(p);
obs.getSubject().setReference("#patient1");
oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
ourLog.info("Input: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
}
{
Patient p = new Patient();
p.setId("patient1");
p.addName().setFamily("Doe").addGiven("Jane");
p.getBirthDateElement().setValueAsString("2000-02-01");
Observation obs = new Observation();
obs.getCode().setText("Observation 2");
obs.getContained().add(p);
obs.getSubject().setReference("#patient1");
myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
ourLog.info("Input: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
}
{
Patient p = new Patient();
p.setId("patient1");
p.addName().setFamily("Jones").addGiven("Peter");
p.getBirthDateElement().setValueAsString("2000-03-01");
Observation obs = new Observation();
obs.getCode().setText("Observation 2");
obs.getContained().add(p);
obs.getSubject().setReference("#patient1");
oid3 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
ourLog.info("Input: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
}
//-- Search by date default op
String uri = ourServerBase + "/Observation?subject.birthdate=2000-01-01&_contained=true";
List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
assertEquals(1L, oids.size());
assertThat(oids, contains(oid1.getValue()));
//-- Search by date op=eq
uri = ourServerBase + "/Observation?subject.birthdate=eq2000-01-01&_contained=true";
oids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
assertEquals(1L, oids.size());
assertThat(oids, contains(oid1.getValue()));
//-- Search by date op=eq, with or
uri = ourServerBase + "/Observation?subject.birthdate=2000-01-01,2000-02-01&_contained=true";
oids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
assertEquals(2L, oids.size());
//assertEquals(oids.toString(), "[Observation/1, Observation/2]");
//-- Simple name match with op = gt
uri = ourServerBase + "/Observation?subject.birthdate=gt2000-02-10&_contained=true";
oids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
assertEquals(1L, oids.size());
assertThat(oids, contains(oid3.getValue()));
//-- Simple name match with AND
uri = ourServerBase + "/Observation?subject.family=Smith&subject.birthdate=eq2000-01-01&_contained=true";
oids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
assertEquals(1L, oids.size());
assertThat(oids, contains(oid1.getValue()));
//-- Simple name match with AND - not found
uri = ourServerBase + "/Observation?subject.family=Smith&subject.birthdate=eq2000-02-01&_contained=true";
oids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
assertEquals(0L, oids.size());
}
@Test
public void testContainedSearchByNumber() throws Exception {
IIdType cid1;
{
Patient p = new Patient();
p.setId("patient1");
p.addName().setFamily("Smith").addGiven("John");
p.getBirthDateElement().setValueAsString("2000-01-01");
RiskAssessment risk = new RiskAssessment();
risk.setId("risk1");
risk.setStatus(RiskAssessmentStatus.CORRECTED);
risk.getSubject().setReference("#patient1");
risk.getPredictionFirstRep().setProbability(new DecimalType(2));
ClinicalImpression imp = new ClinicalImpression();
imp.setStatus(ClinicalImpressionStatus.COMPLETED);
imp.getContained().add(p);
imp.getSubject().setReference("#patient1");
imp.getContained().add(risk);
imp.getInvestigationFirstRep().getItemFirstRep().setReference("#risk1");
ourLog.info("Input: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(imp));
cid1 = myClinicalImpressionDao.create(imp, mySrd).getId().toUnqualifiedVersionless();
ClinicalImpression createdImp = myClinicalImpressionDao.read(cid1);
ourLog.info("Output: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(createdImp));
}
{
Patient p = new Patient();
p.setId("patient1");
p.addName().setFamily("Smith").addGiven("John");
p.getBirthDateElement().setValueAsString("2000-01-01");
RiskAssessment risk = new RiskAssessment();
risk.setId("risk1");
risk.setStatus(RiskAssessmentStatus.CORRECTED);
risk.getSubject().setReference("#patient1");
risk.getPredictionFirstRep().setProbability(new DecimalType(5));
ClinicalImpression imp = new ClinicalImpression();
imp.setStatus(ClinicalImpressionStatus.COMPLETED);
imp.getContained().add(p);
imp.getSubject().setReference("#patient1");
imp.getContained().add(risk);
imp.getInvestigationFirstRep().getItemFirstRep().setReference("#risk1");
ourLog.info("Input: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(imp));
IIdType cid2 = myClinicalImpressionDao.create(imp, mySrd).getId().toUnqualifiedVersionless();
ClinicalImpression createdImp = myClinicalImpressionDao.read(cid2);
ourLog.info("Output: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(createdImp));
}
{
Patient p = new Patient();
p.setId("patient1");
p.addName().setFamily("Smith").addGiven("John");
p.getBirthDateElement().setValueAsString("2000-01-01");
RiskAssessment risk = new RiskAssessment();
risk.setId("risk1");
risk.setStatus(RiskAssessmentStatus.CORRECTED);
risk.getSubject().setReference("#patient1");
risk.getPredictionFirstRep().setProbability(new DecimalType(10));
ClinicalImpression imp = new ClinicalImpression();
imp.setStatus(ClinicalImpressionStatus.COMPLETED);
imp.getContained().add(p);
imp.getSubject().setReference("#patient1");
imp.getContained().add(risk);
imp.getInvestigationFirstRep().getItemFirstRep().setReference("#risk1");
ourLog.info("Input: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(imp));
IIdType cid3 = myClinicalImpressionDao.create(imp, mySrd).getId().toUnqualifiedVersionless();
ClinicalImpression createdImp = myClinicalImpressionDao.read(cid3);
ourLog.info("Output: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(createdImp));
}
//-- Search by number
String uri = ourServerBase + "/ClinicalImpression?investigation.probability=2&_contained=true";
List<String> cids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
assertEquals(1L, cids.size());
assertThat(cids, contains(cid1.getValue()));
//-- Search by number with op = eq
uri = ourServerBase + "/ClinicalImpression?investigation.probability=eq2&_contained=true";
cids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
assertEquals(1L, cids.size());
assertThat(cids, contains(cid1.getValue()));
//-- Search by number with op = eq and or
uri = ourServerBase + "/ClinicalImpression?investigation.probability=eq2,10&_contained=true";
cids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
assertEquals(2L, cids.size());
//-- Search by number with op = lt
uri = ourServerBase + "/ClinicalImpression?investigation.probability=lt4&_contained=true";
cids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
assertEquals(1L, cids.size());
assertThat(cids, contains(cid1.getValue()));
}
@Test
public void testContainedSearchByQuantity() throws Exception {
IIdType eid1;
{
Encounter encounter = new Encounter();
encounter.setStatus(EncounterStatus.ARRIVED);
Patient patient = new Patient();
patient.setId("patient1");
patient.addName().setFamily("Doe").addGiven("Jane");
encounter.getSubject().setReference("#patient1");
encounter.getContained().add(patient);
Observation obs = new Observation();
obs.setId("obs1");
obs.addIdentifier().setSystem("urn:system").setValue("FOO");
obs.getSubject().setReference("#patient1");
CodeableConcept cc = obs.getCode();
cc.addCoding().setCode("2345-7").setSystem("http://loinc.org");
Quantity quantity = obs.getValueQuantity();
quantity.setValue(200);
encounter.addReasonReference().setReference("#obs1");
encounter.getContained().add(obs);
ourLog.info("Input: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(encounter));
eid1 = myEncounterDao.create(encounter, mySrd).getId().toUnqualifiedVersionless();
Encounter createdEncounter = myEncounterDao.read(eid1);
ourLog.info("Output: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(createdEncounter));
}
{
Encounter encounter = new Encounter();
encounter.setStatus(EncounterStatus.ARRIVED);
Patient patient = new Patient();
patient.setId("patient1");
patient.addName().setFamily("Doe").addGiven("Jane");
encounter.getSubject().setReference("#patient1");
encounter.getContained().add(patient);
Observation obs = new Observation();
obs.setId("obs1");
obs.addIdentifier().setSystem("urn:system").setValue("FOO");
obs.getSubject().setReference("#patient1");
CodeableConcept cc = obs.getCode();
cc.addCoding().setCode("2345-7").setSystem("http://loinc.org");
Quantity quantity = obs.getValueQuantity();
quantity.setValue(300);
encounter.addReasonReference().setReference("#obs1");
encounter.getContained().add(obs);
ourLog.info("Input: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(encounter));
IIdType eid2 = myEncounterDao.create(encounter, mySrd).getId().toUnqualifiedVersionless();
Encounter createdEncounter = myEncounterDao.read(eid2);
ourLog.info("Output: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(createdEncounter));
}
{
Encounter encounter = new Encounter();
encounter.setStatus(EncounterStatus.ARRIVED);
Patient patient = new Patient();
patient.setId("patient1");
patient.addName().setFamily("Doe").addGiven("Jane");
encounter.getSubject().setReference("#patient1");
encounter.getContained().add(patient);
Observation obs = new Observation();
obs.setId("obs1");
obs.addIdentifier().setSystem("urn:system").setValue("FOO");
obs.getSubject().setReference("#patient1");
CodeableConcept cc = obs.getCode();
cc.addCoding().setCode("2345-7").setSystem("http://loinc.org");
Quantity quantity = obs.getValueQuantity();
quantity.setValue(400);
encounter.addReasonReference().setReference("#obs1");
encounter.getContained().add(obs);
ourLog.info("Input: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(encounter));
IIdType eid3 = myEncounterDao.create(encounter, mySrd).getId().toUnqualifiedVersionless();
Encounter createdEncounter = myEncounterDao.read(eid3);
ourLog.info("Output: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(createdEncounter));
}
//-- Search by quantity
String uri = ourServerBase + "/Encounter?reason-reference.combo-value-quantity=200&_contained=true";
List<String> eids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
assertEquals(1L, eids.size());
assertThat(eids, contains(eid1.getValue()));
//-- Search by quantity
uri = ourServerBase + "/Encounter?reason-reference.combo-value-quantity=le400&_contained=true";
eids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
assertEquals(3L, eids.size());
}
@Test
public void testContainedSearchByToken() throws Exception {
IIdType eid1;
{
Encounter encounter = new Encounter();
encounter.setStatus(EncounterStatus.ARRIVED);
Patient patient = new Patient();
patient.setId("patient1");
patient.addName().setFamily("Doe").addGiven("Jane");
encounter.getSubject().setReference("#patient1");
encounter.getContained().add(patient);
Observation obs = new Observation();
obs.setId("obs1");
obs.addIdentifier().setSystem("urn:system").setValue("FOO");
obs.getSubject().setReference("#patient1");
CodeableConcept cc = obs.getCode();
cc.addCoding().setCode("2345-7").setSystem("http://loinc.org");
Quantity quantity = obs.getValueQuantity();
quantity.setValue(200);
encounter.addReasonReference().setReference("#obs1");
encounter.getContained().add(obs);
ourLog.info("Input: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(encounter));
eid1 = myEncounterDao.create(encounter, mySrd).getId().toUnqualifiedVersionless();
Encounter createdEncounter = myEncounterDao.read(eid1);
ourLog.info("Output: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(createdEncounter));
}
{
Encounter encounter = new Encounter();
encounter.setStatus(EncounterStatus.ARRIVED);
Patient patient = new Patient();
patient.setId("patient1");
patient.addName().setFamily("Doe").addGiven("Jane");
encounter.getSubject().setReference("#patient1");
encounter.getContained().add(patient);
Observation obs = new Observation();
obs.setId("obs1");
obs.addIdentifier().setSystem("urn:system").setValue("FOO");
obs.getSubject().setReference("#patient1");
CodeableConcept cc = obs.getCode();
cc.addCoding().setCode("2345-8").setSystem("http://loinc.org");
Quantity quantity = obs.getValueQuantity();
quantity.setValue(300);
encounter.addReasonReference().setReference("#obs1");
encounter.getContained().add(obs);
ourLog.info("Input: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(encounter));
IIdType eid2 = myEncounterDao.create(encounter, mySrd).getId().toUnqualifiedVersionless();
Encounter createdEncounter = myEncounterDao.read(eid2);
ourLog.info("Output: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(createdEncounter));
}
{
Encounter encounter = new Encounter();
encounter.setStatus(EncounterStatus.ARRIVED);
Patient patient = new Patient();
patient.setId("patient1");
patient.addName().setFamily("Doe").addGiven("Jane");
encounter.getSubject().setReference("#patient1");
encounter.getContained().add(patient);
Observation obs = new Observation();
obs.setId("obs1");
obs.addIdentifier().setSystem("urn:system").setValue("FOO");
obs.getSubject().setReference("#patient1");
CodeableConcept cc = obs.getCode();
cc.addCoding().setCode("2345-9").setSystem("http://loinc.org");
Quantity quantity = obs.getValueQuantity();
quantity.setValue(400);
encounter.addReasonReference().setReference("#obs1");
encounter.getContained().add(obs);
ourLog.info("Input: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(encounter));
IIdType eid3 = myEncounterDao.create(encounter, mySrd).getId().toUnqualifiedVersionless();
Encounter createdEncounter = myEncounterDao.read(eid3);
ourLog.info("Output: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(createdEncounter));
}
//-- Search by code
String uri = ourServerBase + "/Encounter?reason-reference.code=http://" + UrlUtil.escapeUrlParam("loinc.org|2345-7") + "&_contained=true";
List<String> eids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
assertEquals(1L, eids.size());
assertThat(eids, contains(eid1.getValue()));
}
@Test
public void testContainedSearchByComposite() throws Exception {
IIdType eid2;
{
Encounter encounter = new Encounter();
encounter.setStatus(EncounterStatus.ARRIVED);
Patient patient = new Patient();
patient.setId("patient1");
patient.addName().setFamily("Doe").addGiven("Jane");
encounter.getSubject().setReference("#patient1");
encounter.getContained().add(patient);
Observation obs = new Observation();
obs.setId("obs1");
obs.addIdentifier().setSystem("urn:system").setValue("FOO");
obs.getSubject().setReference("#patient1");
CodeableConcept cc = obs.getCode();
cc.addCoding().setCode("2345-7").setSystem("http://loinc.org");
Quantity quantity = obs.getValueQuantity();
quantity.setValue(200);
encounter.addReasonReference().setReference("#obs1");
encounter.getContained().add(obs);
ourLog.info("Input: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(encounter));
IIdType eid1 = myEncounterDao.create(encounter, mySrd).getId().toUnqualifiedVersionless();
Encounter createdEncounter = myEncounterDao.read(eid1);
ourLog.info("Output: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(createdEncounter));
}
{
Encounter encounter = new Encounter();
encounter.setStatus(EncounterStatus.ARRIVED);
Patient patient = new Patient();
patient.setId("patient1");
patient.addName().setFamily("Doe").addGiven("Jane");
encounter.getSubject().setReference("#patient1");
encounter.getContained().add(patient);
Observation obs = new Observation();
obs.setId("obs1");
obs.addIdentifier().setSystem("urn:system").setValue("FOO");
obs.getSubject().setReference("#patient1");
CodeableConcept cc = obs.getCode();
cc.addCoding().setCode("2345-8").setSystem("http://loinc.org");
Quantity quantity = obs.getValueQuantity();
quantity.setValue(300);
encounter.addReasonReference().setReference("#obs1");
encounter.getContained().add(obs);
ourLog.info("Input: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(encounter));
eid2 = myEncounterDao.create(encounter, mySrd).getId().toUnqualifiedVersionless();
Encounter createdEncounter = myEncounterDao.read(eid2);
ourLog.info("Output: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(createdEncounter));
}
{
Encounter encounter = new Encounter();
encounter.setStatus(EncounterStatus.ARRIVED);
Patient patient = new Patient();
patient.setId("patient1");
patient.addName().setFamily("Doe").addGiven("Jane");
encounter.getSubject().setReference("#patient1");
encounter.getContained().add(patient);
Observation obs = new Observation();
obs.setId("obs1");
obs.addIdentifier().setSystem("urn:system").setValue("FOO");
obs.getSubject().setReference("#patient1");
CodeableConcept cc = obs.getCode();
cc.addCoding().setCode("2345-9").setSystem("http://loinc.org");
Quantity quantity = obs.getValueQuantity();
quantity.setValue(400);
encounter.addReasonReference().setReference("#obs1");
encounter.getContained().add(obs);
ourLog.info("Input: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(encounter));
IIdType eid3 = myEncounterDao.create(encounter, mySrd).getId().toUnqualifiedVersionless();
Encounter createdEncounter = myEncounterDao.read(eid3);
ourLog.info("Output: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(createdEncounter));
}
//-- Search by composite
String uri = ourServerBase + "/Encounter?reason-reference.combo-code-value-quantity=http://" + UrlUtil.escapeUrlParam("loinc.org|2345-8$300") + "&_contained=true";
List<String> eids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
assertEquals(1L, eids.size());
assertThat(eids, contains(eid2.getValue()));
//-- Search by composite - not found
uri = ourServerBase + "/Encounter?reason-reference.combo-code-value-quantity=http://" + UrlUtil.escapeUrlParam("loinc.org|2345-7$300") + "&_contained=true";
eids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
assertEquals(0L, eids.size());
}
@Test
public void testContainedSearchByUri() throws Exception {
IIdType oid1;
{
Patient p = new Patient();
p.setId("patient1");
p.addName().setFamily("Smith").addGiven("John");
p.getBirthDateElement().setValueAsString("2000-01-01");
CarePlan carePlan = new CarePlan();
carePlan.setId("carePlan1");
carePlan.setStatus(CarePlanStatus.ACTIVE);
carePlan.setIntent(CarePlanIntent.ORDER);
carePlan.getSubject().setReference("#patient1");
carePlan.addInstantiatesUri("http://www.hl7.com");
Observation obs = new Observation();
obs.getCode().setText("Observation 1");
obs.getContained().add(p);
obs.getSubject().setReference("#patient1");
obs.getContained().add(carePlan);
obs.getBasedOnFirstRep().setReference("#carePlan1");
oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
ourLog.info("Input: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
Observation createdObs = myObservationDao.read(oid1);
ourLog.info("Output: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(createdObs));
}
{
Patient p = new Patient();
p.setId("patient2");
p.addName().setFamily("Smith").addGiven("John");
p.getBirthDateElement().setValueAsString("2000-01-01");
CarePlan carePlan = new CarePlan();
carePlan.setId("carePlan2");
carePlan.setStatus(CarePlanStatus.ACTIVE);
carePlan.setIntent(CarePlanIntent.ORDER);
carePlan.getSubject().setReference("#patient2");
carePlan.addInstantiatesUri("http://www2.hl7.com");
Observation obs = new Observation();
obs.getCode().setText("Observation 2");
obs.getContained().add(p);
obs.getSubject().setReference("#patient2");
obs.getContained().add(carePlan);
obs.getBasedOnFirstRep().setReference("#carePlan2");
myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
ourLog.info("Input: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
}
{
Patient p = new Patient();
p.setId("patient3");
p.addName().setFamily("Smith").addGiven("John");
p.getBirthDateElement().setValueAsString("2000-01-01");
CarePlan carePlan = new CarePlan();
carePlan.setId("carePlan3");
carePlan.setStatus(CarePlanStatus.ACTIVE);
carePlan.setIntent(CarePlanIntent.ORDER);
carePlan.getSubject().setReference("#patient3");
carePlan.addInstantiatesUri("http://www2.hl7.com");
Observation obs = new Observation();
obs.getCode().setText("Observation 3");
obs.getContained().add(p);
obs.getSubject().setReference("#patient3");
obs.getContained().add(carePlan);
obs.getBasedOnFirstRep().setReference("#carePlan3");
myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
ourLog.info("Input: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
}
//-- Search by uri
String uri = ourServerBase + "/Observation?based-on.instantiates-uri=http://www.hl7.com&_contained=true";
List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
assertEquals(1L, oids.size());
assertThat(oids, contains(oid1.getValue()));
//-- Search by uri more than 1 results
uri = ourServerBase + "/Observation?based-on.instantiates-uri=http://www2.hl7.com&_contained=true";
oids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
assertEquals(2L, oids.size());
//-- Search by uri with 'or'
uri = ourServerBase + "/Observation?based-on.instantiates-uri=http://www.hl7.com,http://www2.hl7.com&_contained=true";
oids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
assertEquals(3L, oids.size());
}
@Test
public void testUpdateContainedResource() throws Exception {
IIdType oid1;
{
Patient p = new Patient();
p.setId("patient1");
p.addName().setFamily("Smith").addGiven("John");
Observation obs = new Observation();
obs.getCode().setText("Observation 1");
obs.getContained().add(p);
obs.getSubject().setReference("#patient1");
oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
ourLog.info("Input: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
Observation createdObs = myObservationDao.read(oid1);
//-- changed the last name to Doe
List<Resource> containedResources = createdObs.getContained();
for (Resource res : containedResources) {
if (res instanceof Patient) {
Patient p1 = (Patient)res;
HumanName name = p1.getNameFirstRep();
name.setFamily("Doe");
break;
}
}
// -- update
oid1 = myObservationDao.update(createdObs, mySrd).getId().toUnqualifiedVersionless();
}
{
Patient p = new Patient();
p.setId("patient1");
p.addName().setFamily("Doe").addGiven("Jane");
Observation obs = new Observation();
obs.getCode().setText("Observation 2");
obs.getContained().add(p);
obs.getSubject().setReference("#patient1");
myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
ourLog.info("Input: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
}
{
Patient p = new Patient();
p.setId("patient1");
p.addName().setFamily("Jones").addGiven("Peter");
Observation obs = new Observation();
obs.getCode().setText("Observation 2");
obs.getContained().add(p);
obs.getSubject().setReference("#patient1");
myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
ourLog.info("Input: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
}
//-- No Obs with Patient Smith
String uri = ourServerBase + "/Observation?subject.family=Smith&_contained=true";
List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
assertEquals(0L, oids.size());
//-- Two Obs with Patient Doe
uri = ourServerBase + "/Observation?subject.family=Doe&_contained=true";
oids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
assertEquals(2L, oids.size());
}
@Test
public void testDeleteContainedResource() throws Exception {
IIdType oid1;
{
Patient p1 = new Patient();
p1.setId("patient1");
p1.addName().setFamily("Smith").addGiven("John");
Patient p2 = new Patient();
p2.setId("patient2");
p2.addName().setFamily("Doe").addGiven("Jane");
Observation obs = new Observation();
obs.getCode().setText("Observation 1");
obs.getContained().add(p1);
obs.getSubject().setReference("#patient1");
oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
// -- remove contained resource
obs.getContained().remove(p1);
// -- add new contained resource
obs.getContained().add(p2);
obs.getSubject().setReference("#patient2");
ourLog.info("Input: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
// -- update
oid1 = myObservationDao.update(obs, mySrd).getId().toUnqualifiedVersionless();
Observation updatedObs = myObservationDao.read(oid1);
ourLog.info("Output: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(updatedObs));
}
//-- No Obs with Patient Smith
String uri = ourServerBase + "/Observation?subject.family=Smith&_contained=true";
List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
assertEquals(0L, oids.size());
//-- 1 Obs with Patient Doe
uri = ourServerBase + "/Observation?subject.family=Doe&_contained=true";
oids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
assertEquals(1L, oids.size());
assertThat(oids, contains(oid1.getValue()));
}
private List<String> searchAndReturnUnqualifiedVersionlessIdValues(String uri) throws IOException {
List<String> ids;
HttpGet get = new HttpGet(uri);
try (CloseableHttpResponse response = ourHttpClient.execute(get)) {
String resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(resp);
Bundle bundle = myFhirCtx.newXmlParser().parseResource(Bundle.class, resp);
ids = toUnqualifiedVersionlessIdValues(bundle);
}
return ids;
}
}

View File

@ -98,6 +98,8 @@ public class ModelConfig {
private Set<String> myAutoVersionReferenceAtPaths = Collections.emptySet();
private Map<String, Set<String>> myTypeToAutoVersionReferenceAtPaths = Collections.emptyMap();
private boolean myRespectVersionsForSearchIncludes;
private boolean myIndexOnContainedResources = false;
/**
* Constructor
@ -730,6 +732,27 @@ public class ModelConfig {
myRespectVersionsForSearchIncludes = theRespectVersionsForSearchIncludes;
}
/**
* Should indexed on the contained resources, it could be searched by <code>_contained=true</code>
* This may have performance impacts
*
* @since 5.4.0
*/
public boolean isIndexOnContainedResources() {
return myIndexOnContainedResources;
}
/**
* Should indexed on the contained resources, it could be searched by <code>_contained=true</code>
* This may have performance impacts
*
* @since 5.4.0
*/
public void setIndexOnContainedResources(boolean theIndexOnContainedResources) {
myIndexOnContainedResources = theIndexOnContainedResources;
}
private static void validateTreatBaseUrlsAsLocal(String theUrl) {
Validate.notBlank(theUrl, "Base URL must not be null or empty");

View File

@ -0,0 +1,40 @@
package ca.uhn.fhir.jpa.searchparam;
/*
* #%L
* HAPI FHIR Search Parameters
* %%
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
public enum SearchContainedEnum {
/**
* default, search on the non-contained (normal) resources
*/
FALSE,
/**
* search on the contained resources only
*/
TRUE,
/**
* Search on the normal resources and contained resources.
* This option is not supported yet.
*/
BOTH,
}

View File

@ -79,7 +79,8 @@ public class SearchParameterMap implements Serializable {
private boolean myLastN;
private Integer myLastNMax;
private boolean myDeleteExpunge;
private SearchContainedEnum mySearchContainedMode = SearchContainedEnum.FALSE;
/**
* Constructor
*/
@ -734,4 +735,13 @@ public class SearchParameterMap implements Serializable {
return retVal;
}
public SearchContainedEnum getSearchContainedMode() {
return mySearchContainedMode;
}
public void setSearchContainedMode(SearchContainedEnum theSearchContainedMode) {
this.mySearchContainedMode = theSearchContainedMode;
}
}

View File

@ -171,9 +171,9 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
}
@Override
public SearchParamSet<PathAndRef> extractResourceLinks(IBaseResource theResource) {
public SearchParamSet<PathAndRef> extractResourceLinks(IBaseResource theResource, boolean theWantLocalReferences) {
IExtractor<PathAndRef> extractor = createReferenceExtractor();
return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.REFERENCE);
return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.REFERENCE, theWantLocalReferences);
}
private IExtractor<PathAndRef> createReferenceExtractor() {
@ -231,7 +231,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
private List<String> extractReferenceParamsAsQueryTokens(RuntimeSearchParam theSearchParam, IBaseResource theResource, IExtractor<PathAndRef> theExtractor) {
SearchParamSet<PathAndRef> params = new SearchParamSet<>();
extractSearchParam(theSearchParam, theResource, theExtractor, params);
extractSearchParam(theSearchParam, theResource, theExtractor, params, false);
return refsToStringList(params);
}
@ -244,7 +244,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
private <T extends BaseResourceIndexedSearchParam> List<String> extractParamsAsQueryTokens(RuntimeSearchParam theSearchParam, IBaseResource theResource, IExtractor<T> theExtractor) {
SearchParamSet<T> params = new SearchParamSet<>();
extractSearchParam(theSearchParam, theResource, theExtractor, params);
extractSearchParam(theSearchParam, theResource, theExtractor, params, false);
return toStringList(params);
}
@ -257,14 +257,14 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
@Override
public SearchParamSet<BaseResourceIndexedSearchParam> extractSearchParamTokens(IBaseResource theResource) {
IExtractor<BaseResourceIndexedSearchParam> extractor = createTokenExtractor(theResource);
return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.TOKEN);
return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.TOKEN, false);
}
@Override
public SearchParamSet<BaseResourceIndexedSearchParam> extractSearchParamTokens(IBaseResource theResource, RuntimeSearchParam theSearchParam) {
IExtractor<BaseResourceIndexedSearchParam> extractor = createTokenExtractor(theResource);
SearchParamSet<BaseResourceIndexedSearchParam> setToPopulate = new SearchParamSet<>();
extractSearchParam(theSearchParam, theResource, extractor, setToPopulate);
extractSearchParam(theSearchParam, theResource, extractor, setToPopulate, false);
return setToPopulate;
}
@ -293,11 +293,11 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
public SearchParamSet<BaseResourceIndexedSearchParam> extractSearchParamSpecial(IBaseResource theResource) {
String resourceTypeName = toRootTypeName(theResource);
IExtractor<BaseResourceIndexedSearchParam> extractor = createSpecialExtractor(resourceTypeName);
return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.SPECIAL);
return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.SPECIAL, false);
}
private IExtractor<BaseResourceIndexedSearchParam> createSpecialExtractor(String theResourceTypeName) {
return (params, searchParam, value, path) -> {
return (params, searchParam, value, path, theWantLocalReferences) -> {
if (COORDS_INDEX_PATHS.contains(path)) {
addCoords_Position(theResourceTypeName, params, searchParam, value);
}
@ -311,11 +311,11 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
@Override
public SearchParamSet<ResourceIndexedSearchParamUri> extractSearchParamUri(IBaseResource theResource) {
IExtractor<ResourceIndexedSearchParamUri> extractor = createUriExtractor(theResource);
return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.URI);
return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.URI, false);
}
private IExtractor<ResourceIndexedSearchParamUri> createUriExtractor(IBaseResource theResource) {
return (params, searchParam, value, path) -> {
return (params, searchParam, value, path, theWantLocalReferences) -> {
String nextType = toRootTypeName(value);
String resourceType = toRootTypeName(theResource);
switch (nextType) {
@ -336,7 +336,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
@Override
public SearchParamSet<ResourceIndexedSearchParamDate> extractSearchParamDates(IBaseResource theResource) {
IExtractor<ResourceIndexedSearchParamDate> extractor = createDateExtractor(theResource);
return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.DATE);
return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.DATE, false);
}
private IExtractor<ResourceIndexedSearchParamDate> createDateExtractor(IBaseResource theResource) {
@ -346,17 +346,17 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
@Override
public Date extractDateFromResource(IBase theValue, String thePath) {
DateExtractor extractor = new DateExtractor("DateType");
return extractor.get(theValue, thePath).getValueHigh();
return extractor.get(theValue, thePath, false).getValueHigh();
}
@Override
public SearchParamSet<ResourceIndexedSearchParamNumber> extractSearchParamNumber(IBaseResource theResource) {
IExtractor<ResourceIndexedSearchParamNumber> extractor = createNumberExtractor(theResource);
return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.NUMBER);
return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.NUMBER, false);
}
private IExtractor<ResourceIndexedSearchParamNumber> createNumberExtractor(IBaseResource theResource) {
return (params, searchParam, value, path) -> {
return (params, searchParam, value, path, theWantLocalReferences) -> {
String nextType = toRootTypeName(value);
String resourceType = toRootTypeName(theResource);
switch (nextType) {
@ -384,18 +384,18 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
@Override
public SearchParamSet<ResourceIndexedSearchParamQuantity> extractSearchParamQuantity(IBaseResource theResource) {
IExtractor<ResourceIndexedSearchParamQuantity> extractor = createQuantityExtractor(theResource);
return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.QUANTITY);
return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.QUANTITY, false);
}
@Override
public SearchParamSet<ResourceIndexedSearchParamQuantityNormalized> extractSearchParamQuantityNormalized(IBaseResource theResource) {
IExtractor<ResourceIndexedSearchParamQuantityNormalized> extractor = createQuantityNormalizedExtractor(theResource);
return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.QUANTITY);
return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.QUANTITY, false);
}
private IExtractor<ResourceIndexedSearchParamQuantity> createQuantityExtractor(IBaseResource theResource) {
return (params, searchParam, value, path) -> {
return (params, searchParam, value, path, theWantLocalReferences) -> {
if (value.getClass().equals(myLocationPositionDefinition.getImplementingClass())) {
return;
}
@ -421,7 +421,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
private IExtractor<ResourceIndexedSearchParamQuantityNormalized> createQuantityNormalizedExtractor(IBaseResource theResource) {
return (params, searchParam, value, path) -> {
return (params, searchParam, value, path, theWantLocalReferences) -> {
if (value.getClass().equals(myLocationPositionDefinition.getImplementingClass())) {
return;
}
@ -449,11 +449,11 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
public SearchParamSet<ResourceIndexedSearchParamString> extractSearchParamStrings(IBaseResource theResource) {
IExtractor<ResourceIndexedSearchParamString> extractor = createStringExtractor(theResource);
return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.STRING);
return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.STRING, false);
}
private IExtractor<ResourceIndexedSearchParamString> createStringExtractor(IBaseResource theResource) {
return (params, searchParam, value, path) -> {
return (params, searchParam, value, path, theWantLocalReferences) -> {
String resourceType = toRootTypeName(theResource);
if (value instanceof IPrimitiveType) {
@ -934,7 +934,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
}
private <T> SearchParamSet<T> extractSearchParams(IBaseResource theResource, IExtractor<T> theExtractor, RestSearchParameterTypeEnum theSearchParamType) {
private <T> SearchParamSet<T> extractSearchParams(IBaseResource theResource, IExtractor<T> theExtractor, RestSearchParameterTypeEnum theSearchParamType, boolean theWantLocalReferences) {
SearchParamSet<T> retVal = new SearchParamSet<>();
Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
@ -943,12 +943,12 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
continue;
}
extractSearchParam(nextSpDef, theResource, theExtractor, retVal);
extractSearchParam(nextSpDef, theResource, theExtractor, retVal, theWantLocalReferences);
}
return retVal;
}
private <T> void extractSearchParam(RuntimeSearchParam theSearchParameterDef, IBaseResource theResource, IExtractor<T> theExtractor, SearchParamSet<T> theSetToPopulate) {
private <T> void extractSearchParam(RuntimeSearchParam theSearchParameterDef, IBaseResource theResource, IExtractor<T> theExtractor, SearchParamSet<T> theSetToPopulate, boolean theWantLocalReferences) {
String nextPathUnsplit = theSearchParameterDef.getPath();
if (isBlank(nextPathUnsplit)) {
return;
@ -961,7 +961,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
if (nextObject != null) {
String typeName = toRootTypeName(nextObject);
if (!myIgnoredForSearchDatatypes.contains(typeName)) {
theExtractor.extract(theSetToPopulate, theSearchParameterDef, nextObject, nextPath);
theExtractor.extract(theSetToPopulate, theSearchParameterDef, nextObject, nextPath, theWantLocalReferences);
}
}
}
@ -1181,7 +1181,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
@FunctionalInterface
private interface IExtractor<T> {
void extract(SearchParamSet<T> theParams, RuntimeSearchParam theSearchParam, IBase theValue, String thePath);
void extract(SearchParamSet<T> theParams, RuntimeSearchParam theSearchParam, IBase theValue, String thePath, boolean theWantLocalReferences);
}
@ -1196,9 +1196,9 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
}
@Override
public void extract(SearchParamSet<T> theParams, RuntimeSearchParam theSearchParam, IBase theValue, String thePath) {
myExtractor0.extract(theParams, theSearchParam, theValue, thePath);
myExtractor1.extract(theParams, theSearchParam, theValue, thePath);
public void extract(SearchParamSet<T> theParams, RuntimeSearchParam theSearchParam, IBase theValue, String thePath, boolean theWantLocalReferences) {
myExtractor0.extract(theParams, theSearchParam, theValue, thePath, theWantLocalReferences);
myExtractor1.extract(theParams, theSearchParam, theValue, thePath, theWantLocalReferences);
}
}
@ -1207,7 +1207,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
private PathAndRef myPathAndRef = null;
@Override
public void extract(SearchParamSet<PathAndRef> theParams, RuntimeSearchParam theSearchParam, IBase theValue, String thePath) {
public void extract(SearchParamSet<PathAndRef> theParams, RuntimeSearchParam theSearchParam, IBase theValue, String thePath, boolean theWantLocalReferences) {
if (theValue instanceof IBaseResource) {
return;
}
@ -1257,10 +1257,13 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
}
if (nextId == null ||
nextId.isEmpty() ||
nextId.getValue().startsWith("#") ||
nextId.getValue().startsWith("urn:")) {
return;
nextId.isEmpty() ||
nextId.getValue().startsWith("urn:")) {
return;
}
if (!theWantLocalReferences) {
if (nextId.getValue().startsWith("#"))
return;
}
myPathAndRef = new PathAndRef(theSearchParam.getName(), thePath, valueRef, false);
@ -1275,7 +1278,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
public PathAndRef get(IBase theValue, String thePath) {
extract(new SearchParamSet<>(),
new RuntimeSearchParam(null, null, "Reference", null, null, null, null, null, null, null),
theValue, thePath);
theValue, thePath, false);
return myPathAndRef;
}
}
@ -1294,7 +1297,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
}
@Override
public void extract(SearchParamSet theParams, RuntimeSearchParam theSearchParam, IBase theValue, String thePath) {
public void extract(SearchParamSet theParams, RuntimeSearchParam theSearchParam, IBase theValue, String thePath, boolean theWantLocalReferences) {
String nextType = toRootTypeName(theValue);
switch (nextType) {
case "date":
@ -1389,10 +1392,10 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
}
}
public ResourceIndexedSearchParamDate get(IBase theValue, String thePath) {
public ResourceIndexedSearchParamDate get(IBase theValue, String thePath, boolean theWantLocalReferences) {
extract(new SearchParamSet<>(),
new RuntimeSearchParam(null, null, "date", null, null, null, null, null, null, null),
theValue, thePath);
theValue, thePath, theWantLocalReferences);
return myIndexedSearchParamDate;
}
}
@ -1407,7 +1410,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
}
@Override
public void extract(SearchParamSet<BaseResourceIndexedSearchParam> params, RuntimeSearchParam searchParam, IBase value, String path) {
public void extract(SearchParamSet<BaseResourceIndexedSearchParam> params, RuntimeSearchParam searchParam, IBase value, String path, boolean theWantLocalReferences) {
// DSTU3+
if (value instanceof IBaseEnumeration<?>) {

View File

@ -61,7 +61,7 @@ public interface ISearchParamExtractor {
SearchParamSet<ResourceIndexedSearchParamUri> extractSearchParamUri(IBaseResource theResource);
SearchParamSet<PathAndRef> extractResourceLinks(IBaseResource theResource);
SearchParamSet<PathAndRef> extractResourceLinks(IBaseResource theResource, boolean theWantLocalReferences);
String[] split(String theExpression);

View File

@ -42,9 +42,7 @@ import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
import ca.uhn.fhir.rest.param.QuantityParam;
import ca.uhn.fhir.rest.param.ReferenceParam;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.collect.Streams;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
@ -57,7 +55,7 @@ import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Stream;
import javax.annotation.Nonnull;
import static org.apache.commons.lang3.StringUtils.compare;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
@ -144,6 +142,17 @@ public final class ResourceIndexedSearchParams {
theEntity.setResourceLinks(myLinks);
}
public void updateSpnamePrefixForIndexedOnContainedResource(String theSpnamePrefix) {
updateSpnamePrefixForIndexedOnContainedResource(myNumberParams, theSpnamePrefix);
updateSpnamePrefixForIndexedOnContainedResource(myQuantityParams, theSpnamePrefix);
updateSpnamePrefixForIndexedOnContainedResource(myQuantityNormalizedParams, theSpnamePrefix);
updateSpnamePrefixForIndexedOnContainedResource(myDateParams, theSpnamePrefix);
updateSpnamePrefixForIndexedOnContainedResource(myUriParams, theSpnamePrefix);
updateSpnamePrefixForIndexedOnContainedResource(myTokenParams, theSpnamePrefix);
updateSpnamePrefixForIndexedOnContainedResource(myStringParams, theSpnamePrefix);
updateSpnamePrefixForIndexedOnContainedResource(myCoordsParams, theSpnamePrefix);
}
void setUpdatedTime(Date theUpdateTime) {
setUpdatedTime(myStringParams, theUpdateTime);
setUpdatedTime(myNumberParams, theUpdateTime);
@ -161,6 +170,14 @@ public final class ResourceIndexedSearchParams {
}
}
private void updateSpnamePrefixForIndexedOnContainedResource(Collection<? extends BaseResourceIndexedSearchParam> theParams, @Nonnull String theSpnamePrefix) {
for (BaseResourceIndexedSearchParam param : theParams) {
param.setParamName(theSpnamePrefix + "." + param.getParamName());
param.calculateHashes(); // re-calculuteHashes
}
}
public Set<String> getPopulatedResourceLinkParameters() {
return myPopulatedResourceLinkParameters;
}

View File

@ -52,6 +52,10 @@ import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.util.FhirTerser;
import ca.uhn.fhir.util.ResourceReferenceInfo;
import ca.uhn.fhir.util.StringUtil;
import com.google.common.annotations.VisibleForTesting;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.instance.model.api.IBaseReference;
@ -96,14 +100,87 @@ public class SearchParamExtractorService {
IBaseResource resource = normalizeResource(theResource);
// All search parameter types except Reference
extractSearchIndexParameters(theRequestDetails, theParams, resource, theEntity);
ResourceIndexedSearchParams normalParams = new ResourceIndexedSearchParams();
extractSearchIndexParameters(theRequestDetails, normalParams, resource, theEntity);
mergeParams(normalParams, theParams);
if (myModelConfig.isIndexOnContainedResources()) {
ResourceIndexedSearchParams containedParams = new ResourceIndexedSearchParams();
extractSearchIndexParametersForContainedResources(theRequestDetails, containedParams, resource, theEntity);
mergeParams(containedParams, theParams);
}
// Do this after, because we add to strings during both string and token processing, and contained resource if any
populateResourceTables(theParams, theEntity);
// Reference search parameters
extractResourceLinks(theRequestPartitionId, theParams, theEntity, resource, theTransactionDetails, theFailOnInvalidReference, theRequestDetails);
theParams.setUpdatedTime(theTransactionDetails.getTransactionDate());
}
private void extractSearchIndexParametersForContainedResources(RequestDetails theRequestDetails, ResourceIndexedSearchParams theParams, IBaseResource theResource, ResourceTable theEntity) {
FhirTerser terser = myContext.newTerser();
// 1. get all contained resources
Collection<IBaseResource> containedResources = terser.getAllEmbeddedResources(theResource, false);
// 2. Find referenced search parameters
ISearchParamExtractor.SearchParamSet<PathAndRef> referencedSearchParamSet = mySearchParamExtractor.extractResourceLinks(theResource, true);
String spnamePrefix = null;
ResourceIndexedSearchParams currParams;
// 3. for each referenced search parameter, create an index
for (PathAndRef nextPathAndRef : referencedSearchParamSet) {
// 3.1 get the search parameter name as spname prefix
spnamePrefix = nextPathAndRef.getSearchParamName();
if (spnamePrefix == null || nextPathAndRef.getRef() == null)
continue;
// 3.2 find the contained resource
IBaseResource containedResource = findContainedResource(containedResources, nextPathAndRef.getRef());
if (containedResource == null)
continue;
currParams = new ResourceIndexedSearchParams();
// 3.3 create indexes for the current contained resource
extractSearchIndexParameters(theRequestDetails, currParams, containedResource, theEntity);
// 3.4 added reference name as a prefix for the contained resource if any
// e.g. for Observation.subject contained reference
// the SP_NAME = subject.family
currParams.updateSpnamePrefixForIndexedOnContainedResource(spnamePrefix);
// 3.5 merge to the mainParams
// NOTE: the spname prefix is different
mergeParams(currParams, theParams);
}
}
private IBaseResource findContainedResource(Collection<IBaseResource> resources, IBaseReference reference) {
for (IBaseResource resource : resources) {
if (resource.getIdElement().equals(reference.getReferenceElement()))
return resource;
}
return null;
}
private void mergeParams(ResourceIndexedSearchParams theSrcParams, ResourceIndexedSearchParams theTargetParams) {
theTargetParams.myNumberParams.addAll(theSrcParams.myNumberParams);
theTargetParams.myQuantityParams.addAll(theSrcParams.myQuantityParams);
theTargetParams.myQuantityNormalizedParams.addAll(theSrcParams.myQuantityNormalizedParams);
theTargetParams.myDateParams.addAll(theSrcParams.myDateParams);
theTargetParams.myUriParams.addAll(theSrcParams.myUriParams);
theTargetParams.myTokenParams.addAll(theSrcParams.myTokenParams);
theTargetParams.myStringParams.addAll(theSrcParams.myStringParams);
theTargetParams.myCoordsParams.addAll(theSrcParams.myCoordsParams);
}
private void extractSearchIndexParameters(RequestDetails theRequestDetails, ResourceIndexedSearchParams theParams, IBaseResource theResource, ResourceTable theEntity) {
// Strings
@ -156,7 +233,10 @@ public class SearchParamExtractorService {
}
}
// Do this after, because we add to strings during both string and token processing
}
private void populateResourceTables(ResourceIndexedSearchParams theParams, ResourceTable theEntity) {
populateResourceTable(theParams.myNumberParams, theEntity);
populateResourceTable(theParams.myQuantityParams, theEntity);
populateResourceTable(theParams.myQuantityNormalizedParams, theEntity);
@ -165,7 +245,6 @@ public class SearchParamExtractorService {
populateResourceTable(theParams.myTokenParams, theEntity);
populateResourceTable(theParams.myStringParams, theEntity);
populateResourceTable(theParams.myCoordsParams, theEntity);
}
/**
@ -186,7 +265,7 @@ public class SearchParamExtractorService {
private void extractResourceLinks(RequestPartitionId theRequestPartitionId, ResourceIndexedSearchParams theParams, ResourceTable theEntity, IBaseResource theResource, TransactionDetails theTransactionDetails, boolean theFailOnInvalidReference, RequestDetails theRequest) {
String resourceName = myContext.getResourceType(theResource);
ISearchParamExtractor.SearchParamSet<PathAndRef> refs = mySearchParamExtractor.extractResourceLinks(theResource);
ISearchParamExtractor.SearchParamSet<PathAndRef> refs = mySearchParamExtractor.extractResourceLinks(theResource, false);
SearchParamExtractorService.handleWarnings(theRequest, myInterceptorBroadcaster, refs);
for (PathAndRef nextPathAndRef : refs) {