Merge remote-tracking branch 'remotes/origin/master' into im_20200316_lastn_operation_elasticsearch

# Conflicts:
#	hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java
This commit is contained in:
ianmarshall 2020-05-19 15:20:06 -04:00
commit d2b54d6203
37 changed files with 1208 additions and 633 deletions

View File

@ -0,0 +1,7 @@
---
type: fix
issue: 1842
title: "When performing queries with multiple chained search parameters, such as 'Observation?subject.identifier=FOO&specimen.identifier=BAR',
an unnecessary SQL join was introduced into the resulting query. This was inefficient, and made it particularly hard for the RDBMS optimizer
to pick an efficient query plan in some cases. This is not fixing a regression (this issue has always existed in HAPI FHIR JPA) but it
was deemed sufficiently important to merit a dedicated point release."

View File

@ -0,0 +1,6 @@
---
type: fix
issue: 1847
title: "Issue #1849 added two new search columns for date SearchParameters that are used to provide searching
at the day granularity in a timezone independent way. These new columns require a new database index that
was missed in the original merge."

View File

@ -0,0 +1,3 @@
---
release-date: "2020-05-15"
codename: "Labrador"

View File

@ -35,13 +35,12 @@ import ca.uhn.fhir.jpa.dao.data.IResourceTagDao;
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
import ca.uhn.fhir.jpa.dao.predicate.PredicateBuilder;
import ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderFactory;
import ca.uhn.fhir.jpa.dao.predicate.QueryRoot;
import ca.uhn.fhir.jpa.dao.predicate.querystack.QueryStack;
import ca.uhn.fhir.jpa.dao.predicate.SearchBuilderJoinEnum;
import ca.uhn.fhir.jpa.dao.predicate.SearchBuilderJoinKey;
import ca.uhn.fhir.jpa.entity.ResourceSearchView;
import ca.uhn.fhir.jpa.interceptor.JpaPreResourceAccessDetails;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedCompositeStringUnique;
import ca.uhn.fhir.jpa.model.entity.ResourceLink;
@ -73,6 +72,7 @@ import ca.uhn.fhir.rest.api.SortOrderEnum;
import ca.uhn.fhir.rest.api.SortSpec;
import ca.uhn.fhir.rest.api.server.IPreResourceAccessDetails;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.param.ReferenceParam;
import ca.uhn.fhir.rest.param.StringParam;
@ -101,7 +101,6 @@ import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.From;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.Order;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
@ -113,6 +112,7 @@ import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import static org.apache.commons.lang3.StringUtils.defaultString;
@ -137,7 +137,6 @@ public class SearchBuilder implements ISearchBuilder {
private static final List<ResourcePersistentId> EMPTY_LONG_LIST = Collections.unmodifiableList(new ArrayList<>());
private static final Logger ourLog = LoggerFactory.getLogger(SearchBuilder.class);
private static final ResourcePersistentId NO_MORE = new ResourcePersistentId(-1L);
private final QueryRoot myQueryRoot = new QueryRoot();
private final String myResourceName;
private final Class<? extends IBaseResource> myResourceType;
private final IDao myCallingDao;
@ -147,6 +146,7 @@ public class SearchBuilder implements ISearchBuilder {
protected IResourceTagDao myResourceTagDao;
@PersistenceContext(type = PersistenceContextType.TRANSACTION)
protected EntityManager myEntityManager;
private QueryStack myQueryStack;
@Autowired
private DaoConfig myDaoConfig;
@Autowired
@ -275,94 +275,16 @@ public class SearchBuilder implements ISearchBuilder {
}
private void init(SearchParameterMap theParams, String theSearchUuid, RequestPartitionId theRequestPartitionId) {
myParams = theParams;
myCriteriaBuilder = myEntityManager.getCriteriaBuilder();
myQueryStack = new QueryStack(myCriteriaBuilder, myResourceName, theParams, theRequestPartitionId);
myParams = theParams;
mySearchUuid = theSearchUuid;
myPredicateBuilder = new PredicateBuilder(this, myPredicateBuilderFactory);
myRequestPartitionId = theRequestPartitionId;
}
private List<TypedQuery<Long>> createQuery(SortSpec sort, Integer theMaximumResults, boolean theCount, RequestDetails theRequest,
SearchRuntimeDetails theSearchRuntimeDetails) {
List<ResourcePersistentId> pids = new ArrayList<>();
/*
* Fulltext or lastn search
*/
if (myParams.containsKey(Constants.PARAM_CONTENT) || myParams.containsKey(Constants.PARAM_TEXT) || myParams.isLastN()) {
if (myParams.containsKey(Constants.PARAM_CONTENT) || myParams.containsKey(Constants.PARAM_TEXT)) {
if (myFulltextSearchSvc == null) {
if (myParams.containsKey(Constants.PARAM_TEXT)) {
throw new InvalidRequestException("Fulltext search is not enabled on this service, can not process parameter: " + Constants.PARAM_TEXT);
} else if (myParams.containsKey(Constants.PARAM_CONTENT)) {
throw new InvalidRequestException("Fulltext search is not enabled on this service, can not process parameter: " + Constants.PARAM_CONTENT);
}
}
if (myParams.getEverythingMode() != null) {
pids = myFulltextSearchSvc.everything(myResourceName, myParams, theRequest);
} else {
pids = myFulltextSearchSvc.search(myResourceName, myParams);
}
} else if (myParams.isLastN()) {
if (myIElasticsearchSvc == null) {
if (myParams.isLastN()) {
throw new InvalidRequestException("LastN operation is not enabled on this service, can not process this request");
}
}
Integer myMaxObservationsPerCode;
if(myParams.getLastNMax() != null) {
myMaxObservationsPerCode = myParams.getLastNMax();
} else {
throw new InvalidRequestException("Max parameter is required for $lastn operation");
}
List<String> lastnResourceIds = myIElasticsearchSvc.executeLastN(myParams, myContext, theMaximumResults);
for (String lastnResourceId : lastnResourceIds) {
pids.add(myIdHelperService.resolveResourcePersistentIds(myRequestPartitionId, myResourceName, lastnResourceId));
}
}
if (theSearchRuntimeDetails != null) {
theSearchRuntimeDetails.setFoundIndexMatchesCount(pids.size());
HookParams params = new HookParams()
.add(RequestDetails.class, theRequest)
.addIfMatchesType(ServletRequestDetails.class, theRequest)
.add(SearchRuntimeDetails.class, theSearchRuntimeDetails);
JpaInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, theRequest, Pointcut.JPA_PERFTRACE_INDEXSEARCH_QUERY_COMPLETE, params);
}
if (pids.isEmpty()) {
// Will never match
pids = Collections.singletonList(new ResourcePersistentId(-1L));
}
}
ArrayList<TypedQuery<Long>> myQueries = new ArrayList<>();
if (!pids.isEmpty()) {
if (theMaximumResults != null && pids.size() > theMaximumResults) {
pids.subList(0,theMaximumResults-1);
}
new QueryChunker<Long>().chunk(ResourcePersistentId.toLongList(pids), t->{
doCreateChunkedQueries(t, sort, theCount, theRequest, myQueries);
});
} else {
myQueries.add(createChunkedQuery(sort,theMaximumResults, theCount, theRequest, null));
}
return myQueries;
}
private void doCreateChunkedQueries(List<Long> thePids, SortSpec sort, boolean theCount, RequestDetails theRequest, ArrayList<TypedQuery<Long>> theQueries) {
if(thePids.size() < getMaximumPageSize()) {
normalizeIdListForLastNInClause(thePids);
}
theQueries.add(createChunkedQuery(sort, thePids.size(), theCount, theRequest, thePids));
}
private TypedQuery<Long> createChunkedQuery(SortSpec sort, Integer theMaximumResults, boolean theCount, RequestDetails theRequest, List<Long> thePidList) {
CriteriaQuery<Long> outerQuery;
private TypedQuery<Long> createQuery(SortSpec sort, Integer theMaximumResults, boolean theCount, RequestDetails theRequest) {
/*
* Sort
*
@ -372,36 +294,24 @@ public class SearchBuilder implements ISearchBuilder {
if (sort != null) {
assert !theCount;
outerQuery = myCriteriaBuilder.createQuery(Long.class);
myQueryRoot.push(outerQuery);
if (theCount) {
outerQuery.multiselect(myCriteriaBuilder.countDistinct(myQueryRoot.getRoot()));
} else {
outerQuery.multiselect(myQueryRoot.get("myId").as(Long.class));
}
myQueryStack.pushResourceTableQuery();
List<Order> orders = Lists.newArrayList();
createSort(myCriteriaBuilder, myQueryRoot, sort, orders);
List<Order> orders = createSort(myCriteriaBuilder, myQueryStack, sort);
if (orders.size() > 0) {
outerQuery.orderBy(orders);
myQueryStack.orderBy(orders);
}
} else {
outerQuery = myCriteriaBuilder.createQuery(Long.class);
myQueryRoot.push(outerQuery);
if (theCount) {
outerQuery.multiselect(myCriteriaBuilder.countDistinct(myQueryRoot.getRoot()));
myQueryStack.pushResourceTableCountQuery();
} else {
outerQuery.multiselect(myQueryRoot.get("myId").as(Long.class));
// KHS This distinct call is causing performance issues in large installations
// outerQuery.distinct(true);
myQueryStack.pushResourceTableQuery();
}
}
if (myParams.getEverythingMode() != null) {
Join<ResourceTable, ResourceLink> join = myQueryRoot.join("myResourceLinks", JoinType.LEFT);
From<?, ResourceLink> join = myQueryStack.createJoin(SearchBuilderJoinEnum.REFERENCE, null);
if (myParams.get(IAnyResource.SP_RES_ID) != null) {
StringParam idParam = (StringParam) myParams.get(IAnyResource.SP_RES_ID).get(0).get(0);
@ -410,11 +320,11 @@ public class SearchBuilder implements ISearchBuilder {
myAlsoIncludePids = new ArrayList<>(1);
}
myAlsoIncludePids.add(pid);
myQueryRoot.addPredicate(myCriteriaBuilder.equal(join.get("myTargetResourcePid").as(Long.class), pid.getIdAsLong()));
myQueryStack.addPredicate(myCriteriaBuilder.equal(join.get("myTargetResourcePid").as(Long.class), pid.getIdAsLong()));
} else {
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));
Predicate sourceTypePredicate = myCriteriaBuilder.equal(myQueryStack.get("myResourceType").as(String.class), myResourceName);
myQueryStack.addPredicate(myCriteriaBuilder.or(sourceTypePredicate, targetTypePredicate));
}
} else {
@ -422,43 +332,43 @@ public class SearchBuilder implements ISearchBuilder {
searchForIdsWithAndOr(myParams, theRequest);
}
// Add PID list predicate for full text search and/or lastn operation
if (thePidList != null && thePidList.size() > 0) {
myQueryRoot.addPredicate(myQueryRoot.get("myId").as(Long.class).in(thePidList));
/*
* Fulltext search
*/
if (myParams.containsKey(Constants.PARAM_CONTENT) || myParams.containsKey(Constants.PARAM_TEXT)) {
if (myFulltextSearchSvc == null) {
if (myParams.containsKey(Constants.PARAM_TEXT)) {
throw new InvalidRequestException("Fulltext search is not enabled on this service, can not process parameter: " + Constants.PARAM_TEXT);
} else if (myParams.containsKey(Constants.PARAM_CONTENT)) {
throw new InvalidRequestException("Fulltext search is not enabled on this service, can not process parameter: " + Constants.PARAM_CONTENT);
}
}
/*
* Add a predicate to make sure we only include non-deleted resources, and only include
* resources of the right type.
*
* If we have any joins to index tables, we get this behaviour already guaranteed so we don't
* need an explicit predicate for it.
*/
if (!myQueryRoot.hasIndexJoins()) {
if (myParams.getEverythingMode() == null) {
myQueryRoot.addPredicate(myCriteriaBuilder.equal(myQueryRoot.get("myResourceType"), myResourceName));
}
myQueryRoot.addPredicate(myCriteriaBuilder.isNull(myQueryRoot.get("myDeleted")));
if (!myRequestPartitionId.isAllPartitions()) {
if (myRequestPartitionId.getPartitionId() != null) {
myQueryRoot.addPredicate(myCriteriaBuilder.equal(myQueryRoot.get("myPartitionIdValue").as(Integer.class), myRequestPartitionId.getPartitionId()));
List<ResourcePersistentId> pids;
if (myParams.getEverythingMode() != null) {
pids = myFulltextSearchSvc.everything(myResourceName, myParams, theRequest);
} else {
myQueryRoot.addPredicate(myCriteriaBuilder.isNull(myQueryRoot.get("myPartitionIdValue").as(Integer.class)));
pids = myFulltextSearchSvc.search(myResourceName, myParams);
}
if (pids.isEmpty()) {
// Will never match
pids = Collections.singletonList(new ResourcePersistentId(-1L));
}
myQueryStack.addPredicate(myQueryStack.get("myId").as(Long.class).in(ResourcePersistentId.toLongList(pids)));
}
// Last updated
DateRangeParam lu = myParams.getLastUpdated();
List<Predicate> lastUpdatedPredicates = createLastUpdatedPredicates(lu, myCriteriaBuilder, myQueryRoot.getRoot());
myQueryRoot.addPredicates(lastUpdatedPredicates);
myQueryRoot.where(myCriteriaBuilder.and(myQueryRoot.getPredicateArray()));
List<Predicate> lastUpdatedPredicates = createLastUpdatedPredicates(lu, myCriteriaBuilder);
myQueryStack.addPredicates(lastUpdatedPredicates);
/*
* Now perform the search
*/
CriteriaQuery<Long> outerQuery = (CriteriaQuery<Long>) myQueryStack.pop();
final TypedQuery<Long> query = myEntityManager.createQuery(outerQuery);
assert myQueryStack.isEmpty();
if (theMaximumResults != null) {
query.setMaxResults(theMaximumResults);
@ -505,32 +415,35 @@ public class SearchBuilder implements ISearchBuilder {
* @return Returns {@literal true} if any search parameter sorts were found, or false if
* no sorts were found, or only non-search parameters ones (e.g. _id, _lastUpdated)
*/
private boolean createSort(CriteriaBuilder theBuilder, QueryRoot theQueryRoot, SortSpec theSort, List<Order> theOrders) {
private List<Order> createSort(CriteriaBuilder theBuilder, QueryStack theQueryStack, SortSpec theSort) {
if (theSort == null || isBlank(theSort.getParamName())) {
return false;
return Collections.emptyList();
}
List<Order> orders = new ArrayList<>(1);
if (IAnyResource.SP_RES_ID.equals(theSort.getParamName())) {
From<?, ?> forcedIdJoin = theQueryRoot.join("myForcedId", JoinType.LEFT);
From<?, ?> forcedIdJoin = theQueryStack.createJoin(SearchBuilderJoinEnum.FORCED_ID, null);
if (theSort.getOrder() == null || theSort.getOrder() == SortOrderEnum.ASC) {
theOrders.add(theBuilder.asc(forcedIdJoin.get("myForcedId")));
theOrders.add(theBuilder.asc(theQueryRoot.get("myId")));
orders.add(theBuilder.asc(forcedIdJoin.get("myForcedId")));
orders.add(theBuilder.asc(theQueryStack.get("myId")));
} else {
theOrders.add(theBuilder.desc(forcedIdJoin.get("myForcedId")));
theOrders.add(theBuilder.desc(theQueryRoot.get("myId")));
orders.add(theBuilder.desc(forcedIdJoin.get("myForcedId")));
orders.add(theBuilder.desc(theQueryStack.get("myId")));
}
return createSort(theBuilder, theQueryRoot, theSort.getChain(), theOrders);
orders.addAll(createSort(theBuilder, theQueryStack, theSort.getChain()));
return orders;
}
if (Constants.PARAM_LASTUPDATED.equals(theSort.getParamName())) {
if (theSort.getOrder() == null || theSort.getOrder() == SortOrderEnum.ASC) {
theOrders.add(theBuilder.asc(theQueryRoot.get("myUpdated")));
orders.add(theBuilder.asc(theQueryStack.get("myUpdated")));
} else {
theOrders.add(theBuilder.desc(theQueryRoot.get("myUpdated")));
orders.add(theBuilder.desc(theQueryStack.get("myUpdated")));
}
return createSort(theBuilder, theQueryRoot, theSort.getChain(), theOrders);
orders.addAll(createSort(theBuilder, theQueryStack, theSort.getChain()));
return orders;
}
RuntimeResourceDefinition resourceDef = myContext.getResourceDefinition(myResourceName);
@ -539,43 +452,35 @@ public class SearchBuilder implements ISearchBuilder {
throw new InvalidRequestException("Unknown sort parameter '" + theSort.getParamName() + "'");
}
String joinAttrName;
String[] sortAttrName;
SearchBuilderJoinEnum joinType;
switch (param.getParamType()) {
case STRING:
joinAttrName = "myParamsString";
sortAttrName = new String[]{"myValueExact"};
joinType = SearchBuilderJoinEnum.STRING;
break;
case DATE:
joinAttrName = "myParamsDate";
sortAttrName = new String[]{"myValueLow"};
joinType = SearchBuilderJoinEnum.DATE;
break;
case REFERENCE:
joinAttrName = "myResourceLinks";
sortAttrName = new String[]{"myTargetResourcePid"};
joinType = SearchBuilderJoinEnum.REFERENCE;
break;
case TOKEN:
joinAttrName = "myParamsToken";
sortAttrName = new String[]{"mySystem", "myValue"};
joinType = SearchBuilderJoinEnum.TOKEN;
break;
case NUMBER:
joinAttrName = "myParamsNumber";
sortAttrName = new String[]{"myValue"};
joinType = SearchBuilderJoinEnum.NUMBER;
break;
case URI:
joinAttrName = "myParamsUri";
sortAttrName = new String[]{"myUri"};
joinType = SearchBuilderJoinEnum.URI;
break;
case QUANTITY:
joinAttrName = "myParamsQuantity";
sortAttrName = new String[]{"myValue"};
joinType = SearchBuilderJoinEnum.QUANTITY;
break;
@ -591,37 +496,40 @@ public class SearchBuilder implements ISearchBuilder {
* sorting on, we'll also sort with it. Otherwise we need a new join.
*/
SearchBuilderJoinKey key = new SearchBuilderJoinKey(theSort.getParamName(), joinType);
Join<?, ?> join = theQueryRoot.getIndexJoin(key);
if (join == null) {
join = theQueryRoot.join(joinAttrName, JoinType.LEFT);
Optional<Join<?, ?>> joinOpt = theQueryStack.getExistingJoin(key);
From<?, ?> join;
if (!joinOpt.isPresent()) {
join = theQueryStack.createJoin(joinType, theSort.getParamName());
if (param.getParamType() == RestSearchParameterTypeEnum.REFERENCE) {
theQueryRoot.addPredicate(join.get("mySourcePath").as(String.class).in(param.getPathsSplit()));
theQueryStack.addPredicate(join.get("mySourcePath").as(String.class).in(param.getPathsSplit()));
} else {
if (myDaoConfig.getDisableHashBasedSearches()) {
Predicate joinParam1 = theBuilder.equal(join.get("myParamName"), theSort.getParamName());
theQueryRoot.addPredicate(joinParam1);
theQueryStack.addPredicate(joinParam1);
} else {
Long hashIdentity = BaseResourceIndexedSearchParam.calculateHashIdentity(myPartitionSettings, myRequestPartitionId, myResourceName, theSort.getParamName());
Predicate joinParam1 = theBuilder.equal(join.get("myHashIdentity"), hashIdentity);
theQueryRoot.addPredicate(joinParam1);
theQueryStack.addPredicate(joinParam1);
}
}
} else {
ourLog.debug("Reusing join for {}", theSort.getParamName());
join = joinOpt.get();
}
for (String next : sortAttrName) {
if (theSort.getOrder() == null || theSort.getOrder() == SortOrderEnum.ASC) {
theOrders.add(theBuilder.asc(join.get(next)));
orders.add(theBuilder.asc(join.get(next)));
} else {
theOrders.add(theBuilder.desc(join.get(next)));
orders.add(theBuilder.desc(join.get(next)));
}
}
createSort(theBuilder, theQueryRoot, theSort.getChain(), theOrders);
orders.addAll(createSort(theBuilder, theQueryStack, theSort.getChain()));
return true;
return orders;
}
@ -737,7 +645,7 @@ public class SearchBuilder implements ISearchBuilder {
List<ResourcePersistentId> pids = new ArrayList<>(thePids);
new QueryChunker<ResourcePersistentId>().chunk(pids, t -> {
doLoadPids(t, theIncludedPids, theResourceListToPopulate, theForHistoryOperation, position);
doLoadPids(t, theIncludedPids, theResourceListToPopulate, theForHistoryOperation, position, theDetails);
});
}
@ -1005,17 +913,16 @@ public class SearchBuilder implements ISearchBuilder {
}
private void addPredicateCompositeStringUnique(@Nonnull SearchParameterMap theParams, String theIndexedString, RequestPartitionId theRequestPartitionId) {
Join<ResourceTable, ResourceIndexedCompositeStringUnique> join = myQueryRoot.join("myParamsCompositeStringUnique", JoinType.LEFT);
From<?, ResourceIndexedCompositeStringUnique> join = myQueryStack.createJoin(SearchBuilderJoinEnum.COMPOSITE_UNIQUE, null);
if (!theRequestPartitionId.isAllPartitions()) {
Integer partitionId = theRequestPartitionId.getPartitionId();
Predicate predicate = myCriteriaBuilder.equal(join.get("myPartitionIdValue").as(Integer.class), partitionId);
myQueryRoot.addPredicate(predicate);
myQueryStack.addPredicate(predicate);
}
myQueryRoot.setHasIndexJoins();
Predicate predicate = myCriteriaBuilder.equal(join.get("myIndexString"), theIndexedString);
myQueryRoot.addPredicate(predicate);
myQueryStack.addPredicateWithImplicitTypeSelection(predicate);
// Remove any empty parameters remaining after this
theParams.clean();
@ -1044,8 +951,8 @@ public class SearchBuilder implements ISearchBuilder {
return myCriteriaBuilder;
}
public QueryRoot getQueryRoot() {
return myQueryRoot;
public QueryStack getQueryStack() {
return myQueryStack;
}
public Class<? extends IBaseResource> getResourceType() {
@ -1061,6 +968,22 @@ public class SearchBuilder implements ISearchBuilder {
myDaoConfig = theDaoConfig;
}
private List<Predicate> createLastUpdatedPredicates(final DateRangeParam theLastUpdated, CriteriaBuilder builder) {
List<Predicate> lastUpdatedPredicates = new ArrayList<>();
if (theLastUpdated != null) {
if (theLastUpdated.getLowerBoundAsInstant() != null) {
ourLog.debug("LastUpdated lower bound: {}", new InstantDt(theLastUpdated.getLowerBoundAsInstant()));
Predicate predicateLower = builder.greaterThanOrEqualTo(myQueryStack.getLastUpdatedColumn(), theLastUpdated.getLowerBoundAsInstant());
lastUpdatedPredicates.add(predicateLower);
}
if (theLastUpdated.getUpperBoundAsInstant() != null) {
Predicate predicateUpper = builder.lessThanOrEqualTo(myQueryStack.getLastUpdatedColumn(), theLastUpdated.getUpperBoundAsInstant());
lastUpdatedPredicates.add(predicateUpper);
}
}
return lastUpdatedPredicates;
}
public class IncludesIterator extends BaseIterator<ResourcePersistentId> implements Iterator<ResourcePersistentId> {
private final RequestDetails myRequest;

View File

@ -24,11 +24,10 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.dao.predicate.querystack.QueryStack;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.BasePartitionable;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.model.entity.SearchParamPresent;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.model.api.IQueryParameterType;
@ -43,8 +42,6 @@ import javax.annotation.PostConstruct;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.From;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.Predicate;
import java.math.BigDecimal;
import java.math.MathContext;
@ -54,7 +51,7 @@ import java.util.List;
abstract class BasePredicateBuilder {
private static final Logger ourLog = LoggerFactory.getLogger(BasePredicateBuilder.class);
final CriteriaBuilder myCriteriaBuilder;
final QueryRoot myQueryRoot;
final QueryStack myQueryStack;
final Class<? extends IBaseResource> myResourceType;
final String myResourceName;
final SearchParameterMap myParams;
@ -68,7 +65,7 @@ abstract class BasePredicateBuilder {
BasePredicateBuilder(SearchBuilder theSearchBuilder) {
myCriteriaBuilder = theSearchBuilder.getBuilder();
myQueryRoot = theSearchBuilder.getQueryRoot();
myQueryStack = theSearchBuilder.getQueryStack();
myResourceType = theSearchBuilder.getResourceType();
myResourceName = theSearchBuilder.getResourceName();
myParams = theSearchBuilder.getParams();
@ -79,44 +76,8 @@ abstract class BasePredicateBuilder {
myDontUseHashesForSearch = myDaoConfig.getDisableHashBasedSearches();
}
@SuppressWarnings("unchecked")
<T> Join<ResourceTable, T> createJoin(SearchBuilderJoinEnum theType, String theSearchParameterName) {
Join<ResourceTable, ResourceIndexedSearchParamDate> join = null;
switch (theType) {
case DATE:
join = myQueryRoot.join("myParamsDate", JoinType.LEFT);
break;
case NUMBER:
join = myQueryRoot.join("myParamsNumber", JoinType.LEFT);
break;
case QUANTITY:
join = myQueryRoot.join("myParamsQuantity", JoinType.LEFT);
break;
case REFERENCE:
join = myQueryRoot.join("myResourceLinks", JoinType.LEFT);
break;
case STRING:
join = myQueryRoot.join("myParamsString", JoinType.LEFT);
break;
case URI:
join = myQueryRoot.join("myParamsUri", JoinType.LEFT);
break;
case TOKEN:
join = myQueryRoot.join("myParamsToken", JoinType.LEFT);
break;
case COORDS:
join = myQueryRoot.join("myParamsCoords", JoinType.LEFT);
break;
}
SearchBuilderJoinKey key = new SearchBuilderJoinKey(theSearchParameterName, theType);
myQueryRoot.putIndex(key, join);
return (Join<ResourceTable, T>) join;
}
void addPredicateParamMissingForReference(String theResourceName, String theParamName, boolean theMissing, RequestPartitionId theRequestPartitionId) {
Join<ResourceTable, SearchParamPresent> paramPresentJoin = myQueryRoot.join("mySearchParamPresents", JoinType.LEFT);
From<?, SearchParamPresent> paramPresentJoin = myQueryStack.createJoin(SearchBuilderJoinEnum.PRESENCE, null);
Expression<Long> hashPresence = paramPresentJoin.get("myHashPresence").as(Long.class);
Long hash = SearchParamPresent.calculateHashPresence(myPartitionSettings, theRequestPartitionId, theResourceName, theParamName, !theMissing);
@ -126,22 +87,20 @@ abstract class BasePredicateBuilder {
addPartitionIdPredicate(theRequestPartitionId, paramPresentJoin, predicates);
myQueryRoot.setHasIndexJoins();
myQueryRoot.addPredicates(predicates);
myQueryStack.addPredicatesWithImplicitTypeSelection(predicates);
}
void addPredicateParamMissingForNonReference(String theResourceName, String theParamName, boolean theMissing, Join<ResourceTable, ? extends BaseResourceIndexedSearchParam> theJoin, RequestPartitionId theRequestPartitionId) {
void addPredicateParamMissingForNonReference(String theResourceName, String theParamName, boolean theMissing, From<?, ? extends BaseResourceIndexedSearchParam> theJoin, RequestPartitionId theRequestPartitionId) {
if (!theRequestPartitionId.isAllPartitions()) {
if (theRequestPartitionId.getPartitionId() != null) {
myQueryRoot.addPredicate(myCriteriaBuilder.equal(theJoin.get("myPartitionIdValue"), theRequestPartitionId.getPartitionId()));
myQueryStack.addPredicate(myCriteriaBuilder.equal(theJoin.get("myPartitionIdValue"), theRequestPartitionId.getPartitionId()));
} else {
myQueryRoot.addPredicate(myCriteriaBuilder.isNull(theJoin.get("myPartitionIdValue")));
myQueryStack.addPredicate(myCriteriaBuilder.isNull(theJoin.get("myPartitionIdValue")));
}
}
myQueryRoot.addPredicate(myCriteriaBuilder.equal(theJoin.get("myResourceType"), theResourceName));
myQueryRoot.addPredicate(myCriteriaBuilder.equal(theJoin.get("myParamName"), theParamName));
myQueryRoot.addPredicate(myCriteriaBuilder.equal(theJoin.get("myMissing"), theMissing));
myQueryRoot.setHasIndexJoins();
myQueryStack.addPredicateWithImplicitTypeSelection(myCriteriaBuilder.equal(theJoin.get("myResourceType"), theResourceName));
myQueryStack.addPredicate(myCriteriaBuilder.equal(theJoin.get("myParamName"), theParamName));
myQueryStack.addPredicate(myCriteriaBuilder.equal(theJoin.get("myMissing"), theMissing));
}
Predicate combineParamIndexPredicateWithParamNamePredicate(String theResourceName, String theParamName, From<?, ? extends BaseResourceIndexedSearchParam> theFrom, Predicate thePredicate, RequestPartitionId theRequestPartitionId) {
@ -232,7 +191,7 @@ abstract class BasePredicateBuilder {
} else {
partitionPredicate = myCriteriaBuilder.isNull(theJoin.get("myPartitionIdValue").as(Integer.class));
}
myQueryRoot.addPredicate(partitionPredicate);
myQueryStack.addPredicate(partitionPredicate);
}
}

View File

@ -20,8 +20,6 @@ package ca.uhn.fhir.jpa.dao.predicate;
* #L%
*/
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import com.google.common.collect.Maps;
import javax.persistence.criteria.Join;
@ -30,7 +28,7 @@ import java.util.Map;
public class IndexJoins {
Map<SearchBuilderJoinKey, Join<?, ?>> myIndexJoins = Maps.newHashMap();
public void put(SearchBuilderJoinKey theKey, Join<ResourceTable, ResourceIndexedSearchParamDate> theJoin) {
public void put(SearchBuilderJoinKey theKey, Join<?, ?> theJoin) {
myIndexJoins.put(theKey, theJoin);
}

View File

@ -97,10 +97,10 @@ public class PredicateBuilder {
}
Subquery<Long> createLinkSubquery(String theParameterName, String theTargetResourceType, ArrayList<IQueryParameterType> theOrValues, RequestDetails theRequest, RequestPartitionId theRequestPartitionId) {
return myPredicateBuilderReference.createLinkSubquery(true, theParameterName, theTargetResourceType, theOrValues, theRequest, theRequestPartitionId);
return myPredicateBuilderReference.createLinkSubquery(theParameterName, theTargetResourceType, theOrValues, theRequest, theRequestPartitionId);
}
Predicate createResourceLinkPathPredicate(String theTargetResourceType, String theParamReference, Join<ResourceTable, ResourceLink> theJoin) {
Predicate createResourceLinkPathPredicate(String theTargetResourceType, String theParamReference, Join<?, ResourceLink> theJoin) {
return myPredicateBuilderReference.createResourceLinkPathPredicate(theTargetResourceType, theParamReference, theJoin);
}

View File

@ -23,7 +23,6 @@ package ca.uhn.fhir.jpa.dao.predicate;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamCoords;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.util.CoordCalculator;
import ca.uhn.fhir.jpa.util.SearchBox;
import ca.uhn.fhir.model.api.IQueryParameterType;
@ -39,7 +38,6 @@ import org.springframework.stereotype.Component;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.From;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.Predicate;
import java.util.ArrayList;
import java.util.List;
@ -151,7 +149,7 @@ public class PredicateBuilderCoords extends BasePredicateBuilder implements IPre
List<? extends IQueryParameterType> theList,
SearchFilterParser.CompareOperation theOperation,
RequestPartitionId theRequestPartitionId) {
Join<ResourceTable, ResourceIndexedSearchParamCoords> join = createJoin(SearchBuilderJoinEnum.COORDS, theParamName);
From<?, ResourceIndexedSearchParamCoords> join = myQueryStack.createJoin(SearchBuilderJoinEnum.COORDS, theParamName);
if (theList.get(0).getMissing() != null) {
addPredicateParamMissingForNonReference(theResourceName, theParamName, theList.get(0).getMissing(), join, theRequestPartitionId);
@ -173,8 +171,7 @@ public class PredicateBuilderCoords extends BasePredicateBuilder implements IPre
}
Predicate retVal = myCriteriaBuilder.or(toArray(codePredicates));
myQueryRoot.addPredicate(retVal);
myQueryRoot.setHasIndexJoins();
myQueryStack.addPredicateWithImplicitTypeSelection(retVal);
return retVal;
}
}

View File

@ -23,7 +23,6 @@ package ca.uhn.fhir.jpa.dao.predicate;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
import ca.uhn.fhir.rest.param.DateParam;
@ -37,7 +36,6 @@ import org.springframework.stereotype.Component;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.From;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.Predicate;
import java.util.ArrayList;
import java.util.Date;
@ -50,7 +48,7 @@ import java.util.Map;
public class PredicateBuilderDate extends BasePredicateBuilder implements IPredicateBuilder {
private static final Logger ourLog = LoggerFactory.getLogger(PredicateBuilderDate.class);
private Map<String, Join<ResourceTable, ResourceIndexedSearchParamDate>> myJoinMap;
private Map<String, From<?, ResourceIndexedSearchParamDate>> myJoinMap;
PredicateBuilderDate(SearchBuilder theSearchBuilder) {
super(theSearchBuilder);
@ -69,9 +67,9 @@ public class PredicateBuilderDate extends BasePredicateBuilder implements IPredi
}
String key = theResourceName + " " + theParamName;
Join<ResourceTable, ResourceIndexedSearchParamDate> join = myJoinMap.get(key);
From<?, ResourceIndexedSearchParamDate> join = myJoinMap.get(key);
if (join == null) {
join = createJoin(SearchBuilderJoinEnum.DATE, theParamName);
join = myQueryStack.createJoin(SearchBuilderJoinEnum.DATE, theParamName);
myJoinMap.put(key, join);
newJoin = true;
}
@ -95,12 +93,11 @@ public class PredicateBuilderDate extends BasePredicateBuilder implements IPredi
Predicate orPredicates = myCriteriaBuilder.or(toArray(codePredicates));
myQueryRoot.setHasIndexJoins();
if (newJoin) {
Predicate identityAndValuePredicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, join, orPredicates, theRequestPartitionId);
myQueryRoot.addPredicate(identityAndValuePredicate);
myQueryStack.addPredicateWithImplicitTypeSelection(identityAndValuePredicate);
} else {
myQueryRoot.addPredicate(orPredicates);
myQueryStack.addPredicateWithImplicitTypeSelection(orPredicates);
}
return orPredicates;

View File

@ -23,7 +23,6 @@ package ca.uhn.fhir.jpa.dao.predicate;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamNumber;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.param.NumberParam;
import ca.uhn.fhir.rest.param.ParamPrefixEnum;
@ -33,7 +32,7 @@ import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.From;
import javax.persistence.criteria.Predicate;
import java.math.BigDecimal;
import java.util.ArrayList;
@ -57,7 +56,7 @@ class PredicateBuilderNumber extends BasePredicateBuilder implements IPredicateB
SearchFilterParser.CompareOperation operation,
RequestPartitionId theRequestPartitionId) {
Join<ResourceTable, ResourceIndexedSearchParamNumber> join = createJoin(SearchBuilderJoinEnum.NUMBER, theParamName);
From<?, ResourceIndexedSearchParamNumber> join = myQueryStack.createJoin(SearchBuilderJoinEnum.NUMBER, theParamName);
if (theList.get(0).getMissing() != null) {
addPredicateParamMissingForNonReference(theResourceName, theParamName, theList.get(0).getMissing(), join, theRequestPartitionId);
@ -109,8 +108,7 @@ class PredicateBuilderNumber extends BasePredicateBuilder implements IPredicateB
}
Predicate predicate = myCriteriaBuilder.or(toArray(codePredicates));
myQueryRoot.setHasIndexJoins();
myQueryRoot.addPredicate(predicate);
myQueryStack.addPredicateWithImplicitTypeSelection(predicate);
return predicate;
}
}

View File

@ -24,7 +24,6 @@ import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.model.base.composite.BaseQuantityDt;
import ca.uhn.fhir.rest.param.ParamPrefixEnum;
@ -35,7 +34,6 @@ import org.springframework.stereotype.Component;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.From;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.Predicate;
import java.math.BigDecimal;
import java.util.ArrayList;
@ -59,14 +57,14 @@ class PredicateBuilderQuantity extends BasePredicateBuilder implements IPredicat
SearchFilterParser.CompareOperation theOperation,
RequestPartitionId theRequestPartitionId) {
Join<ResourceTable, ResourceIndexedSearchParamQuantity> join = createJoin(SearchBuilderJoinEnum.QUANTITY, theParamName);
From<?, ResourceIndexedSearchParamQuantity> join = myQueryStack.createJoin(SearchBuilderJoinEnum.QUANTITY, theParamName);
if (theList.get(0).getMissing() != null) {
addPredicateParamMissingForNonReference(theResourceName, theParamName, theList.get(0).getMissing(), join, theRequestPartitionId);
return null;
}
List<Predicate> codePredicates = new ArrayList<Predicate>();
List<Predicate> codePredicates = new ArrayList<>();
addPartitionIdPredicate(theRequestPartitionId, join, codePredicates);
for (IQueryParameterType nextOr : theList) {
@ -75,8 +73,7 @@ class PredicateBuilderQuantity extends BasePredicateBuilder implements IPredicat
}
Predicate retVal = myCriteriaBuilder.or(toArray(codePredicates));
myQueryRoot.setHasIndexJoins();
myQueryRoot.addPredicate(retVal);
myQueryStack.addPredicateWithImplicitTypeSelection(retVal);
return retVal;
}

View File

@ -38,14 +38,12 @@ import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao;
import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
import ca.uhn.fhir.jpa.model.entity.ResourceHistoryProvenanceEntity;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken;
import ca.uhn.fhir.jpa.model.entity.ResourceLink;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage;
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
import ca.uhn.fhir.jpa.searchparam.ResourceMetaParams;
@ -61,6 +59,7 @@ import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.QualifiedParamList;
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
import ca.uhn.fhir.rest.param.CompositeParam;
import ca.uhn.fhir.rest.param.DateParam;
import ca.uhn.fhir.rest.param.HasParam;
@ -141,7 +140,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
RequestDetails theRequest,
RequestPartitionId theRequestPartitionId) {
//Is this just to ensure the chain has been split correctly???
// This just to ensure the chain has been split correctly
assert theParamName.contains(".") == false;
if ((operation != null) &&
@ -155,7 +154,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
return null;
}
Join<ResourceTable, ResourceLink> join = createJoin(SearchBuilderJoinEnum.REFERENCE, theParamName);
From<?, ResourceLink> join = myQueryStack.createJoin(SearchBuilderJoinEnum.REFERENCE, theParamName);
List<IIdType> targetIds = new ArrayList<>();
List<String> targetQualifiedUrls = new ArrayList<>();
@ -251,17 +250,12 @@ class PredicateBuilderReference extends BasePredicateBuilder {
codePredicates.add(myCriteriaBuilder.and(pathPredicate, pidPredicate));
}
myQueryRoot.setHasIndexJoins();
if (codePredicates.size() > 0) {
Predicate predicate = myCriteriaBuilder.or(toArray(codePredicates));
myQueryRoot.addPredicate(predicate);
myQueryStack.addPredicateWithImplicitTypeSelection(predicate);
return predicate;
} else {
// Add a predicate that will never match
Predicate pidPredicate = join.get("myTargetResourcePid").in(-1L);
myQueryRoot.clearPredicates();
myQueryRoot.addPredicate(pidPredicate);
return pidPredicate;
return myQueryStack.addNeverMatchingPredicate();
}
}
@ -269,7 +263,124 @@ class PredicateBuilderReference extends BasePredicateBuilder {
* 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, RequestPartitionId theRequestPartitionId) {
private Predicate addPredicateReferenceWithChain(String theResourceName, String theParamName, List<? extends IQueryParameterType> theList, From<?, ResourceLink> theJoin, List<Predicate> theCodePredicates, ReferenceParam theReferenceParam, RequestDetails theRequest, RequestPartitionId theRequestPartitionId) {
/*
* Which resource types can the given chained parameter actually link to? This might be a list
* where the chain is unqualified, as in: Observation?subject.identifier=(...)
* since subject can link to several possible target types.
*
* If the user has qualified the chain, as in: Observation?subject:Patient.identifier=(...)
* this is just a simple 1-entry list.
*/
final List<Class<? extends IBaseResource>> resourceTypes = determineCandidateResourceTypesForChain(theResourceName, theParamName, theReferenceParam);
/*
* Handle chain on _type
*/
if (Constants.PARAM_TYPE.equals(theReferenceParam.getChain())) {
return createChainPredicateOnType(theResourceName, theParamName, theJoin, theReferenceParam, resourceTypes);
}
boolean foundChainMatch = false;
List<Class<? extends IBaseResource>> candidateTargetTypes = new ArrayList<>();
for (Class<? extends IBaseResource> nextType : resourceTypes) {
String chain = theReferenceParam.getChain();
String remainingChain = null;
int chainDotIndex = chain.indexOf('.');
if (chainDotIndex != -1) {
remainingChain = chain.substring(chainDotIndex + 1);
chain = chain.substring(0, chainDotIndex);
}
RuntimeResourceDefinition typeDef = myContext.getResourceDefinition(nextType);
String subResourceName = typeDef.getName();
IDao dao = myDaoRegistry.getResourceDao(nextType);
if (dao == null) {
ourLog.debug("Don't have a DAO for type {}", nextType.getSimpleName());
continue;
}
int qualifierIndex = chain.indexOf(':');
String qualifier = null;
if (qualifierIndex != -1) {
qualifier = chain.substring(qualifierIndex);
chain = chain.substring(0, qualifierIndex);
}
boolean isMeta = ResourceMetaParams.RESOURCE_META_PARAMS.containsKey(chain);
RuntimeSearchParam param = null;
if (!isMeta) {
param = mySearchParamRegistry.getSearchParamByName(typeDef, chain);
if (param == null) {
ourLog.debug("Type {} doesn't have search param {}", nextType.getSimpleName(), param);
continue;
}
}
ArrayList<IQueryParameterType> orValues = Lists.newArrayList();
for (IQueryParameterType next : theList) {
String nextValue = next.getValueAsQueryToken(myContext);
IQueryParameterType chainValue = mapReferenceChainToRawParamType(remainingChain, param, theParamName, qualifier, nextType, chain, isMeta, nextValue);
if (chainValue == null) {
continue;
}
foundChainMatch = true;
orValues.add(chainValue);
}
// If this is false, we throw an exception below so no sense doing any further processing
if (foundChainMatch) {
Subquery<Long> subQ = createLinkSubquery(chain, subResourceName, orValues, theRequest, theRequestPartitionId);
Predicate pathPredicate = createResourceLinkPathPredicate(theResourceName, theParamName, theJoin);
Predicate pidPredicate = theJoin.get("myTargetResourcePid").in(subQ);
Predicate andPredicate = myCriteriaBuilder.and(pathPredicate, pidPredicate);
theCodePredicates.add(andPredicate);
candidateTargetTypes.add(nextType);
}
}
if (!foundChainMatch) {
throw new InvalidRequestException(myContext.getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "invalidParameterChain", theParamName + '.' + theReferenceParam.getChain()));
}
if (candidateTargetTypes.size() > 1) {
warnAboutPerformanceOnUnqualifiedResources(theParamName, theRequest, candidateTargetTypes);
}
Predicate predicate = myCriteriaBuilder.or(toArray(theCodePredicates));
myQueryStack.addPredicate(predicate);
return predicate;
}
private Predicate createChainPredicateOnType(String theResourceName, String theParamName, From<?, ResourceLink> theJoin, ReferenceParam theReferenceParam, List<Class<? extends IBaseResource>> theResourceTypes) {
String typeValue = theReferenceParam.getValue();
Class<? extends IBaseResource> wantedType;
try {
wantedType = myContext.getResourceDefinition(typeValue).getImplementingClass();
} catch (DataFormatException e) {
throw newInvalidResourceTypeException(typeValue);
}
if (!theResourceTypes.contains(wantedType)) {
throw newInvalidTargetTypeForChainException(theResourceName, theParamName, typeValue);
}
Predicate pathPredicate = createResourceLinkPathPredicate(theResourceName, theParamName, theJoin);
Predicate sourceTypeParameter = myCriteriaBuilder.equal(theJoin.get("mySourceResourceType"), myResourceName);
Predicate targetTypeParameter = myCriteriaBuilder.equal(theJoin.get("myTargetResourceType"), typeValue);
Predicate composite = myCriteriaBuilder.and(pathPredicate, sourceTypeParameter, targetTypeParameter);
myQueryStack.addPredicate(composite);
return composite;
}
@Nonnull
private List<Class<? extends IBaseResource>> determineCandidateResourceTypesForChain(String theResourceName, String theParamName, ReferenceParam theReferenceParam) {
final List<Class<? extends IBaseResource>> resourceTypes;
if (!theReferenceParam.hasResourceType()) {
@ -339,101 +450,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
}
}
// Handle chain on _type
if (Constants.PARAM_TYPE.equals(theReferenceParam.getChain())) {
String typeValue = theReferenceParam.getValue();
Class<? extends IBaseResource> wantedType;
try {
wantedType = myContext.getResourceDefinition(typeValue).getImplementingClass();
} catch (DataFormatException e) {
throw newInvalidResourceTypeException(typeValue);
}
if (!resourceTypes.contains(wantedType)) {
throw newInvalidTargetTypeForChainException(theResourceName, theParamName, typeValue);
}
Predicate pathPredicate = createResourceLinkPathPredicate(theResourceName, theParamName, theJoin);
Predicate sourceTypeParameter = myCriteriaBuilder.equal(theJoin.get("mySourceResourceType"), myResourceName);
Predicate targetTypeParameter = myCriteriaBuilder.equal(theJoin.get("myTargetResourceType"), typeValue);
Predicate composite = myCriteriaBuilder.and(pathPredicate, sourceTypeParameter, targetTypeParameter);
myQueryRoot.addPredicate(composite);
return composite;
}
boolean foundChainMatch = false;
List<Class<? extends IBaseResource>> candidateTargetTypes = new ArrayList<>();
for (Class<? extends IBaseResource> nextType : resourceTypes) {
String chain = theReferenceParam.getChain();
String remainingChain = null;
int chainDotIndex = chain.indexOf('.');
if (chainDotIndex != -1) {
remainingChain = chain.substring(chainDotIndex + 1);
chain = chain.substring(0, chainDotIndex);
}
RuntimeResourceDefinition typeDef = myContext.getResourceDefinition(nextType);
String subResourceName = typeDef.getName();
IDao dao = myDaoRegistry.getResourceDao(nextType);
if (dao == null) {
ourLog.debug("Don't have a DAO for type {}", nextType.getSimpleName());
continue;
}
int qualifierIndex = chain.indexOf(':');
String qualifier = null;
if (qualifierIndex != -1) {
qualifier = chain.substring(qualifierIndex);
chain = chain.substring(0, qualifierIndex);
}
boolean isMeta = ResourceMetaParams.RESOURCE_META_PARAMS.containsKey(chain);
RuntimeSearchParam param = null;
if (!isMeta) {
param = mySearchParamRegistry.getSearchParamByName(typeDef, chain);
if (param == null) {
ourLog.debug("Type {} doesn't have search param {}", nextType.getSimpleName(), param);
continue;
}
}
ArrayList<IQueryParameterType> orValues = Lists.newArrayList();
for (IQueryParameterType next : theList) {
String nextValue = next.getValueAsQueryToken(myContext);
IQueryParameterType chainValue = mapReferenceChainToRawParamType(remainingChain, param, theParamName, qualifier, nextType, chain, isMeta, nextValue);
if (chainValue == null) {
continue;
}
foundChainMatch = true;
orValues.add(chainValue);
}
Subquery<Long> subQ = createLinkSubquery(foundChainMatch, chain, subResourceName, orValues, theRequest, theRequestPartitionId);
Predicate pathPredicate = createResourceLinkPathPredicate(theResourceName, theParamName, theJoin);
Predicate pidPredicate = theJoin.get("myTargetResourcePid").in(subQ);
Predicate andPredicate = myCriteriaBuilder.and(pathPredicate, pidPredicate);
theCodePredicates.add(andPredicate);
candidateTargetTypes.add(nextType);
}
if (!foundChainMatch) {
throw new InvalidRequestException(myContext.getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "invalidParameterChain", theParamName + '.' + theReferenceParam.getChain()));
}
if (candidateTargetTypes.size() > 1) {
warnAboutPerformanceOnUnqualifiedResources(theParamName, theRequest, candidateTargetTypes);
}
Predicate predicate = myCriteriaBuilder.or(toArray(theCodePredicates));
myQueryRoot.addPredicate(predicate);
return predicate;
return resourceTypes;
}
private void warnAboutPerformanceOnUnqualifiedResources(String theParamName, RequestDetails theRequest, @Nullable List<Class<? extends IBaseResource>> theCandidateTargetTypes) {
@ -510,33 +527,30 @@ class PredicateBuilderReference extends BasePredicateBuilder {
return chainValue;
}
Subquery<Long> createLinkSubquery(boolean theFoundChainMatch, String theChain, String theSubResourceName, List<IQueryParameterType> theOrValues, RequestDetails theRequest, RequestPartitionId theRequestPartitionId) {
Subquery<Long> subQ = myQueryRoot.subquery(Long.class);
Subquery<Long> createLinkSubquery(String theChain, String theSubResourceName, List<IQueryParameterType> theOrValues, RequestDetails theRequest, RequestPartitionId theRequestPartitionId) {
/*
* We're doing a chain call, so push the current query root
* and predicate list down and put new ones at the top of the
* stack and run a subquery
*/
myQueryRoot.push(subQ);
subQ.select(myQueryRoot.get("myId").as(Long.class));
RuntimeSearchParam nextParamDef = mySearchParamRegistry.getActiveSearchParam(theSubResourceName, theChain);
if (nextParamDef != null && !theChain.startsWith("_")) {
myQueryStack.pushIndexTableSubQuery();
} else {
myQueryStack.pushResourceTableSubQuery(theSubResourceName);
}
List<List<IQueryParameterType>> andOrParams = new ArrayList<>();
andOrParams.add(theOrValues);
// Create the subquery predicates
myQueryRoot.addPredicate(myCriteriaBuilder.equal(myQueryRoot.get("myResourceType"), theSubResourceName));
myQueryRoot.addPredicate(myCriteriaBuilder.isNull(myQueryRoot.get("myDeleted")));
if (theFoundChainMatch) {
searchForIdsWithAndOr(theSubResourceName, theChain, andOrParams, theRequest, theRequestPartitionId);
subQ.where(myQueryRoot.getPredicateArray());
}
/*
* Pop the old query root and predicate list back
*/
myQueryRoot.pop();
return subQ;
return (Subquery<Long>) myQueryStack.pop();
}
void searchForIdsWithAndOr(String theResourceName, String theParamName, List<List<IQueryParameterType>> theAndOrParams, RequestDetails theRequest, RequestPartitionId theRequestPartitionId) {
@ -656,18 +670,18 @@ class PredicateBuilderReference extends BasePredicateBuilder {
// TODO: we clear the predicates below because the filter builds up
// its own collection of predicates. It'd probably be good at some
// point to do something more fancy...
ArrayList<Predicate> holdPredicates = new ArrayList<>(myQueryRoot.getPredicates());
ArrayList<Predicate> holdPredicates = new ArrayList<>(myQueryStack.getPredicates());
Predicate filterPredicate = processFilter(filter, theResourceName, theRequest, theRequestPartitionId);
myQueryRoot.clearPredicates();
myQueryRoot.addPredicates(holdPredicates);
myQueryRoot.addPredicate(filterPredicate);
myQueryStack.clearPredicates();
myQueryStack.addPredicates(holdPredicates);
myQueryStack.addPredicate(filterPredicate);
// Because filters can have an OR at the root, we never know for sure that we haven't done an optimized
// search that doesn't check the resource type. This could be improved in the future, but for now it's
// safest to just clear this flag. The test "testRetrieveDifferentTypeEq" will fail if we don't clear
// this here.
myQueryRoot.clearHasIndexJoins();
myQueryStack.clearHasImplicitTypeSelection();
}
}
@ -844,13 +858,13 @@ class PredicateBuilderReference extends BasePredicateBuilder {
Predicate predicate;
if ((operation == null) ||
(operation == SearchFilterParser.CompareOperation.eq)) {
predicate = myQueryRoot.get("myLanguage").as(String.class).in(values);
predicate = myQueryStack.get("myLanguage").as(String.class).in(values);
} else if (operation == SearchFilterParser.CompareOperation.ne) {
predicate = myQueryRoot.get("myLanguage").as(String.class).in(values).not();
predicate = myQueryStack.get("myLanguage").as(String.class).in(values).not();
} else {
throw new InvalidRequestException("Unsupported operator specified in language query, only \"eq\" and \"ne\" are supported");
}
myQueryRoot.addPredicate(predicate);
myQueryStack.addPredicate(predicate);
if (operation != null) {
return predicate;
}
@ -874,7 +888,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
throw new InvalidRequestException(msg);
}
Join<ResourceTable, ResourceHistoryProvenanceEntity> join = myQueryRoot.join("myProvenance", JoinType.LEFT);
From<?, ResourceHistoryProvenanceEntity> join = myQueryStack.createJoin(SearchBuilderJoinEnum.PROVENANCE, Constants.PARAM_SOURCE);
List<Predicate> codePredicates = new ArrayList<>();
@ -894,7 +908,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
}
Predicate retVal = myCriteriaBuilder.or(toArray(codePredicates));
myQueryRoot.addPredicate(retVal);
myQueryStack.addPredicate(retVal);
return retVal;
}
@ -962,13 +976,13 @@ class PredicateBuilderReference extends BasePredicateBuilder {
}
Subquery<Long> subQ = myPredicateBuilder.createLinkSubquery(paramName, targetResourceType, orValues, theRequest, theRequestPartitionId);
Join<ResourceTable, ResourceLink> join = myQueryRoot.join("myResourceLinksAsTarget", JoinType.LEFT);
Join<?, ResourceLink> join = (Join) myQueryStack.createJoin(SearchBuilderJoinEnum.HAS, "_has");
Predicate pathPredicate = myPredicateBuilder.createResourceLinkPathPredicate(targetResourceType, paramReference, join);
Predicate sourceTypePredicate = myCriteriaBuilder.equal(join.get("myTargetResourceType"), theResourceType);
Predicate sourcePidPredicate = join.get("mySourceResourcePid").in(subQ);
Predicate andPredicate = myCriteriaBuilder.and(pathPredicate, sourcePidPredicate, sourceTypePredicate);
myQueryRoot.addPredicate(andPredicate);
myQueryStack.addPredicate(andPredicate);
}
}
@ -987,15 +1001,15 @@ class PredicateBuilderReference extends BasePredicateBuilder {
RuntimeSearchParam left = theParamDef.getCompositeOf().get(0);
IQueryParameterType leftValue = cp.getLeftValue();
myQueryRoot.addPredicate(createCompositeParamPart(theResourceName, myQueryRoot.getRoot(), left, leftValue, theRequestPartitionId));
myQueryStack.addPredicate(createCompositeParamPart(theResourceName, myQueryStack.getRootForComposite(), left, leftValue, theRequestPartitionId));
RuntimeSearchParam right = theParamDef.getCompositeOf().get(1);
IQueryParameterType rightValue = cp.getRightValue();
myQueryRoot.addPredicate(createCompositeParamPart(theResourceName, myQueryRoot.getRoot(), right, rightValue, theRequestPartitionId));
myQueryStack.addPredicate(createCompositeParamPart(theResourceName, myQueryStack.getRootForComposite(), right, rightValue, theRequestPartitionId));
}
private Predicate createCompositeParamPart(String theResourceName, Root<ResourceTable> theRoot, RuntimeSearchParam theParam, IQueryParameterType leftValue, RequestPartitionId theRequestPartitionId) {
private Predicate createCompositeParamPart(String theResourceName, Root<?> theRoot, RuntimeSearchParam theParam, IQueryParameterType leftValue, RequestPartitionId theRequestPartitionId) {
Predicate retVal = null;
switch (theParam.getParamType()) {
case STRING: {

View File

@ -23,9 +23,8 @@ package ca.uhn.fhir.jpa.dao.predicate;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import org.hl7.fhir.r4.model.IdType;
import org.slf4j.Logger;
@ -36,7 +35,6 @@ import org.springframework.stereotype.Component;
import javax.annotation.Nullable;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
@ -60,10 +58,10 @@ class PredicateBuilderResourceId extends BasePredicateBuilder {
@Nullable
Predicate addPredicateResourceId(List<List<IQueryParameterType>> theValues, String theResourceName, SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) {
Predicate nextPredicate = createPredicate(myQueryRoot.getRoot(), theResourceName, theValues, theOperation, theRequestPartitionId);
Predicate nextPredicate = createPredicate(theResourceName, theValues, theOperation, theRequestPartitionId);
if (nextPredicate != null) {
myQueryRoot.addPredicate(nextPredicate);
myQueryStack.addPredicate(nextPredicate);
return nextPredicate;
}
@ -71,7 +69,7 @@ class PredicateBuilderResourceId extends BasePredicateBuilder {
}
@Nullable
private Predicate createPredicate(Root<ResourceTable> theRoot, String theResourceName, List<List<IQueryParameterType>> theValues, SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) {
private Predicate createPredicate(String theResourceName, List<List<IQueryParameterType>> theValues, SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) {
Predicate nextPredicate = null;
Set<ResourcePersistentId> allOrPids = null;
@ -110,7 +108,7 @@ class PredicateBuilderResourceId extends BasePredicateBuilder {
if (allOrPids != null && allOrPids.isEmpty()) {
// This will never match
nextPredicate = myCriteriaBuilder.equal(theRoot.get("myId").as(Long.class), -1);
nextPredicate = myCriteriaBuilder.equal(myQueryStack.getResourcePidColumn(), -1);
} else if (allOrPids != null) {
@ -120,11 +118,11 @@ class PredicateBuilderResourceId extends BasePredicateBuilder {
switch (operation) {
default:
case eq:
codePredicates.add(theRoot.get("myId").as(Long.class).in(ResourcePersistentId.toLongList(allOrPids)));
codePredicates.add(myQueryStack.getResourcePidColumn().in(ResourcePersistentId.toLongList(allOrPids)));
nextPredicate = myCriteriaBuilder.and(toArray(codePredicates));
break;
case ne:
codePredicates.add(theRoot.get("myId").as(Long.class).in(ResourcePersistentId.toLongList(allOrPids)).not());
codePredicates.add(myQueryStack.getResourcePidColumn().in(ResourcePersistentId.toLongList(allOrPids)).not());
nextPredicate = myCriteriaBuilder.and(toArray(codePredicates));
break;
}

View File

@ -24,7 +24,6 @@ import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.model.util.StringNormalizer;
import ca.uhn.fhir.model.api.IPrimitiveDatatype;
import ca.uhn.fhir.model.api.IQueryParameterType;
@ -38,7 +37,6 @@ import org.springframework.stereotype.Component;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.From;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.Predicate;
import java.util.ArrayList;
import java.util.List;
@ -46,6 +44,7 @@ import java.util.List;
@Component
@Scope("prototype")
class PredicateBuilderString extends BasePredicateBuilder implements IPredicateBuilder {
@Autowired
DaoConfig myDaoConfig;
@ -60,7 +59,7 @@ class PredicateBuilderString extends BasePredicateBuilder implements IPredicateB
SearchFilterParser.CompareOperation theOperation,
RequestPartitionId theRequestPartitionId) {
Join<ResourceTable, ResourceIndexedSearchParamString> join = createJoin(SearchBuilderJoinEnum.STRING, theParamName);
From<?, ResourceIndexedSearchParamString> join = myQueryStack.createJoin(SearchBuilderJoinEnum.STRING, theParamName);
if (theList.get(0).getMissing() != null) {
addPredicateParamMissingForNonReference(theResourceName, theParamName, theList.get(0).getMissing(), join, theRequestPartitionId);
@ -77,8 +76,7 @@ class PredicateBuilderString extends BasePredicateBuilder implements IPredicateB
Predicate retVal = myCriteriaBuilder.or(toArray(codePredicates));
myQueryRoot.setHasIndexJoins();
myQueryRoot.addPredicate(retVal);
myQueryStack.addPredicateWithImplicitTypeSelection(retVal);
return retVal;
}

View File

@ -22,7 +22,6 @@ package ca.uhn.fhir.jpa.dao.predicate;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.model.entity.ResourceTag;
import ca.uhn.fhir.jpa.model.entity.TagDefinition;
import ca.uhn.fhir.jpa.model.entity.TagTypeEnum;
@ -41,8 +40,6 @@ import org.springframework.stereotype.Component;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.From;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
@ -137,14 +134,14 @@ class PredicateBuilderTag extends BasePredicateBuilder {
if (paramInverted) {
ourLog.debug("Searching for _tag:not");
Subquery<Long> subQ = myQueryRoot.subquery(Long.class);
Subquery<Long> subQ = myQueryStack.subqueryForTagNegation();
Root<ResourceTag> subQfrom = subQ.from(ResourceTag.class);
subQ.select(subQfrom.get("myResourceId").as(Long.class));
myQueryRoot.addPredicate(
myQueryStack.addPredicate(
myCriteriaBuilder.not(
myCriteriaBuilder.in(
myQueryRoot.get("myId")
myQueryStack.get("myId")
).value(subQ)
)
);
@ -162,7 +159,7 @@ class PredicateBuilderTag extends BasePredicateBuilder {
}
Join<ResourceTable, ResourceTag> tagJoin = myQueryRoot.join("myTags", JoinType.LEFT);
From<?, ResourceTag> tagJoin = myQueryStack.createJoin(SearchBuilderJoinEnum.RESOURCE_TAGS, null);
From<ResourceTag, TagDefinition> defJoin = tagJoin.join("myTag");
Predicate tagListPredicate = createPredicateTagList(defJoin, myCriteriaBuilder, tagType, tokens);
@ -172,7 +169,7 @@ class PredicateBuilderTag extends BasePredicateBuilder {
addPartitionIdPredicate(theRequestPartitionId, tagJoin, predicates);
}
myQueryRoot.addPredicates(predicates);
myQueryStack.addPredicates(predicates);
}

View File

@ -30,7 +30,6 @@ import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.searchparam.extractor.BaseSearchParamExtractor;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
import ca.uhn.fhir.jpa.term.api.ITermReadSvc;
@ -53,7 +52,6 @@ import org.springframework.stereotype.Component;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.From;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import java.util.ArrayList;
@ -91,7 +89,7 @@ class PredicateBuilderToken extends BasePredicateBuilder implements IPredicateBu
RequestPartitionId theRequestPartitionId) {
if (theList.get(0).getMissing() != null) {
Join<ResourceTable, ResourceIndexedSearchParamToken> join = createJoin(SearchBuilderJoinEnum.TOKEN, theParamName);
From<?, ResourceIndexedSearchParamToken> join = myQueryStack.createJoin(SearchBuilderJoinEnum.TOKEN, theParamName);
addPredicateParamMissingForNonReference(theResourceName, theParamName, theList.get(0).getMissing(), join, theRequestPartitionId);
return null;
}
@ -130,7 +128,7 @@ class PredicateBuilderToken extends BasePredicateBuilder implements IPredicateBu
return null;
}
Join<ResourceTable, ResourceIndexedSearchParamToken> join = createJoin(SearchBuilderJoinEnum.TOKEN, theParamName);
From<?, ResourceIndexedSearchParamToken> join = myQueryStack.createJoin(SearchBuilderJoinEnum.TOKEN, theParamName);
addPartitionIdPredicate(theRequestPartitionId, join, codePredicates);
Collection<Predicate> singleCode = createPredicateToken(tokens, theResourceName, theParamName, myCriteriaBuilder, join, theOperation, theRequestPartitionId);
@ -139,8 +137,7 @@ class PredicateBuilderToken extends BasePredicateBuilder implements IPredicateBu
Predicate spPredicate = myCriteriaBuilder.or(toArray(codePredicates));
myQueryRoot.setHasIndexJoins();
myQueryRoot.addPredicate(spPredicate);
myQueryStack.addPredicateWithImplicitTypeSelection(spPredicate);
return spPredicate;
}
@ -375,7 +372,13 @@ class PredicateBuilderToken extends BasePredicateBuilder implements IPredicateBu
break;
}
Predicate predicate = hashField.in(values);
Predicate predicate;
if (values.size() == 1) {
predicate = myCriteriaBuilder.equal(hashField, values.get(0));
} else {
predicate = hashField.in(values);
}
if (theModifier == TokenParamModifier.NOT) {
Predicate identityPredicate = theBuilder.equal(theFrom.get("myHashIdentity").as(Long.class), BaseResourceIndexedSearchParam.calculateHashIdentity(getPartitionSettings(), theRequestPartitionId, theResourceName, theParamName));
Predicate disjunctionPredicate = theBuilder.not(predicate);

View File

@ -25,7 +25,6 @@ import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamUriDao;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamUri;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.param.UriParam;
import ca.uhn.fhir.rest.param.UriParamQualifierEnum;
@ -35,7 +34,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.From;
import javax.persistence.criteria.Predicate;
import java.util.ArrayList;
import java.util.Collection;
@ -59,7 +58,7 @@ class PredicateBuilderUri extends BasePredicateBuilder implements IPredicateBuil
SearchFilterParser.CompareOperation operation,
RequestPartitionId theRequestPartitionId) {
Join<ResourceTable, ResourceIndexedSearchParamUri> join = createJoin(SearchBuilderJoinEnum.URI, theParamName);
From<?, ResourceIndexedSearchParamUri> join = myQueryStack.createJoin(SearchBuilderJoinEnum.URI, theParamName);
if (theList.get(0).getMissing() != null) {
addPredicateParamMissingForNonReference(theResourceName, theParamName, theList.get(0).getMissing(), join, theRequestPartitionId);
@ -169,8 +168,7 @@ class PredicateBuilderUri extends BasePredicateBuilder implements IPredicateBuil
*/
if (codePredicates.isEmpty()) {
Predicate predicate = myCriteriaBuilder.isNull(join.get("myMissing").as(String.class));
myQueryRoot.setHasIndexJoins();
myQueryRoot.addPredicate(predicate);
myQueryStack.addPredicateWithImplicitTypeSelection(predicate);
return null;
}
@ -181,8 +179,7 @@ class PredicateBuilderUri extends BasePredicateBuilder implements IPredicateBuil
join,
orPredicate,
theRequestPartitionId);
myQueryRoot.setHasIndexJoins();
myQueryRoot.addPredicate(outerPredicate);
myQueryStack.addPredicateWithImplicitTypeSelection(outerPredicate);
return outerPredicate;
}

View File

@ -1,106 +0,0 @@
package ca.uhn.fhir.jpa.dao.predicate;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* 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%
*/
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import javax.persistence.criteria.*;
import java.util.List;
import java.util.Stack;
public class QueryRoot {
private final Stack<QueryRootEntry> myQueryRootStack = new Stack<>();
private boolean myHasIndexJoins;
public void push(AbstractQuery<Long> theResourceTableQuery) {
myQueryRootStack.push(new QueryRootEntry(theResourceTableQuery));
}
private QueryRootEntry top() {
return myQueryRootStack.peek();
}
void pop() {
myQueryRootStack.pop();
}
public Root<ResourceTable> getRoot() {
return top().getRoot();
}
public <Y> Path<Y> get(String theAttributeName) {
return top().get(theAttributeName);
}
public <Y> Join<ResourceTable, Y> join(String theAttributeName, JoinType theJoinType) {
return top().join(theAttributeName, theJoinType);
}
public Join<?,?> getIndexJoin(SearchBuilderJoinKey theKey) {
return top().getIndexJoin(theKey);
}
public void addPredicate(Predicate thePredicate) {
top().addPredicate(thePredicate);
}
public void addPredicates(List<Predicate> thePredicates) {
top().addPredicates(thePredicates);
}
public Predicate[] getPredicateArray() {
return top().getPredicateArray();
}
void putIndex(SearchBuilderJoinKey theKey, Join<ResourceTable, ResourceIndexedSearchParamDate> theJoin) {
myHasIndexJoins = true;
top().putIndex(theKey, theJoin);
}
void clearPredicates() {
top().clearPredicates();
}
List<Predicate> getPredicates() {
return top().getPredicates();
}
public void where(Predicate theAnd) {
top().where(theAnd);
}
<T> Subquery<T> subquery(Class<T> theClass) {
return top().subquery(theClass);
}
public boolean hasIndexJoins() {
return myHasIndexJoins;
}
public void setHasIndexJoins() {
myHasIndexJoins = true;
}
public void clearHasIndexJoins() {
myHasIndexJoins = false;
}
}

View File

@ -1,89 +0,0 @@
package ca.uhn.fhir.jpa.dao.predicate;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* 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%
*/
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import javax.persistence.criteria.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class QueryRootEntry {
private final AbstractQuery<Long> myResourceTableQuery;
private final Root<ResourceTable> myResourceTableRoot;
private final ArrayList<Predicate> myPredicates = new ArrayList<>();
private final IndexJoins myIndexJoins = new IndexJoins();
public QueryRootEntry(AbstractQuery<Long> theResourceTableQuery) {
myResourceTableQuery = theResourceTableQuery;
myResourceTableRoot = theResourceTableQuery.from(ResourceTable.class);
}
public Root<ResourceTable> getRoot() {
return myResourceTableRoot;
}
public <Y> Path<Y> get(String theAttributeName) {
return myResourceTableRoot.get(theAttributeName);
}
public <Y> Join<ResourceTable, Y> join(String theAttributeName, JoinType theJoinType) {
return myResourceTableRoot.join(theAttributeName, theJoinType);
}
public Join<?,?> getIndexJoin(SearchBuilderJoinKey theKey) {
return myIndexJoins.get(theKey);
}
public void addPredicate(Predicate thePredicate) {
myPredicates.add(thePredicate);
}
public void addPredicates(List<Predicate> thePredicates) {
myPredicates.addAll(thePredicates);
}
public Predicate[] getPredicateArray() {
return myPredicates.toArray(new Predicate[0]);
}
void putIndex(SearchBuilderJoinKey theKey, Join<ResourceTable, ResourceIndexedSearchParamDate> theJoin) {
myIndexJoins.put(theKey, theJoin);
}
void clearPredicates() {
myPredicates.clear();
}
List<Predicate> getPredicates() {
return Collections.unmodifiableList(myPredicates);
}
public void where(Predicate theAnd) {
myResourceTableQuery.where(theAnd);
}
<T> Subquery<T> subquery(Class<T> theClass) {
return myResourceTableQuery.subquery(theClass);
}
}

View File

@ -28,6 +28,8 @@ public enum SearchBuilderJoinEnum {
STRING,
TOKEN,
URI,
COORDS
COORDS,
HAS,
FORCED_ID, PRESENCE, COMPOSITE_UNIQUE, RESOURCE_TAGS, PROVENANCE
}

View File

@ -0,0 +1,124 @@
package ca.uhn.fhir.jpa.dao.predicate.querystack;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* 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%
*/
import ca.uhn.fhir.jpa.dao.predicate.IndexJoins;
import ca.uhn.fhir.jpa.dao.predicate.SearchBuilderJoinEnum;
import ca.uhn.fhir.jpa.dao.predicate.SearchBuilderJoinKey;
import javax.persistence.criteria.AbstractQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.From;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.Order;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Subquery;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Optional;
abstract class QueryRootEntry {
private final ArrayList<Predicate> myPredicates = new ArrayList<>();
private final IndexJoins myIndexJoins = new IndexJoins();
private final CriteriaBuilder myCriteriaBuilder;
private boolean myHasImplicitTypeSelection;
QueryRootEntry(CriteriaBuilder theCriteriaBuilder) {
myCriteriaBuilder = theCriteriaBuilder;
}
boolean isHasImplicitTypeSelection() {
return myHasImplicitTypeSelection;
}
void setHasImplicitTypeSelection(boolean theHasImplicitTypeSelection) {
myHasImplicitTypeSelection = theHasImplicitTypeSelection;
}
Optional<Join<?, ?>> getIndexJoin(SearchBuilderJoinKey theKey) {
return Optional.ofNullable(myIndexJoins.get(theKey));
}
void addPredicate(Predicate thePredicate) {
myPredicates.add(thePredicate);
}
void addPredicates(List<Predicate> thePredicates) {
myPredicates.addAll(thePredicates);
}
Predicate addNeverMatchingPredicate() {
Predicate predicate = myCriteriaBuilder.equal(getResourcePidColumn(), -1L);
clearPredicates();
addPredicate(predicate);
return predicate;
}
Predicate[] getPredicateArray() {
return myPredicates.toArray(new Predicate[0]);
}
void putIndex(SearchBuilderJoinKey theKey, Join<?, ?> theJoin) {
myIndexJoins.put(theKey, theJoin);
}
void clearPredicates() {
myPredicates.clear();
}
List<Predicate> getPredicates() {
return Collections.unmodifiableList(myPredicates);
}
<Y> Path<Y> get(String theAttributeName) {
return getRoot().get(theAttributeName);
}
AbstractQuery<Long> pop() {
Predicate[] predicateArray = getPredicateArray();
if (predicateArray.length == 1) {
getQueryRoot().where(predicateArray[0]);
} else {
getQueryRoot().where(myCriteriaBuilder.and(predicateArray));
}
return getQueryRoot();
}
abstract void orderBy(List<Order> theOrders);
abstract Expression<Date> getLastUpdatedColumn();
abstract <T> From<?, T> createJoin(SearchBuilderJoinEnum theType, String theSearchParameterName);
abstract AbstractQuery<Long> getQueryRoot();
abstract Root<?> getRoot();
abstract Expression<Long> getResourcePidColumn();
abstract Subquery<Long> subqueryForTagNegation();
}

View File

@ -0,0 +1,145 @@
package ca.uhn.fhir.jpa.dao.predicate.querystack;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* 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%
*/
import ca.uhn.fhir.jpa.dao.predicate.SearchBuilderJoinEnum;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndex;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamCoords;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamNumber;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamUri;
import ca.uhn.fhir.jpa.model.entity.ResourceLink;
import org.apache.commons.lang3.Validate;
import javax.persistence.criteria.AbstractQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.From;
import javax.persistence.criteria.Order;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Subquery;
import java.util.Date;
import java.util.List;
public class QueryRootEntryIndexTable extends QueryRootEntry {
private final Subquery<Long> myQuery;
private Root<? extends BaseResourceIndex> myRoot;
private SearchBuilderJoinEnum myParamType;
private Expression<Long> myResourcePidColumn;
public QueryRootEntryIndexTable(CriteriaBuilder theCriteriaBuilder, QueryRootEntry theParent) {
super(theCriteriaBuilder);
AbstractQuery<Long> queryRoot = theParent.getQueryRoot();
myQuery = queryRoot.subquery(Long.class);
}
@Override
void orderBy(List<Order> theOrders) {
throw new IllegalStateException();
}
@Override
Expression<Date> getLastUpdatedColumn() {
return getRoot().get("myUpdated").as(Date.class);
}
@Override
<T> From<?, T> createJoin(SearchBuilderJoinEnum theType, String theSearchParameterName) {
if (myParamType == null) {
switch (theType) {
case REFERENCE:
myRoot = myQuery.from(ResourceLink.class);
myResourcePidColumn = myRoot.get("mySourceResourcePid").as(Long.class);
myParamType = SearchBuilderJoinEnum.REFERENCE;
break;
case NUMBER:
myRoot = myQuery.from(ResourceIndexedSearchParamNumber.class);
myResourcePidColumn = myRoot.get("myResourcePid").as(Long.class);
myParamType = SearchBuilderJoinEnum.NUMBER;
break;
case DATE:
myRoot = myQuery.from(ResourceIndexedSearchParamDate.class);
myResourcePidColumn = myRoot.get("myResourcePid").as(Long.class);
myParamType = SearchBuilderJoinEnum.DATE;
break;
case STRING:
myRoot = myQuery.from(ResourceIndexedSearchParamString.class);
myResourcePidColumn = myRoot.get("myResourcePid").as(Long.class);
myParamType = SearchBuilderJoinEnum.STRING;
break;
case TOKEN:
myRoot = myQuery.from(ResourceIndexedSearchParamToken.class);
myResourcePidColumn = myRoot.get("myResourcePid").as(Long.class);
myParamType = SearchBuilderJoinEnum.TOKEN;
break;
case QUANTITY:
myRoot = myQuery.from(ResourceIndexedSearchParamQuantity.class);
myResourcePidColumn = myRoot.get("myResourcePid").as(Long.class);
myParamType = SearchBuilderJoinEnum.QUANTITY;
break;
case URI:
myRoot = myQuery.from(ResourceIndexedSearchParamUri.class);
myResourcePidColumn = myRoot.get("myResourcePid").as(Long.class);
myParamType = SearchBuilderJoinEnum.URI;
break;
case COORDS:
myRoot = myQuery.from(ResourceIndexedSearchParamCoords.class);
myResourcePidColumn = myRoot.get("myResourcePid").as(Long.class);
myParamType = SearchBuilderJoinEnum.COORDS;
break;
default:
throw new IllegalStateException();
}
myQuery.select(myResourcePidColumn);
}
Validate.isTrue(theType == myParamType, "Wanted %s but got %s for %s", myParamType, theType, theSearchParameterName);
return (From<?, T>) myRoot;
}
@Override
AbstractQuery<Long> getQueryRoot() {
Validate.isTrue(myQuery != null);
return myQuery;
}
@Override
Root<?> getRoot() {
Validate.isTrue(myRoot != null);
return myRoot;
}
@Override
public Expression<Long> getResourcePidColumn() {
Validate.isTrue(myResourcePidColumn != null);
return myResourcePidColumn;
}
@Override
public Subquery<Long> subqueryForTagNegation() {
throw new IllegalStateException();
}
}

View File

@ -0,0 +1,205 @@
package ca.uhn.fhir.jpa.dao.predicate.querystack;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* 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%
*/
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.dao.predicate.SearchBuilderJoinEnum;
import ca.uhn.fhir.jpa.dao.predicate.SearchBuilderJoinKey;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import javax.persistence.criteria.AbstractQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.From;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.Order;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Subquery;
import java.util.Date;
import java.util.List;
class QueryRootEntryResourceTable extends QueryRootEntry {
private final CriteriaBuilder myCriteriaBuilder;
private final AbstractQuery<Long> myQuery;
private final SearchParameterMap mySearchParameterMap;
private final RequestPartitionId myRequestPartitionId;
private final String myResourceType;
/**
* This method will ddd a predicate to make sure we only include non-deleted resources, and only include
* resources of the right type.
*
* If we have any joins to index tables, we get this behaviour already guaranteed so we don't
* need an explicit predicate for it.
*/
@Override
AbstractQuery<Long> pop() {
if (!isHasImplicitTypeSelection()) {
if (mySearchParameterMap.getEverythingMode() == null) {
addPredicate(myCriteriaBuilder.equal(getRoot().get("myResourceType"), myResourceType));
}
addPredicate(myCriteriaBuilder.isNull(getRoot().get("myDeleted")));
if (!myRequestPartitionId.isAllPartitions()) {
if (myRequestPartitionId.getPartitionId() != null) {
addPredicate(myCriteriaBuilder.equal(getRoot().get("myPartitionIdValue").as(Integer.class), myRequestPartitionId.getPartitionId()));
} else {
addPredicate(myCriteriaBuilder.isNull(getRoot().get("myPartitionIdValue").as(Integer.class)));
}
}
}
return super.pop();
}
private final Root<ResourceTable> myResourceTableRoot;
/**
* Root query constructor
*/
QueryRootEntryResourceTable(CriteriaBuilder theCriteriaBuilder, boolean theCountQuery, SearchParameterMap theSearchParameterMap, String theResourceType, RequestPartitionId theRequestPartitionId) {
super(theCriteriaBuilder);
myCriteriaBuilder = theCriteriaBuilder;
mySearchParameterMap = theSearchParameterMap;
myRequestPartitionId = theRequestPartitionId;
myResourceType = theResourceType;
CriteriaQuery<Long> query = myCriteriaBuilder.createQuery(Long.class);
myResourceTableRoot = query.from(ResourceTable.class);
if (theCountQuery) {
query.multiselect(myCriteriaBuilder.countDistinct(myResourceTableRoot));
} else {
query.multiselect(get("myId").as(Long.class));
}
myQuery = query;
}
/**
* Subquery constructor
*/
QueryRootEntryResourceTable(CriteriaBuilder theCriteriaBuilder, QueryRootEntry theParent, SearchParameterMap theSearchParameterMap, String theResourceType, RequestPartitionId theRequestPartitionId) {
super(theCriteriaBuilder);
myCriteriaBuilder = theCriteriaBuilder;
mySearchParameterMap = theSearchParameterMap;
myRequestPartitionId = theRequestPartitionId;
myResourceType = theResourceType;
AbstractQuery<Long> queryRoot = theParent.getQueryRoot();
Subquery<Long> query = queryRoot.subquery(Long.class);
myQuery = query;
myResourceTableRoot = myQuery.from(ResourceTable.class);
query.select(myResourceTableRoot.get("myId").as(Long.class));
}
@Override
void orderBy(List<Order> theOrders) {
assert myQuery instanceof CriteriaQuery;
((CriteriaQuery<?>)myQuery).orderBy(theOrders);
}
@Override
Expression<Date> getLastUpdatedColumn() {
return myResourceTableRoot.get("myUpdated").as(Date.class);
}
@SuppressWarnings("unchecked")
@Override
<T> From<?, T> createJoin(SearchBuilderJoinEnum theType, String theSearchParameterName) {
Join<?,?> join = null;
switch (theType) {
case DATE:
join = myResourceTableRoot.join("myParamsDate", JoinType.LEFT);
break;
case NUMBER:
join = myResourceTableRoot.join("myParamsNumber", JoinType.LEFT);
break;
case QUANTITY:
join = myResourceTableRoot.join("myParamsQuantity", JoinType.LEFT);
break;
case REFERENCE:
join = myResourceTableRoot.join("myResourceLinks", JoinType.LEFT);
break;
case STRING:
join = myResourceTableRoot.join("myParamsString", JoinType.LEFT);
break;
case URI:
join = myResourceTableRoot.join("myParamsUri", JoinType.LEFT);
break;
case TOKEN:
join = myResourceTableRoot.join("myParamsToken", JoinType.LEFT);
break;
case COORDS:
join = myResourceTableRoot.join("myParamsCoords", JoinType.LEFT);
break;
case HAS:
join = myResourceTableRoot.join("myResourceLinksAsTarget", JoinType.LEFT);
break;
case PROVENANCE:
join = myResourceTableRoot.join("myProvenance", JoinType.LEFT);
break;
case FORCED_ID:
join = myResourceTableRoot.join("myForcedId", JoinType.LEFT);
break;
case PRESENCE:
join = myResourceTableRoot.join("mySearchParamPresents", JoinType.LEFT);
break;
case COMPOSITE_UNIQUE:
join = myResourceTableRoot.join("myParamsCompositeStringUnique", JoinType.LEFT);
break;
case RESOURCE_TAGS:
join = myResourceTableRoot.join("myTags", JoinType.LEFT);
break;
}
SearchBuilderJoinKey key = new SearchBuilderJoinKey(theSearchParameterName, theType);
putIndex(key, join);
return (From<?, T>) join;
}
@Override
AbstractQuery<Long> getQueryRoot() {
return myQuery;
}
@Override
Root<ResourceTable> getRoot() {
return myResourceTableRoot;
}
@Override
public Expression<Long> getResourcePidColumn() {
return myResourceTableRoot.get("myId").as(Long.class);
}
@Override
public Subquery<Long> subqueryForTagNegation() {
return myQuery.subquery(Long.class);
}
}

View File

@ -0,0 +1,284 @@
package ca.uhn.fhir.jpa.dao.predicate.querystack;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* 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%
*/
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.dao.predicate.SearchBuilderJoinEnum;
import ca.uhn.fhir.jpa.dao.predicate.SearchBuilderJoinKey;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import org.apache.commons.lang3.Validate;
import javax.persistence.criteria.AbstractQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.From;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.Order;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Subquery;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.Stack;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
/**
* This class represents a SQL SELECT statement that is selecting for resource PIDs, ie.
* the <code>RES_ID</code> column on the <code>HFJ_RESOURCE</code> ({@link ca.uhn.fhir.jpa.model.entity.ResourceTable})
* table.
* <p>
* We add predicates (WHERE A=B) to it, and can join other tables to it as well. At the root of the query
* we are typically doing a <code>select RES_ID from HFJ_RESOURCE where (....)</code> and this class
* is used to build the <i>where</i> clause. In the case of subqueries though, we may be performing a
* select on a different table since many tables have a column with a FK dependency on RES_ID.
* </p>
*/
public class QueryStack {
private final Stack<QueryRootEntry> myQueryRootStack = new Stack<>();
private final CriteriaBuilder myCriteriaBuilder;
private final SearchParameterMap mySearchParameterMap;
private final RequestPartitionId myRequestPartitionId;
private final String myResourceType;
/**
* Constructor
*/
public QueryStack(CriteriaBuilder theCriteriaBuilder, String theResourceType, SearchParameterMap theSearchParameterMap, RequestPartitionId theRequestPartitionId) {
assert theCriteriaBuilder != null;
assert isNotBlank(theResourceType);
assert theSearchParameterMap != null;
assert theRequestPartitionId != null;
myCriteriaBuilder = theCriteriaBuilder;
mySearchParameterMap = theSearchParameterMap;
myRequestPartitionId = theRequestPartitionId;
myResourceType = theResourceType;
}
/**
* Add a new <code>select RES_ID from HFJ_RESOURCE</code> to the stack. All predicates added to the {@literal QueryRootStack}
* will be added to this select clause until {@link #pop()} is called.
* <p>
* This method must only be called when the stack is empty.
* </p>
*/
public void pushResourceTableQuery() {
assert myQueryRootStack.isEmpty();
myQueryRootStack.push(new QueryRootEntryResourceTable(myCriteriaBuilder, false, mySearchParameterMap, myResourceType, myRequestPartitionId));
}
/**
* Add a new <code>select count(RES_ID) from HFJ_RESOURCE</code> to the stack. All predicates added to the {@literal QueryRootStack}
* will be added to this select clause until {@link #pop()} is called.
* <p>
* This method must only be called when the stack is empty.
* </p>
*/
public void pushResourceTableCountQuery() {
assert myQueryRootStack.isEmpty();
myQueryRootStack.push(new QueryRootEntryResourceTable(myCriteriaBuilder, true, mySearchParameterMap, myResourceType, myRequestPartitionId));
}
/**
* Add a new <code>select RES_ID from HFJ_RESOURCE</code> to the stack. All predicates added to the {@literal QueryRootStack}
* will be added to this select clause until {@link #pop()} is called.
* <p>
* This method must only be called when the stack is NOT empty.
* </p>
*/
public void pushResourceTableSubQuery(String theResourceType) {
assert !myQueryRootStack.isEmpty();
myQueryRootStack.push(new QueryRootEntryResourceTable(myCriteriaBuilder, top(), mySearchParameterMap, theResourceType, myRequestPartitionId));
}
/**
* Add a new <code>select RES_ID from (....)</code> to the stack, where the specific table being selected on will be
* determined based on the first call to {@link #createJoin(SearchBuilderJoinEnum, String)}. All predicates added
* to the {@literal QueryRootStack} will be added to this select clause until {@link #pop()} is called.
* <p>
* This method must only be called when the stack is NOT empty.
* </p>
*/
public void pushIndexTableSubQuery() {
assert !myQueryRootStack.isEmpty();
myQueryRootStack.push(new QueryRootEntryIndexTable(myCriteriaBuilder, top()));
}
/**
* This method must be called once all predicates have been added
*/
public AbstractQuery<Long> pop() {
QueryRootEntry element = myQueryRootStack.pop();
return element.pop();
}
/**
* Creates a new SQL join from the current select statement to another table, using the resource PID as the
* joining key
*/
public <T> From<?, T> createJoin(SearchBuilderJoinEnum theType, String theSearchParameterName) {
return top().createJoin(theType, theSearchParameterName);
}
/**
* Returns a join that was previously created by a call to {@link #createJoin(SearchBuilderJoinEnum, String)},
* if one exists for the given key.
*/
public Optional<Join<?, ?>> getExistingJoin(SearchBuilderJoinKey theKey) {
return top().getIndexJoin(theKey);
}
/**
* Gets an attribute (aka a column) from the current select statement.
*
* @param theAttributeName Must be the name of a java field for the entity/table being selected on
*/
public <Y> Path<Y> get(String theAttributeName) {
return top().get(theAttributeName);
}
/**
* Adds a predicate to the current select statement
*/
public void addPredicate(Predicate thePredicate) {
top().addPredicate(thePredicate);
}
/**
* Adds a predicate and marks it as having implicit type selection in it. In other words, call this method if a
* this predicate will ensure:
* <ul>
* <li>Only Resource PIDs for the correct resource type will be selected</li>
* <li>Only Resource PIDs for non-deleted resources will be selected</li>
* </ul>
* Setting this flag is a performance optimization, since it avoids the need for us to explicitly
* add predicates for the two conditions above.
*/
public void addPredicateWithImplicitTypeSelection(Predicate thePredicate) {
setHasImplicitTypeSelection();
addPredicate(thePredicate);
}
/**
* Adds predicates and marks them as having implicit type selection in it. In other words, call this method if a
* this predicate will ensure:
* <ul>
* <li>Only Resource PIDs for the correct resource type will be selected</li>
* <li>Only Resource PIDs for non-deleted resources will be selected</li>
* </ul>
* Setting this flag is a performance optimization, since it avoids the need for us to explicitly
* add predicates for the two conditions above.
*/
public void addPredicatesWithImplicitTypeSelection(List<Predicate> thePredicates) {
setHasImplicitTypeSelection();
addPredicates(thePredicates);
}
/**
* Adds predicate(s) to the current select statement
*/
public void addPredicates(List<Predicate> thePredicates) {
top().addPredicates(thePredicates);
}
/**
* Clear all predicates from the current select statement
*/
public void clearPredicates() {
top().clearPredicates();
}
/**
* Fetch all the current predicates
* <p>
* TODO This should really be package protected, but it is called externally in one spot - We need to clean that up
* at some point.
*/
public List<Predicate> getPredicates() {
return top().getPredicates();
}
private void setHasImplicitTypeSelection() {
top().setHasImplicitTypeSelection(true);
}
/**
* @see #setHasImplicitTypeSelection()
*/
public void clearHasImplicitTypeSelection() {
top().setHasImplicitTypeSelection(false);
}
public boolean isEmpty() {
return myQueryRootStack.isEmpty();
}
/**
* Add an SQL <code>order by</code> expression
*/
public void orderBy(List<Order> theOrders) {
top().orderBy(theOrders);
}
/**
* Fetch the column for the current table root that corresponds to the resource's lastUpdated time
*/
public Expression<Date> getLastUpdatedColumn() {
return top().getLastUpdatedColumn();
}
/**
* Fetch the column for the current table root that corresponds to the resource's PID
*/
public Expression<Long> getResourcePidColumn() {
return top().getResourcePidColumn();
}
public Subquery<Long> subqueryForTagNegation() {
return top().subqueryForTagNegation();
}
private QueryRootEntry top() {
Validate.isTrue(!myQueryRootStack.empty());
return myQueryRootStack.peek();
}
/**
* TODO This class should avoid leaking the internal query root, but we need to do so for how composite search params are
* currently implemented. These only half work in the first place so I'm not going to worry about the fact that
* they rely on a leaky abstraction right now.. But when we get around to implementing composites properly,
* let's not continue this. JA 2020-05-12
*/
public Root<?> getRootForComposite() {
return top().getRoot();
}
/**
* Add a predicate that will never match any resources
*/
public Predicate addNeverMatchingPredicate() {
return top().addNeverMatchingPredicate();
}
}

View File

@ -1225,7 +1225,9 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
ourLog.info("Created patient, got it: {}", id);
myCaptureQueriesListener.clear();
myPatientDao.deleteByUrl("Patient?organization._profile=http://foo", mySrd);
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertGone(id);
myOrganizationDao.deleteByUrl("Organization?_profile=http://foo", mySrd);

View File

@ -434,8 +434,7 @@ public class ConsentEventsDaoR4Test extends BaseJpaR4SystemTest {
IPreResourceAccessDetails accessDetails = theArgs.get(IPreResourceAccessDetails.class);
// FIXME: restore
// assertThat(accessDetails.size(), greaterThan(0));
assertThat(accessDetails.size(), greaterThan(0));
List<String> currentPassIds = new ArrayList<>();
for (int i = 0; i < accessDetails.size(); i++) {

View File

@ -21,9 +21,11 @@ import org.junit.Test;
import java.util.List;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.empty;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.mockito.ArgumentMatchers.contains;
public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4QueryCountTest.class);
@ -459,6 +461,35 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
}
@Test
public void testSearchOnChainedToken() {
Patient patient = new Patient();
patient.setId("P");
patient.addIdentifier().setSystem("sys").setValue("val");
myPatientDao.update(patient);
Observation obs = new Observation();
obs.setId("O");
obs.getSubject().setReference("Patient/P");
myObservationDao.update(obs);
SearchParameterMap map = SearchParameterMap.newSynchronous(Observation.SP_SUBJECT, new ReferenceParam("identifier", "sys|val"));
myCaptureQueriesListener.clear();
IBundleProvider outcome = myObservationDao.search(map);
assertThat(toUnqualifiedVersionlessIdValues(outcome), containsInAnyOrder("Observation/O"));
assertEquals(2, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
assertEquals(0, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
assertEquals(0, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread());
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertEquals(1, StringUtils.countMatches(myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true).toLowerCase(), "join"));
}
@Test
public void testTransactionWithMultipleReferences() {
Bundle input = new Bundle();

View File

@ -405,24 +405,30 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
IBundleProvider results;
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Encounter.SP_SUBJECT, new ReferenceParam("subject", "foo|bar").setChain("identifier"));
myCaptureQueriesListener.clear();
results = myEncounterDao.search(map);
myCaptureQueriesListener.logSelectQueriesForCurrentThread(0);
ids = toUnqualifiedVersionlessIdValues(results);
assertThat(ids, hasItems(enc1Id, enc2Id));
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Encounter.SP_SUBJECT, new ReferenceParam("subject:Patient", "foo|bar").setChain("identifier"));
results = myEncounterDao.search(map);
ids = toUnqualifiedVersionlessIdValues(results);
assertThat(ids, hasItems(enc1Id));
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Encounter.SP_SUBJECT, new ReferenceParam("subject:Group", "foo|bar").setChain("identifier"));
results = myEncounterDao.search(map);
ids = toUnqualifiedVersionlessIdValues(results);
assertThat(ids, hasItems(enc2Id));
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Encounter.SP_SUBJECT, new ReferenceParam("subject", "04823543").setChain("identifier"));
results = myEncounterDao.search(map);
ids = toUnqualifiedVersionlessIdValues(results);
@ -3021,7 +3027,7 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
String searchQuery = queries.get(0);
assertEquals(searchQuery, 3, StringUtils.countMatches(searchQuery.toUpperCase(), "HFJ_SPIDX_TOKEN"));
assertEquals(searchQuery, 5, StringUtils.countMatches(searchQuery.toUpperCase(), "LEFT OUTER JOIN"));
assertEquals(searchQuery, 4, StringUtils.countMatches(searchQuery.toUpperCase(), "LEFT OUTER JOIN"));
}

View File

@ -692,7 +692,7 @@ public class FhirResourceDaoR4SearchOptimizedTest extends BaseJpaR4Test {
myCaptureQueriesListener.logSelectQueries();
String selectQuery = myCaptureQueriesListener.getSelectQueries().get(1).getSql(true, true);
assertThat(selectQuery, containsString("HASH_VALUE in"));
assertThat(selectQuery, containsString("HASH_VALUE="));
assertThat(selectQuery, not(containsString("HASH_SYS")));
}

View File

@ -9,16 +9,20 @@ import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.DateParam;
import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.param.ParamPrefixEnum;
import ca.uhn.fhir.rest.param.ReferenceParam;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.util.TestUtil;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.DateTimeType;
import org.hl7.fhir.r4.model.Enumerations.AdministrativeGender;
import org.hl7.fhir.r4.model.Observation;
import org.hl7.fhir.r4.model.Patient;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Test;
import java.util.List;
import java.util.stream.Collectors;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder;
@ -320,6 +324,46 @@ public class FhirResourceDaoR4SortTest extends BaseJpaR4Test {
assertThat(ids, contains("Patient/AA", "Patient/AB", "Patient/BA", "Patient/BB"));
}
@Test
public void testSortWithChainedSearch() {
myDaoConfig.setIndexMissingFields(DaoConfig.IndexEnabledEnum.DISABLED);
Patient pCA = new Patient();
pCA.setId("CA");
pCA.addIdentifier().setSystem("PCA").setValue("PCA");
myPatientDao.update(pCA);
Observation obs1 = new Observation();
obs1.setId("OBS1");
obs1.getSubject().setReference("Patient/CA");
obs1.setEffective(new DateTimeType("2000-01-01"));
myObservationDao.update(obs1);
Observation obs2 = new Observation();
obs2.setId("OBS2");
obs2.getSubject().setReference("Patient/CA");
obs2.setEffective(new DateTimeType("2000-02-02"));
myObservationDao.update(obs2);
SearchParameterMap map;
List<String> ids;
runInTransaction(()->{
ourLog.info("Dates:\n * {}", myResourceIndexedSearchParamDateDao.findAll().stream().map(t->t.toString()).collect(Collectors.joining("\n * ")));
});
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Observation.SP_SUBJECT, new ReferenceParam("Patient", "identifier", "PCA|PCA"));
map.setSort(new SortSpec("date").setOrder(SortOrderEnum.DESC));
myCaptureQueriesListener.clear();
ids = toUnqualifiedVersionlessIdValues(myObservationDao.search(map));
ourLog.info("IDS: {}", ids);
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertThat(ids.toString(), ids, contains("Observation/OBS2", "Observation/OBS1"));
}
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();

View File

@ -398,7 +398,7 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
unformattedSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false);
assertThat(unformattedSql, stringContainsInOrder(
"IDX_STRING='Patient?identifier=urn%7C111'",
"HASH_SYS_AND_VALUE in ('-3122824860083758210')"
"HASH_SYS_AND_VALUE='-3122824860083758210'"
));
assertThat(unformattedSql, not(containsString(("RES_DELETED_AT"))));
assertThat(unformattedSql, not(containsString(("RES_TYPE"))));
@ -535,7 +535,7 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
unformattedSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false);
assertThat(unformattedSql, stringContainsInOrder(
"IDX_STRING='ServiceRequest?identifier=sys%7C111&patient=Patient%2F" + ptId.getIdPart() + "&performer=Practitioner%2F" + practId.getIdPart() + "'",
"HASH_SYS_AND_VALUE in ('6795110643554413877')"
"HASH_SYS_AND_VALUE='6795110643554413877'"
));
assertThat(unformattedSql, not(containsString(("RES_DELETED_AT"))));
assertThat(unformattedSql, not(containsString(("RES_TYPE"))));

View File

@ -66,7 +66,7 @@ public class SearchWithInterceptorR4Test extends BaseJpaR4Test {
String query = list.get(0).getSql(true, false);
ourLog.info("Query: {}", query);
assertThat(query, containsString("HASH_SYS_AND_VALUE in ('3788488238034018567')"));
assertThat(query, containsString("HASH_SYS_AND_VALUE='3788488238034018567'"));
} finally {
myInterceptorRegistry.unregisterInterceptor(interceptor);

View File

@ -60,9 +60,11 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
init410(); // 20190815 - 20191014
init420(); // 20191015 - 20200217
init430(); // Replaced by 5.0.0
init500(); // 20200218 - present
init500(); // 20200218 - 20200513
init501(); // 20200514 - present
}
/**
* Partway through the 4.3.0 releaase cycle we renumbered to
* 5.0.0 - We have a bunch of NOP tasks here to avoid breakage for anyone
@ -122,7 +124,17 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
version.addNop("20200420.42");
}
protected void init500() { // 20200218 - present
private void init501() { //20200514 - present
Builder version = forVersion(VersionEnum.V5_0_1);
Builder.BuilderWithTableName spidxDate = version.onTable("HFJ_SPIDX_DATE");
spidxDate.addIndex("20200514.1", "IDX_SP_DATE_HASH_LOW").unique(false).withColumns("HASH_IDENTITY", "SP_VALUE_LOW");
spidxDate.addIndex("20200514.2", "IDX_SP_DATE_ORD_HASH").unique(false).withColumns("HASH_IDENTITY", "SP_VALUE_LOW_DATE_ORDINAL", "SP_VALUE_HIGH_DATE_ORDINAL");
spidxDate.addIndex("20200514.3", "IDX_SP_DATE_ORD_HASH_LOW").unique(false).withColumns("HASH_IDENTITY", "SP_VALUE_LOW_DATE_ORDINAL");
}
protected void init500() { // 20200218 - 20200519
Builder version = forVersion(VersionEnum.V5_0_0);
// Eliminate circular dependency.

View File

@ -65,7 +65,12 @@ public class BaseMigrationTasks<T extends Enum> {
theTask.validate();
myTasks.put(theRelease, theTask);
};
return new Builder(theRelease.name(), sink);
return new Builder(toReleaseName(theRelease), sink);
}
@Nonnull
protected String toReleaseName(T theRelease) {
return theRelease.name();
}
public List<BaseTask> getAllTasks(T[] theVersionEnumValues) {

View File

@ -54,8 +54,11 @@ import java.util.Date;
@Table(name = "HFJ_SPIDX_DATE", indexes = {
// We previously had an index called IDX_SP_DATE - Dont reuse
@Index(name = "IDX_SP_DATE_HASH", columnList = "HASH_IDENTITY,SP_VALUE_LOW,SP_VALUE_HIGH"),
@Index(name = "IDX_SP_DATE_HASH_LOW", columnList = "HASH_IDENTITY,SP_VALUE_LOW"),
@Index(name = "IDX_SP_DATE_ORD_HASH", columnList = "HASH_IDENTITY,SP_VALUE_LOW_DATE_ORDINAL,SP_VALUE_HIGH_DATE_ORDINAL"),
@Index(name = "IDX_SP_DATE_ORD_HASH_LOW", columnList = "HASH_IDENTITY,SP_VALUE_LOW_DATE_ORDINAL"),
@Index(name = "IDX_SP_DATE_RESID", columnList = "RES_ID"),
@Index(name = "IDX_SP_DATE_UPDATED", columnList = "SP_UPDATED"),
@Index(name = "IDX_SP_DATE_RESID", columnList = "RES_ID")
})
public class ResourceIndexedSearchParamDate extends BaseResourceIndexedSearchParam {

View File

@ -677,4 +677,18 @@ public class SearchParameterMap implements Serializable {
public int size() {
return mySearchParameterMap.size();
}
public static SearchParameterMap newSynchronous() {
SearchParameterMap retVal = new SearchParameterMap();
retVal.setLoadSynchronous(true);
return retVal;
}
public static SearchParameterMap newSynchronous(String theName, IQueryParameterType theParam) {
SearchParameterMap retVal = new SearchParameterMap();
retVal.setLoadSynchronous(true);
retVal.add(theName, theParam);
return retVal;
}
}

View File

@ -107,10 +107,12 @@ public interface ITestDataBuilder {
}
default <T extends IBaseResource> Consumer<T> withId(String theId) {
return t -> {
assertThat(theId, matchesPattern("[a-zA-Z0-9]+"));
t.setId(theId);
};
assertThat(theId, matchesPattern("[a-zA-Z0-9-]+"));
return t -> t.setId(theId);
}
default <T extends IBaseResource> Consumer<T> withId(IIdType theId) {
return withId(theId.getIdPart());
}
default Consumer<IBaseResource> withTag(String theSystem, String theCode) {