Merge pull request #1760 from jamesagnew/bugfix/1752-support-chained-parameters-inside-reverse-chained-parameter

Bugfix/1752 support chained parameters inside reverse chained parameter
This commit is contained in:
Tadgh 2020-03-17 13:27:20 -07:00 committed by GitHub
commit 4583cb9939
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 219 additions and 172 deletions

View File

@ -0,0 +1,8 @@
---
type: add
issue: 1760
title: "Adds support for chained parameters in a _has query. For example
`GET /Patient?_has:Observation:subject:device.identifier=1234-5`. Adds a performance warning on any queries that use
an unqualified resource in a chain which ends up resolving to 2 or more candidate target types."

View File

@ -81,7 +81,6 @@ public class MatchResourceUrlService {
.add(StorageProcessingMessage.class, message);
JpaInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, theRequest, Pointcut.JPA_PERFTRACE_INFO, params);
}
return retVal;
}

View File

@ -157,7 +157,7 @@ public class SearchBuilder implements ISearchBuilder {
@Autowired
private PredicateBuilderFactory myPredicateBuilderFactory;
private List<ResourcePersistentId> myAlsoIncludePids;
private CriteriaBuilder myBuilder;
private CriteriaBuilder myCriteriaBuilder;
private IDao myCallingDao;
private SearchParameterMap myParams;
private String mySearchUuid;
@ -195,15 +195,8 @@ public class SearchBuilder implements ISearchBuilder {
Dstu3DistanceHelper.setNearDistance(myResourceType, theParams);
}
/*
* Check if there is a unique key associated with the set
* of parameters passed in
*/
boolean couldBeEligibleForCompositeUniqueSpProcessing =
myDaoConfig.isUniqueIndexesEnabled() &&
myParams.getEverythingMode() == null &&
myParams.isAllParametersHaveNoModifier();
if (couldBeEligibleForCompositeUniqueSpProcessing) {
// Attempt to lookup via composite unique key.
if (isCompositeUniqueSpCandidate()) {
attemptCompositeUniqueSpProcessing(theParams, theRequest);
}
@ -213,7 +206,16 @@ public class SearchBuilder implements ISearchBuilder {
List<List<IQueryParameterType>> andOrParams = nextParamEntry.getValue();
searchForIdsWithAndOr(myResourceName, nextParamName, andOrParams, theRequest);
}
}
/**
* A search is a candidate for Composite Unique SP if unique indexes are enabled, there is no EverythingMode, and the
* parameters all have no modifiers.
*/
private boolean isCompositeUniqueSpCandidate() {
return myDaoConfig.isUniqueIndexesEnabled() &&
myParams.getEverythingMode() == null &&
myParams.isAllParametersHaveNoModifier();
}
@Override
@ -243,10 +245,10 @@ public class SearchBuilder implements ISearchBuilder {
return new QueryIterator(theSearchRuntimeDetails, theRequest);
}
private void init(SearchParameterMap theParams, String theTheSearchUuid) {
private void init(SearchParameterMap theParams, String theSearchUuid) {
myParams = theParams;
myBuilder = myEntityManager.getCriteriaBuilder();
mySearchUuid = theTheSearchUuid;
myCriteriaBuilder = myEntityManager.getCriteriaBuilder();
mySearchUuid = theSearchUuid;
myPredicateBuilder = new PredicateBuilder(this, myPredicateBuilderFactory);
}
@ -261,50 +263,49 @@ public class SearchBuilder implements ISearchBuilder {
if (sort != null) {
assert !theCount;
outerQuery = myBuilder.createQuery(Long.class);
outerQuery = myCriteriaBuilder.createQuery(Long.class);
myQueryRoot.push(outerQuery);
if (theCount) {
outerQuery.multiselect(myBuilder.countDistinct(myQueryRoot.getRoot()));
outerQuery.multiselect(myCriteriaBuilder.countDistinct(myQueryRoot.getRoot()));
} else {
outerQuery.multiselect(myQueryRoot.get("myId").as(Long.class));
}
List<Order> orders = Lists.newArrayList();
createSort(myBuilder, myQueryRoot, sort, orders);
createSort(myCriteriaBuilder, myQueryRoot, sort, orders);
if (orders.size() > 0) {
outerQuery.orderBy(orders);
}
} else {
outerQuery = myBuilder.createQuery(Long.class);
outerQuery = myCriteriaBuilder.createQuery(Long.class);
myQueryRoot.push(outerQuery);
if (theCount) {
outerQuery.multiselect(myBuilder.countDistinct(myQueryRoot.getRoot()));
outerQuery.multiselect(myCriteriaBuilder.countDistinct(myQueryRoot.getRoot()));
} else {
outerQuery.multiselect(myQueryRoot.get("myId").as(Long.class));
// KHS This distinct call is causing performance issues in large installations
// outerQuery.distinct(true);
}
}
if (myParams.getEverythingMode() != null) {
Join<ResourceTable, ResourceLink> join = myQueryRoot.join("myResourceLinks", JoinType.LEFT);
if (myParams.get(IAnyResource.SP_RES_ID) != null) {
StringParam idParm = (StringParam) myParams.get(IAnyResource.SP_RES_ID).get(0).get(0);
ResourcePersistentId pid = myIdHelperService.resolveResourcePersistentIds(myResourceName, idParm.getValue());
StringParam idParam = (StringParam) myParams.get(IAnyResource.SP_RES_ID).get(0).get(0);
ResourcePersistentId pid = myIdHelperService.resolveResourcePersistentIds(myResourceName, idParam.getValue());
if (myAlsoIncludePids == null) {
myAlsoIncludePids = new ArrayList<>(1);
}
myAlsoIncludePids.add(pid);
myQueryRoot.addPredicate(myBuilder.equal(join.get("myTargetResourcePid").as(Long.class), pid.getIdAsLong()));
myQueryRoot.addPredicate(myCriteriaBuilder.equal(join.get("myTargetResourcePid").as(Long.class), pid.getIdAsLong()));
} else {
Predicate targetTypePredicate = myBuilder.equal(join.get("myTargetResourceType").as(String.class), myResourceName);
Predicate sourceTypePredicate = myBuilder.equal(myQueryRoot.get("myResourceType").as(String.class), myResourceName);
myQueryRoot.addPredicate(myBuilder.or(sourceTypePredicate, targetTypePredicate));
Predicate targetTypePredicate = myCriteriaBuilder.equal(join.get("myTargetResourceType").as(String.class), myResourceName);
Predicate sourceTypePredicate = myCriteriaBuilder.equal(myQueryRoot.get("myResourceType").as(String.class), myResourceName);
myQueryRoot.addPredicate(myCriteriaBuilder.or(sourceTypePredicate, targetTypePredicate));
}
} else {
@ -348,17 +349,17 @@ public class SearchBuilder implements ISearchBuilder {
boolean haveNoIndexSearchParams = myParams.size() == 0 || myParams.keySet().stream().allMatch(t -> t.startsWith("_"));
if (haveNoIndexSearchParams) {
if (myParams.getEverythingMode() == null) {
myQueryRoot.addPredicate(myBuilder.equal(myQueryRoot.get("myResourceType"), myResourceName));
myQueryRoot.addPredicate(myCriteriaBuilder.equal(myQueryRoot.get("myResourceType"), myResourceName));
}
myQueryRoot.addPredicate(myBuilder.isNull(myQueryRoot.get("myDeleted")));
myQueryRoot.addPredicate(myCriteriaBuilder.isNull(myQueryRoot.get("myDeleted")));
}
// Last updated
DateRangeParam lu = myParams.getLastUpdated();
List<Predicate> lastUpdatedPredicates = createLastUpdatedPredicates(lu, myBuilder, myQueryRoot.getRoot());
List<Predicate> lastUpdatedPredicates = createLastUpdatedPredicates(lu, myCriteriaBuilder, myQueryRoot.getRoot());
myQueryRoot.addPredicates(lastUpdatedPredicates);
myQueryRoot.where(myBuilder.and(myQueryRoot.getPredicateArray()));
myQueryRoot.where(myCriteriaBuilder.and(myQueryRoot.getPredicateArray()));
/*
* Now perform the search
@ -880,7 +881,7 @@ public class SearchBuilder implements ISearchBuilder {
private void addPredicateCompositeStringUnique(@Nonnull SearchParameterMap theParams, String theIndexedString) {
myQueryRoot.setHasIndexJoins(true);
Join<ResourceTable, ResourceIndexedCompositeStringUnique> join = myQueryRoot.join("myParamsCompositeStringUnique", JoinType.LEFT);
Predicate predicate = myBuilder.equal(join.get("myIndexString"), theIndexedString);
Predicate predicate = myCriteriaBuilder.equal(join.get("myIndexString"), theIndexedString);
myQueryRoot.addPredicate(predicate);
// Remove any empty parameters remaining after this
@ -907,7 +908,7 @@ public class SearchBuilder implements ISearchBuilder {
}
public CriteriaBuilder getBuilder() {
return myBuilder;
return myCriteriaBuilder;
}
public QueryRoot getQueryRoot() {
@ -984,7 +985,7 @@ public class SearchBuilder implements ISearchBuilder {
private final SearchRuntimeDetails mySearchRuntimeDetails;
private final RequestDetails myRequest;
private final boolean myHaveRawSqlHooks;
private final boolean myHavePerftraceFoundIdHook;
private final boolean myHavePerfTraceFoundIdHook;
private boolean myFirst = true;
private IncludesIterator myIncludesIterator;
private ResourcePersistentId myNext;
@ -1005,7 +1006,7 @@ public class SearchBuilder implements ISearchBuilder {
myStillNeedToFetchIncludes = true;
}
myHavePerftraceFoundIdHook = JpaInterceptorBroadcaster.hasHooks(Pointcut.JPA_PERFTRACE_SEARCH_FOUND_ID, myInterceptorBroadcaster, myRequest);
myHavePerfTraceFoundIdHook = JpaInterceptorBroadcaster.hasHooks(Pointcut.JPA_PERFTRACE_SEARCH_FOUND_ID, myInterceptorBroadcaster, myRequest);
myHaveRawSqlHooks = JpaInterceptorBroadcaster.hasHooks(Pointcut.JPA_PERFTRACE_RAW_SQL, myInterceptorBroadcaster, myRequest);
}
@ -1047,7 +1048,7 @@ public class SearchBuilder implements ISearchBuilder {
if (myNext == null) {
while (myResultsIterator.hasNext()) {
Long nextLong = myResultsIterator.next();
if (myHavePerftraceFoundIdHook) {
if (myHavePerfTraceFoundIdHook) {
HookParams params = new HookParams()
.add(Integer.class, System.identityHashCode(this))
.add(Object.class, nextLong);

View File

@ -109,10 +109,8 @@ public class IdHelperService {
}
/**
* Given a resource type and ID, looks up the resource and returns a {@link IResourceLookup}. This
* object contains the internal PID for the resource and the resource deletion status, making it sufficient
* for persisting resource links between resources without adding any further database calls after the
* single one performed by this call.
* Given a forced ID, convert it to it's Long value. Since you are allowed to use string IDs for resources, we need to
* convert those to the underlying Long values that are stored, for lookup and comparison purposes.
*
* @throws ResourceNotFoundException If the ID can not be found
*/
@ -167,10 +165,10 @@ public class IdHelperService {
if (myDaoConfig.getResourceClientIdStrategy() != DaoConfig.ClientIdStrategyEnum.ANY) {
theIds
.stream()
.filter(t -> isValidPid(t))
.map(t -> t.getIdPartAsLong())
.map(t -> new ResourcePersistentId(t))
.forEach(t -> retVal.add(t));
.filter(IdHelperService::isValidPid)
.map(IIdType::getIdPartAsLong)
.map(ResourcePersistentId::new)
.forEach(retVal::add);
}
ListMultimap<String, String> typeToIds = organizeIdsByResourceType(theIds);
@ -274,15 +272,10 @@ public class IdHelperService {
.map(t -> t.getIdPartAsLong())
.collect(Collectors.toList());
if (!pids.isEmpty()) {
Collection<Object[]> lookups = myResourceTableDao.findLookupFieldsByResourcePid(pids);
for (Object[] next : lookups) {
String resourceType = (String) next[0];
Long resourcePid = (Long) next[1];
Date deletedAt = (Date) next[2];
retVal.add(new ResourceLookup(resourceType, resourcePid, deletedAt));
}
myResourceTableDao.findLookupFieldsByResourcePid(pids)
.stream()
.map(lookup -> new ResourceLookup((String)lookup[0], (Long)lookup[1], (Date)lookup[2]))
.forEach(retVal::add);
}
}

View File

@ -52,7 +52,7 @@ abstract class BasePredicateBuilder {
boolean myDontUseHashesForSearch;
final IDao myCallingDao;
final CriteriaBuilder myBuilder;
final CriteriaBuilder myCriteriaBuilder;
final QueryRoot myQueryRoot;
final Class<? extends IBaseResource> myResourceType;
final String myResourceName;
@ -60,7 +60,7 @@ abstract class BasePredicateBuilder {
BasePredicateBuilder(SearchBuilder theSearchBuilder) {
myCallingDao = theSearchBuilder.getCallingDao();
myBuilder = theSearchBuilder.getBuilder();
myCriteriaBuilder = theSearchBuilder.getBuilder();
myQueryRoot = theSearchBuilder.getQueryRoot();
myResourceType = theSearchBuilder.getResourceType();
myResourceName = theSearchBuilder.getResourceName();
@ -122,27 +122,27 @@ abstract class BasePredicateBuilder {
Expression<Long> hashPresence = paramPresentJoin.get("myHashPresence").as(Long.class);
Long hash = SearchParamPresent.calculateHashPresence(theResourceName, theParamName, !theMissing);
myQueryRoot.addPredicate(myBuilder.equal(hashPresence, hash));
myQueryRoot.addPredicate(myCriteriaBuilder.equal(hashPresence, hash));
}
void addPredicateParamMissing(String theResourceName, String theParamName, boolean theMissing, Join<ResourceTable, ? extends BaseResourceIndexedSearchParam> theJoin) {
myQueryRoot.addPredicate(myBuilder.equal(theJoin.get("myResourceType"), theResourceName));
myQueryRoot.addPredicate(myBuilder.equal(theJoin.get("myParamName"), theParamName));
myQueryRoot.addPredicate(myBuilder.equal(theJoin.get("myMissing"), theMissing));
myQueryRoot.addPredicate(myCriteriaBuilder.equal(theJoin.get("myResourceType"), theResourceName));
myQueryRoot.addPredicate(myCriteriaBuilder.equal(theJoin.get("myParamName"), theParamName));
myQueryRoot.addPredicate(myCriteriaBuilder.equal(theJoin.get("myMissing"), theMissing));
}
Predicate combineParamIndexPredicateWithParamNamePredicate(String theResourceName, String theParamName, From<?, ? extends BaseResourceIndexedSearchParam> theFrom, Predicate thePredicate) {
if (myDontUseHashesForSearch) {
Predicate resourceTypePredicate = myBuilder.equal(theFrom.get("myResourceType"), theResourceName);
Predicate paramNamePredicate = myBuilder.equal(theFrom.get("myParamName"), theParamName);
Predicate outerPredicate = myBuilder.and(resourceTypePredicate, paramNamePredicate, thePredicate);
Predicate resourceTypePredicate = myCriteriaBuilder.equal(theFrom.get("myResourceType"), theResourceName);
Predicate paramNamePredicate = myCriteriaBuilder.equal(theFrom.get("myParamName"), theParamName);
Predicate outerPredicate = myCriteriaBuilder.and(resourceTypePredicate, paramNamePredicate, thePredicate);
return outerPredicate;
}
long hashIdentity = BaseResourceIndexedSearchParam.calculateHashIdentity(theResourceName, theParamName);
Predicate hashIdentityPredicate = myBuilder.equal(theFrom.get("myHashIdentity"), hashIdentity);
return myBuilder.and(hashIdentityPredicate, thePredicate);
Predicate hashIdentityPredicate = myCriteriaBuilder.equal(theFrom.get("myHashIdentity"), hashIdentity);
return myCriteriaBuilder.and(hashIdentityPredicate, thePredicate);
}
Predicate createPredicateNumeric(String theResourceName,

View File

@ -161,13 +161,13 @@ public class PredicateBuilderCoords extends BasePredicateBuilder implements IPre
Predicate singleCode = createPredicateCoords(nextOr,
theResourceName,
theParamName,
myBuilder,
myCriteriaBuilder,
join
);
codePredicates.add(singleCode);
}
Predicate retVal = myBuilder.or(toArray(codePredicates));
Predicate retVal = myCriteriaBuilder.or(toArray(codePredicates));
myQueryRoot.addPredicate(retVal);
return retVal;
}

View File

@ -85,13 +85,13 @@ public class PredicateBuilderDate extends BasePredicateBuilder implements IPredi
Predicate p = createPredicateDate(params,
theResourceName,
theParamName,
myBuilder,
myCriteriaBuilder,
join,
operation);
codePredicates.add(p);
}
Predicate orPredicates = myBuilder.or(toArray(codePredicates));
Predicate orPredicates = myCriteriaBuilder.or(toArray(codePredicates));
if (newJoin) {
Predicate identityAndValuePredicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, join, orPredicates);

View File

@ -94,7 +94,7 @@ class PredicateBuilderNumber extends BasePredicateBuilder implements IPredicateB
String invalidMessageName = "invalidNumberPrefix";
Predicate predicateNumeric = createPredicateNumeric(theResourceName, theParamName, join, myBuilder, nextOr, prefix, value, fromObj, invalidMessageName);
Predicate predicateNumeric = createPredicateNumeric(theResourceName, theParamName, join, myCriteriaBuilder, nextOr, prefix, value, fromObj, invalidMessageName);
Predicate predicateOuter = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, join, predicateNumeric);
codePredicates.add(predicateOuter);
@ -104,7 +104,7 @@ class PredicateBuilderNumber extends BasePredicateBuilder implements IPredicateB
}
Predicate predicate = myBuilder.or(toArray(codePredicates));
Predicate predicate = myCriteriaBuilder.or(toArray(codePredicates));
myQueryRoot.addPredicate(predicate);
return predicate;
}

View File

@ -66,13 +66,13 @@ class PredicateBuilderQuantity extends BasePredicateBuilder implements IPredicat
Predicate singleCode = createPredicateQuantity(nextOr,
theResourceName,
theParamName,
myBuilder,
myCriteriaBuilder,
join,
operation);
codePredicates.add(singleCode);
}
Predicate retVal = myBuilder.or(toArray(codePredicates));
Predicate retVal = myCriteriaBuilder.or(toArray(codePredicates));
myQueryRoot.addPredicate(retVal);
return retVal;
}
@ -171,13 +171,13 @@ class PredicateBuilderQuantity extends BasePredicateBuilder implements IPredicat
Predicate hashPredicate;
if (!isBlank(systemValue) && !isBlank(unitsValue)) {
long hash = ResourceIndexedSearchParamQuantity.calculateHashSystemAndUnits(theResourceName, theParamName, systemValue, unitsValue);
hashPredicate = myBuilder.equal(theFrom.get("myHashIdentitySystemAndUnits"), hash);
hashPredicate = myCriteriaBuilder.equal(theFrom.get("myHashIdentitySystemAndUnits"), hash);
} else if (!isBlank(unitsValue)) {
long hash = ResourceIndexedSearchParamQuantity.calculateHashUnits(theResourceName, theParamName, unitsValue);
hashPredicate = myBuilder.equal(theFrom.get("myHashIdentityAndUnits"), hash);
hashPredicate = myCriteriaBuilder.equal(theFrom.get("myHashIdentityAndUnits"), hash);
} else {
long hash = BaseResourceIndexedSearchParam.calculateHashIdentity(theResourceName, theParamName);
hashPredicate = myBuilder.equal(theFrom.get("myHashIdentity"), hash);
hashPredicate = myCriteriaBuilder.equal(theFrom.get("myHashIdentity"), hash);
}
cmpValue = defaultIfNull(cmpValue, ParamPrefixEnum.EQUAL);

View File

@ -21,14 +21,19 @@ package ca.uhn.fhir.jpa.dao.predicate;
*/
import ca.uhn.fhir.context.*;
import ca.uhn.fhir.interceptor.api.HookParams;
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.jpa.dao.*;
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId;
import ca.uhn.fhir.jpa.model.entity.*;
import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage;
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
import ca.uhn.fhir.jpa.searchparam.ResourceMetaParams;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
import ca.uhn.fhir.jpa.searchparam.util.SourceParam;
import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster;
import ca.uhn.fhir.model.api.IQueryParameterAnd;
import ca.uhn.fhir.model.api.IQueryParameterOr;
import ca.uhn.fhir.model.api.IQueryParameterType;
@ -41,7 +46,9 @@ import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.param.*;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import com.google.common.collect.Lists;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
@ -53,6 +60,7 @@ import org.springframework.stereotype.Component;
import javax.persistence.criteria.*;
import java.util.*;
import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.*;
@ -69,6 +77,8 @@ class PredicateBuilderReference extends BasePredicateBuilder {
MatchUrlService myMatchUrlService;
@Autowired
DaoRegistry myDaoRegistry;
@Autowired
private IInterceptorBroadcaster myInterceptorBroadcaster;
private final PredicateBuilder myPredicateBuilder;
@ -87,6 +97,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
SearchFilterParser.CompareOperation operation,
RequestDetails theRequest) {
//Is this just to ensure the chain has been split correctly???
assert theParamName.contains(".") == false;
if ((operation != null) &&
@ -164,7 +175,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
} else {
pidPredicate = join.get("myTargetResourcePid").in(ResourcePersistentId.toLongList(targetPids)).not();
}
codePredicates.add(myBuilder.and(pathPredicate, pidPredicate));
codePredicates.add(myCriteriaBuilder.and(pathPredicate, pidPredicate));
}
// Resources by fully qualified URL
@ -182,11 +193,11 @@ class PredicateBuilderReference extends BasePredicateBuilder {
} else {
pidPredicate = join.get("myTargetResourceUrl").in(targetQualifiedUrls).not();
}
codePredicates.add(myBuilder.and(pathPredicate, pidPredicate));
codePredicates.add(myCriteriaBuilder.and(pathPredicate, pidPredicate));
}
if (codePredicates.size() > 0) {
Predicate predicate = myBuilder.or(toArray(codePredicates));
Predicate predicate = myCriteriaBuilder.or(toArray(codePredicates));
myQueryRoot.addPredicate(predicate);
return predicate;
} else {
@ -198,9 +209,13 @@ class PredicateBuilderReference extends BasePredicateBuilder {
}
}
private Predicate addPredicateReferenceWithChain(String theResourceName, String theParamName, List<? extends IQueryParameterType> theList, Join<ResourceTable, ResourceLink> theJoin, List<Predicate> theCodePredicates, ReferenceParam theRef, RequestDetails theRequest) {
/**
* This is for handling queries like the following: /Observation?device.identifier=urn:system|foo in which we use a chain
* on the device.
*/
private Predicate addPredicateReferenceWithChain(String theResourceName, String theParamName, List<? extends IQueryParameterType> theList, Join<ResourceTable, ResourceLink> theJoin, List<Predicate> theCodePredicates, ReferenceParam theReferenceParam, RequestDetails theRequest) {
final List<Class<? extends IBaseResource>> resourceTypes;
if (!theRef.hasResourceType()) {
if (!theReferenceParam.hasResourceType()) {
RuntimeSearchParam param = mySearchParamRegistry.getActiveSearchParam(theResourceName, theParamName);
resourceTypes = new ArrayList<>();
@ -259,19 +274,19 @@ class PredicateBuilderReference extends BasePredicateBuilder {
} else {
try {
RuntimeResourceDefinition resDef = myContext.getResourceDefinition(theRef.getResourceType());
RuntimeResourceDefinition resDef = myContext.getResourceDefinition(theReferenceParam.getResourceType());
resourceTypes = new ArrayList<>(1);
resourceTypes.add(resDef.getImplementingClass());
} catch (DataFormatException e) {
throw new InvalidRequestException("Invalid resource type: " + theRef.getResourceType());
throw new InvalidRequestException("Invalid resource type: " + theReferenceParam.getResourceType());
}
}
boolean foundChainMatch = false;
List<Class<? extends IBaseResource>> candidateTargetTypes = new ArrayList<>();
for (Class<? extends IBaseResource> nextType : resourceTypes) {
String chain = theRef.getChain();
String chain = theReferenceParam.getChain();
String remainingChain = null;
int chainDotIndex = chain.indexOf('.');
if (chainDotIndex != -1) {
@ -317,24 +332,46 @@ class PredicateBuilderReference extends BasePredicateBuilder {
orValues.add(chainValue);
}
Subquery<Long> subQ = createLinkSubquery(foundChainMatch, chain, subResourceName, orValues, theRequest);
Predicate pathPredicate = createResourceLinkPathPredicate(theResourceName, theParamName, theJoin);
Predicate pidPredicate = theJoin.get("myTargetResourcePid").in(subQ);
Predicate andPredicate = myBuilder.and(pathPredicate, pidPredicate);
Predicate andPredicate = myCriteriaBuilder.and(pathPredicate, pidPredicate);
theCodePredicates.add(andPredicate);
candidateTargetTypes.add(nextType);
}
if (!foundChainMatch) {
throw new InvalidRequestException(myContext.getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "invalidParameterChain", theParamName + '.' + theRef.getChain()));
throw new InvalidRequestException(myContext.getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "invalidParameterChain", theParamName + '.' + theReferenceParam.getChain()));
}
Predicate predicate = myBuilder.or(toArray(theCodePredicates));
if (candidateTargetTypes.size() > 1) {
warnAboutPerformanceOnUnqualifiedResources(theParamName, theRequest, candidateTargetTypes);
}
Predicate predicate = myCriteriaBuilder.or(toArray(theCodePredicates));
myQueryRoot.addPredicate(predicate);
return predicate;
}
private void warnAboutPerformanceOnUnqualifiedResources(String theParamName, RequestDetails theRequest, List<Class<? extends IBaseResource>> theCandidateTargetTypes) {
String message = new StringBuilder()
.append("This search uses an unqualified resource(a parameter in a chain without a resource type). ")
.append("This is less efficient than using a qualified type. ")
.append("[" + theParamName + "] resolves to ["+ theCandidateTargetTypes.stream().map(Class::getSimpleName).collect(Collectors.joining(",")) +"].")
.append("If you know what you're looking for, try qualifying it like this: ")
.append(theCandidateTargetTypes.stream().map(cls -> "[" +cls.getSimpleName() +":"+theParamName+"]").collect(Collectors.joining(" or ")))
.toString();
StorageProcessingMessage msg = new StorageProcessingMessage()
.setMessage(message);
HookParams params = new HookParams()
.add(RequestDetails.class, theRequest)
.addIfMatchesType(ServletRequestDetails.class, theRequest)
.add(StorageProcessingMessage.class, msg);
JpaInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, theRequest, Pointcut.JPA_PERFTRACE_WARNING, params);
}
Predicate createResourceLinkPathPredicate(String theResourceName, String theParamName, From<?, ? extends ResourceLink> from) {
return createResourceLinkPathPredicate(myContext, theParamName, from, theResourceName);
}
@ -398,8 +435,8 @@ class PredicateBuilderReference extends BasePredicateBuilder {
andOrParams.add(theOrValues);
// Create the subquery predicates
myQueryRoot.addPredicate(myBuilder.equal(myQueryRoot.get("myResourceType"), theSubResourceName));
myQueryRoot.addPredicate(myBuilder.isNull(myQueryRoot.get("myDeleted")));
myQueryRoot.addPredicate(myCriteriaBuilder.equal(myQueryRoot.get("myResourceType"), theSubResourceName));
myQueryRoot.addPredicate(myCriteriaBuilder.isNull(myQueryRoot.get("myDeleted")));
if (theFoundChainMatch) {
searchForIdsWithAndOr(theSubResourceName, theChain, andOrParams, theRequest);
@ -557,9 +594,9 @@ class PredicateBuilderReference extends BasePredicateBuilder {
theResourceName, theRequest);
if (((SearchFilterParser.FilterLogical) theFilter).getOperation() == SearchFilterParser.FilterLogicalOperation.and) {
return myBuilder.and(xPredicate, yPredicate);
return myCriteriaBuilder.and(xPredicate, yPredicate);
} else if (((SearchFilterParser.FilterLogical) theFilter).getOperation() == SearchFilterParser.FilterLogicalOperation.or) {
return myBuilder.or(xPredicate, yPredicate);
return myCriteriaBuilder.or(xPredicate, yPredicate);
}
} else if (theFilter instanceof SearchFilterParser.FilterParameterGroup) {
return processFilter(((SearchFilterParser.FilterParameterGroup) theFilter).getContained(),
@ -744,10 +781,10 @@ class PredicateBuilderReference extends BasePredicateBuilder {
SourceParam sourceParameter = new SourceParam(nextParameter.getValueAsQueryToken(myContext));
String sourceUri = sourceParameter.getSourceUri();
String requestId = sourceParameter.getRequestId();
Predicate sourceUriPredicate = myBuilder.equal(join.get("mySourceUri"), sourceUri);
Predicate requestIdPredicate = myBuilder.equal(join.get("myRequestId"), requestId);
Predicate sourceUriPredicate = myCriteriaBuilder.equal(join.get("mySourceUri"), sourceUri);
Predicate requestIdPredicate = myCriteriaBuilder.equal(join.get("myRequestId"), requestId);
if (isNotBlank(sourceUri) && isNotBlank(requestId)) {
codePredicates.add(myBuilder.and(sourceUriPredicate, requestIdPredicate));
codePredicates.add(myCriteriaBuilder.and(sourceUriPredicate, requestIdPredicate));
} else if (isNotBlank(sourceUri)) {
codePredicates.add(sourceUriPredicate);
} else if (isNotBlank(requestId)) {
@ -755,7 +792,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
}
}
Predicate retVal = myBuilder.or(toArray(codePredicates));
Predicate retVal = myCriteriaBuilder.or(toArray(codePredicates));
myQueryRoot.addPredicate(retVal);
return retVal;
}
@ -791,11 +828,17 @@ class PredicateBuilderReference extends BasePredicateBuilder {
}
assert parameterName != null;
//Ensure that the name of the search param
// (e.g. the `code` in Patient?_has:Observation:subject:code=sys|val)
// exists on the target resource type.
RuntimeSearchParam owningParameterDef = mySearchParamRegistry.getSearchParamByName(targetResourceDefinition, paramName);
if (owningParameterDef == null) {
throw new InvalidRequestException("Unknown parameter name: " + targetResourceType + ':' + parameterName);
}
//Ensure that the name of the back-referenced search param on the target (e.g. the `subject` in Patient?_has:Observation:subject:code=sys|val)
//exists on the target resource.
owningParameterDef = mySearchParamRegistry.getSearchParamByName(targetResourceDefinition, paramReference);
if (owningParameterDef == null) {
throw new InvalidRequestException("Unknown parameter name: " + targetResourceType + ':' + paramReference);
@ -810,18 +853,30 @@ class PredicateBuilderReference extends BasePredicateBuilder {
for (IQueryParameterOr<IQueryParameterType> next : parsedParam.getValuesAsQueryTokens()) {
orValues.addAll(next.getValuesAsQueryTokens());
}
//Handle internal chain inside the has.
if (parameterName.contains(".")) {
String chainedPartOfParameter = getChainedPart(parameterName);
orValues.stream()
.filter(qp -> qp instanceof ReferenceParam)
.map(qp -> (ReferenceParam)qp)
.forEach(rp -> rp.setChain(getChainedPart(chainedPartOfParameter)));
}
Subquery<Long> subQ = myPredicateBuilder.createLinkSubquery(parameterName, targetResourceType, orValues, theRequest);
Subquery<Long> subQ = myPredicateBuilder.createLinkSubquery(paramName, targetResourceType, orValues, theRequest);
Join<ResourceTable, ResourceLink> join = myQueryRoot.join("myResourceLinksAsTarget", JoinType.LEFT);
Predicate pathPredicate = myPredicateBuilder.createResourceLinkPathPredicate(targetResourceType, paramReference, join);
Predicate sourceTypePredicate = myBuilder.equal(join.get("myTargetResourceType"), theResourceType);
Predicate sourceTypePredicate = myCriteriaBuilder.equal(join.get("myTargetResourceType"), theResourceType);
Predicate sourcePidPredicate = join.get("mySourceResourcePid").in(subQ);
Predicate andPredicate = myBuilder.and(pathPredicate, sourcePidPredicate, sourceTypePredicate);
Predicate andPredicate = myCriteriaBuilder.and(pathPredicate, sourcePidPredicate, sourceTypePredicate);
myQueryRoot.addPredicate(andPredicate);
}
}
private String getChainedPart(String parameter) {
return parameter.substring(parameter.indexOf(".") + 1);
}
private void addPredicateComposite(String theResourceName, RuntimeSearchParam theParamDef, List<? extends IQueryParameterType> theNextAnd) {
// TODO: fail if missing is set for a composite query
@ -846,24 +901,24 @@ class PredicateBuilderReference extends BasePredicateBuilder {
switch (theParam.getParamType()) {
case STRING: {
From<ResourceIndexedSearchParamString, ResourceIndexedSearchParamString> stringJoin = theRoot.join("myParamsString", JoinType.INNER);
retVal = myPredicateBuilder.createPredicateString(leftValue, theResourceName, theParam.getName(), myBuilder, stringJoin);
retVal = myPredicateBuilder.createPredicateString(leftValue, theResourceName, theParam.getName(), myCriteriaBuilder, stringJoin);
break;
}
case TOKEN: {
From<ResourceIndexedSearchParamToken, ResourceIndexedSearchParamToken> tokenJoin = theRoot.join("myParamsToken", JoinType.INNER);
List<IQueryParameterType> tokens = Collections.singletonList(leftValue);
Collection<Predicate> tokenPredicates = myPredicateBuilder.createPredicateToken(tokens, theResourceName, theParam.getName(), myBuilder, tokenJoin);
retVal = myBuilder.and(tokenPredicates.toArray(new Predicate[0]));
Collection<Predicate> tokenPredicates = myPredicateBuilder.createPredicateToken(tokens, theResourceName, theParam.getName(), myCriteriaBuilder, tokenJoin);
retVal = myCriteriaBuilder.and(tokenPredicates.toArray(new Predicate[0]));
break;
}
case DATE: {
From<ResourceIndexedSearchParamDate, ResourceIndexedSearchParamDate> dateJoin = theRoot.join("myParamsDate", JoinType.INNER);
retVal = myPredicateBuilder.createPredicateDate(leftValue, theResourceName, theParam.getName(), myBuilder, dateJoin);
retVal = myPredicateBuilder.createPredicateDate(leftValue, theResourceName, theParam.getName(), myCriteriaBuilder, dateJoin);
break;
}
case QUANTITY: {
From<ResourceIndexedSearchParamQuantity, ResourceIndexedSearchParamQuantity> dateJoin = theRoot.join("myParamsQuantity", JoinType.INNER);
retVal = myPredicateBuilder.createPredicateQuantity(leftValue, theResourceName, theParam.getName(), myBuilder, dateJoin);
retVal = myPredicateBuilder.createPredicateQuantity(leftValue, theResourceName, theParam.getName(), myCriteriaBuilder, dateJoin);
break;
}
case COMPOSITE:

View File

@ -110,7 +110,7 @@ class PredicateBuilderResourceId extends BasePredicateBuilder {
if (allOrPids != null && allOrPids.isEmpty()) {
// This will never match
nextPredicate = myBuilder.equal(theRoot.get("myId").as(Long.class), -1);
nextPredicate = myCriteriaBuilder.equal(theRoot.get("myId").as(Long.class), -1);
} else if (allOrPids != null) {
@ -121,11 +121,11 @@ class PredicateBuilderResourceId extends BasePredicateBuilder {
default:
case eq:
codePredicates.add(theRoot.get("myId").as(Long.class).in(ResourcePersistentId.toLongList(allOrPids)));
nextPredicate = myBuilder.and(toArray(codePredicates));
nextPredicate = myCriteriaBuilder.and(toArray(codePredicates));
break;
case ne:
codePredicates.add(theRoot.get("myId").as(Long.class).in(ResourcePersistentId.toLongList(allOrPids)).not());
nextPredicate = myBuilder.and(toArray(codePredicates));
nextPredicate = myCriteriaBuilder.and(toArray(codePredicates));
break;
}

View File

@ -71,13 +71,13 @@ class PredicateBuilderString extends BasePredicateBuilder implements IPredicateB
Predicate singleCode = createPredicateString(theParameter,
theResourceName,
theParamName,
myBuilder,
myCriteriaBuilder,
join,
operation);
codePredicates.add(singleCode);
}
Predicate retVal = myBuilder.or(toArray(codePredicates));
Predicate retVal = myCriteriaBuilder.or(toArray(codePredicates));
myQueryRoot.addPredicate(retVal);
return retVal;
}

View File

@ -134,8 +134,8 @@ class PredicateBuilderTag extends BasePredicateBuilder {
subQ.select(subQfrom.get("myResourceId").as(Long.class));
myQueryRoot.addPredicate(
myBuilder.not(
myBuilder.in(
myCriteriaBuilder.not(
myCriteriaBuilder.in(
myQueryRoot.get("myId")
).value(subQ)
)
@ -147,7 +147,7 @@ class PredicateBuilderTag extends BasePredicateBuilder {
subQ.where(subQfrom.get("myTagId").as(Long.class).in(defJoin));
Predicate tagListPredicate = createPredicateTagList(defJoinFrom, myBuilder, tagType, tokens);
Predicate tagListPredicate = createPredicateTagList(defJoinFrom, myCriteriaBuilder, tagType, tokens);
defJoin.where(tagListPredicate);
continue;
@ -156,7 +156,7 @@ class PredicateBuilderTag extends BasePredicateBuilder {
Join<ResourceTable, ResourceTag> tagJoin = myQueryRoot.join("myTags", JoinType.LEFT);
From<ResourceTag, TagDefinition> defJoin = tagJoin.join("myTag");
Predicate tagListPredicate = createPredicateTagList(defJoin, myBuilder, tagType, tokens);
Predicate tagListPredicate = createPredicateTagList(defJoin, myCriteriaBuilder, tagType, tokens);
myQueryRoot.addPredicate(tagListPredicate);
}

View File

@ -98,11 +98,11 @@ class PredicateBuilderToken extends BasePredicateBuilder implements IPredicateBu
}
Join<ResourceTable, ResourceIndexedSearchParamToken> join = createJoin(SearchBuilderJoinEnum.TOKEN, theParamName);
Collection<Predicate> singleCode = createPredicateToken(tokens, theResourceName, theParamName, myBuilder, join, operation);
Collection<Predicate> singleCode = createPredicateToken(tokens, theResourceName, theParamName, myCriteriaBuilder, join, operation);
assert singleCode != null;
codePredicates.addAll(singleCode);
Predicate spPredicate = myBuilder.or(toArray(codePredicates));
Predicate spPredicate = myCriteriaBuilder.or(toArray(codePredicates));
myQueryRoot.addPredicate(spPredicate);
return spPredicate;
}
@ -345,8 +345,8 @@ class PredicateBuilderToken extends BasePredicateBuilder implements IPredicateBu
private <T> Expression<Boolean> toEqualOrIsNullPredicate(Path<T> theExpression, T theCode) {
if (theCode == null) {
return myBuilder.isNull(theExpression);
return myCriteriaBuilder.isNull(theExpression);
}
return myBuilder.equal(theExpression, theCode);
return myCriteriaBuilder.equal(theExpression, theCode);
}
}

View File

@ -109,37 +109,37 @@ class PredicateBuilderUri extends BasePredicateBuilder implements IPredicateBuil
} else if (param.getQualifier() == UriParamQualifierEnum.BELOW) {
Predicate uriPredicate = myBuilder.like(join.get("myUri").as(String.class), createLeftMatchLikeExpression(value));
Predicate uriPredicate = myCriteriaBuilder.like(join.get("myUri").as(String.class), createLeftMatchLikeExpression(value));
Predicate hashAndUriPredicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, join, uriPredicate);
codePredicates.add(hashAndUriPredicate);
} else {
if (myDontUseHashesForSearch) {
Predicate predicate = myBuilder.equal(join.get("myUri").as(String.class), value);
Predicate predicate = myCriteriaBuilder.equal(join.get("myUri").as(String.class), value);
codePredicates.add(predicate);
} else {
Predicate uriPredicate = null;
if (operation == null || operation == SearchFilterParser.CompareOperation.eq) {
long hashUri = ResourceIndexedSearchParamUri.calculateHashUri(theResourceName, theParamName, value);
Predicate hashPredicate = myBuilder.equal(join.get("myHashUri"), hashUri);
Predicate hashPredicate = myCriteriaBuilder.equal(join.get("myHashUri"), hashUri);
codePredicates.add(hashPredicate);
} else if (operation == SearchFilterParser.CompareOperation.ne) {
uriPredicate = myBuilder.notEqual(join.get("myUri").as(String.class), value);
uriPredicate = myCriteriaBuilder.notEqual(join.get("myUri").as(String.class), value);
} else if (operation == SearchFilterParser.CompareOperation.co) {
uriPredicate = myBuilder.like(join.get("myUri").as(String.class), createLeftAndRightMatchLikeExpression(value));
uriPredicate = myCriteriaBuilder.like(join.get("myUri").as(String.class), createLeftAndRightMatchLikeExpression(value));
} else if (operation == SearchFilterParser.CompareOperation.gt) {
uriPredicate = myBuilder.greaterThan(join.get("myUri").as(String.class), value);
uriPredicate = myCriteriaBuilder.greaterThan(join.get("myUri").as(String.class), value);
} else if (operation == SearchFilterParser.CompareOperation.lt) {
uriPredicate = myBuilder.lessThan(join.get("myUri").as(String.class), value);
uriPredicate = myCriteriaBuilder.lessThan(join.get("myUri").as(String.class), value);
} else if (operation == SearchFilterParser.CompareOperation.ge) {
uriPredicate = myBuilder.greaterThanOrEqualTo(join.get("myUri").as(String.class), value);
uriPredicate = myCriteriaBuilder.greaterThanOrEqualTo(join.get("myUri").as(String.class), value);
} else if (operation == SearchFilterParser.CompareOperation.le) {
uriPredicate = myBuilder.lessThanOrEqualTo(join.get("myUri").as(String.class), value);
uriPredicate = myCriteriaBuilder.lessThanOrEqualTo(join.get("myUri").as(String.class), value);
} else if (operation == SearchFilterParser.CompareOperation.sw) {
uriPredicate = myBuilder.like(join.get("myUri").as(String.class), createLeftMatchLikeExpression(value));
uriPredicate = myCriteriaBuilder.like(join.get("myUri").as(String.class), createLeftMatchLikeExpression(value));
} else if (operation == SearchFilterParser.CompareOperation.ew) {
uriPredicate = myBuilder.like(join.get("myUri").as(String.class), createRightMatchLikeExpression(value));
uriPredicate = myCriteriaBuilder.like(join.get("myUri").as(String.class), createRightMatchLikeExpression(value));
} else {
throw new IllegalArgumentException(String.format("Unsupported operator specified in _filter clause, %s",
operation.toString()));
@ -147,8 +147,8 @@ class PredicateBuilderUri extends BasePredicateBuilder implements IPredicateBuil
if (uriPredicate != null) {
long hashIdentity = BaseResourceIndexedSearchParam.calculateHashIdentity(theResourceName, theParamName);
Predicate hashIdentityPredicate = myBuilder.equal(join.get("myHashIdentity"), hashIdentity);
codePredicates.add(myBuilder.and(hashIdentityPredicate, uriPredicate));
Predicate hashIdentityPredicate = myCriteriaBuilder.equal(join.get("myHashIdentity"), hashIdentity);
codePredicates.add(myCriteriaBuilder.and(hashIdentityPredicate, uriPredicate));
}
}
}
@ -164,12 +164,12 @@ class PredicateBuilderUri extends BasePredicateBuilder implements IPredicateBuil
* just add a predicate that can never match
*/
if (codePredicates.isEmpty()) {
Predicate predicate = myBuilder.isNull(join.get("myMissing").as(String.class));
Predicate predicate = myCriteriaBuilder.isNull(join.get("myMissing").as(String.class));
myQueryRoot.addPredicate(predicate);
return null;
}
Predicate orPredicate = myBuilder.or(toArray(codePredicates));
Predicate orPredicate = myCriteriaBuilder.or(toArray(codePredicates));
Predicate outerPredicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName,
theParamName,

View File

@ -1,6 +1,7 @@
package ca.uhn.fhir.jpa.dao.dstu3;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.DaoMethodOutcome;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.rest.api.server.IBundleProvider;

View File

@ -5,6 +5,7 @@ import ca.uhn.fhir.jpa.model.entity.*;
import ca.uhn.fhir.jpa.searchparam.SearchParamConstants;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap.EverythingModeEnum;
import ca.uhn.fhir.jpa.util.SqlQuery;
import ca.uhn.fhir.jpa.util.TestUtil;
import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
@ -15,6 +16,7 @@ import ca.uhn.fhir.rest.api.SortSpec;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.*;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.method.SearchParameter;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.dstu3.model.*;
@ -47,6 +49,7 @@ import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
@ -362,16 +365,16 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test {
obs.addIdentifier().setSystem("urn:system").setValue("FOO");
obs.setDevice(new Reference(devId));
obs.setSubject(new Reference(pid0));
myObservationDao.create(obs, mySrd).getId();
obs.setCode(new CodeableConcept(new Coding("sys", "val", "disp")));
myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
}
SearchParameterMap params;
SearchParameterMap params = new SearchParameterMap();
// Not currently working
// params = new SearchParameterMap();
// params.setLoadSynchronous(true);
// params.add("_has", new HasParam("Observation", "subject", "device.identifier", "urn:system|DEVICEID"));
// assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(params)), contains(pid0.getValue()));
// Target exists and is linked
params.setLoadSynchronous(true);
params.add("_has", new HasParam("Observation", "subject", "device.identifier", "urn:system|DEVICEID"));
assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(params)), contains(pid0.getValue()));
// No targets exist
params = new SearchParameterMap();

View File

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

View File

@ -18,6 +18,7 @@ import org.apache.commons.lang3.builder.ToStringStyle;
import java.io.Serializable;
import java.util.*;
import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
@ -455,45 +456,33 @@ public class SearchParameterMap implements Serializable {
return b.toString();
}
public void clean() {
for (Map.Entry<String, List<List<IQueryParameterType>>> nextParamEntry : this.entrySet()) {
String nextParamName = nextParamEntry.getKey();
List<List<IQueryParameterType>> andOrParams = nextParamEntry.getValue();
clean(nextParamName, andOrParams);
cleanParameter(nextParamName, andOrParams);
}
}
/*
* Filter out
* Given a particular named parameter, e.g. `name`, iterate over AndOrParams and remove any which are empty.
*/
private void clean(String theParamName, List<List<IQueryParameterType>> theAndOrParams) {
for (int andListIdx = 0; andListIdx < theAndOrParams.size(); andListIdx++) {
List<? extends IQueryParameterType> nextOrList = theAndOrParams.get(andListIdx);
private void cleanParameter(String theParamName, List<List<IQueryParameterType>> theAndOrParams) {
theAndOrParams
.forEach(
orList -> {
List<IQueryParameterType> emptyParameters = orList.stream()
.filter(nextOr -> nextOr.getMissing() == null)
.filter(nextOr -> nextOr instanceof QuantityParam)
.filter(nextOr -> isBlank(((QuantityParam) nextOr).getValueAsString()))
.collect(Collectors.toList());
for (int orListIdx = 0; orListIdx < nextOrList.size(); orListIdx++) {
IQueryParameterType nextOr = nextOrList.get(orListIdx);
boolean hasNoValue = false;
if (nextOr.getMissing() != null) {
continue;
}
if (nextOr instanceof QuantityParam) {
if (isBlank(((QuantityParam) nextOr).getValueAsString())) {
hasNoValue = true;
}
}
if (hasNoValue) {
ourLog.debug("Ignoring empty parameter: {}", theParamName);
nextOrList.remove(orListIdx);
orListIdx--;
orList.removeAll(emptyParameters);
}
}
if (nextOrList.isEmpty()) {
theAndOrParams.remove(andListIdx);
andListIdx--;
}
}
);
theAndOrParams.removeIf(List::isEmpty);
}
public void setNearDistanceParam(QuantityParam theQuantityParam) {