Optimize chain sql paths (#1844)
* Start cleanup * Almost workinf * Possibly working!! * Fixes * Test fix * Update hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/querystack/QueryRootStack.java Co-authored-by: Ken Stevens <khstevens@gmail.com> * Update hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/querystack/QueryRootStack.java Co-authored-by: Ken Stevens <khstevens@gmail.com> * Update hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/querystack/QueryRootStack.java Co-authored-by: Ken Stevens <khstevens@gmail.com> * Address review comments * Add changelog Co-authored-by: Ken Stevens <khstevens@gmail.com>
This commit is contained in:
parent
8a360ebd5b
commit
79a064dfd9
|
@ -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."
|
|
@ -0,0 +1,3 @@
|
|||
---
|
||||
release-date: "2020-05-15"
|
||||
codename: "Labrador"
|
|
@ -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;
|
||||
|
@ -71,6 +70,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;
|
||||
|
@ -99,7 +99,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;
|
||||
|
@ -111,6 +110,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;
|
||||
|
@ -133,7 +133,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;
|
||||
|
@ -143,6 +142,7 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
protected IResourceTagDao myResourceTagDao;
|
||||
@PersistenceContext(type = PersistenceContextType.TRANSACTION)
|
||||
protected EntityManager myEntityManager;
|
||||
private QueryStack myQueryStack;
|
||||
@Autowired
|
||||
private DaoConfig myDaoConfig;
|
||||
@Autowired
|
||||
|
@ -253,8 +253,9 @@ 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;
|
||||
|
@ -262,7 +263,6 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
|
||||
|
||||
private TypedQuery<Long> createQuery(SortSpec sort, Integer theMaximumResults, boolean theCount, RequestDetails theRequest) {
|
||||
CriteriaQuery<Long> outerQuery;
|
||||
/*
|
||||
* Sort
|
||||
*
|
||||
|
@ -272,36 +272,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);
|
||||
|
@ -310,11 +298,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 {
|
||||
|
@ -345,41 +333,20 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
pids = Collections.singletonList(new ResourcePersistentId(-1L));
|
||||
}
|
||||
|
||||
myQueryRoot.addPredicate(myQueryRoot.get("myId").as(Long.class).in(ResourcePersistentId.toLongList(pids)));
|
||||
}
|
||||
|
||||
/*
|
||||
* 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()));
|
||||
} else {
|
||||
myQueryRoot.addPredicate(myCriteriaBuilder.isNull(myQueryRoot.get("myPartitionIdValue").as(Integer.class)));
|
||||
}
|
||||
}
|
||||
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);
|
||||
|
@ -392,32 +359,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);
|
||||
|
@ -426,43 +396,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;
|
||||
|
@ -478,37 +440,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;
|
||||
}
|
||||
|
||||
|
||||
|
@ -616,7 +581,7 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
}
|
||||
|
||||
List<ResourcePersistentId> pids = new ArrayList<>(thePids);
|
||||
new QueryChunker<ResourcePersistentId>().chunk(pids, t->{
|
||||
new QueryChunker<ResourcePersistentId>().chunk(pids, t -> {
|
||||
doLoadPids(t, theIncludedPids, theResourceListToPopulate, theForHistoryOperation, position, theDetails);
|
||||
});
|
||||
|
||||
|
@ -885,17 +850,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();
|
||||
|
@ -924,8 +888,8 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
return myCriteriaBuilder;
|
||||
}
|
||||
|
||||
public QueryRoot getQueryRoot() {
|
||||
return myQueryRoot;
|
||||
public QueryStack getQueryStack() {
|
||||
return myQueryStack;
|
||||
}
|
||||
|
||||
public Class<? extends IBaseResource> getResourceType() {
|
||||
|
@ -941,6 +905,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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
@ -121,7 +120,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
|
|||
@Autowired
|
||||
DaoRegistry myDaoRegistry;
|
||||
@Autowired
|
||||
PartitionSettings myPartitionSettings;
|
||||
PartitionSettings myPartitionSettings;
|
||||
@Autowired
|
||||
private IInterceptorBroadcaster myInterceptorBroadcaster;
|
||||
|
||||
|
@ -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());
|
||||
}
|
||||
searchForIdsWithAndOr(theSubResourceName, theChain, andOrParams, theRequest, theRequestPartitionId);
|
||||
|
||||
/*
|
||||
* 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: {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -28,6 +28,8 @@ public enum SearchBuilderJoinEnum {
|
|||
STRING,
|
||||
TOKEN,
|
||||
URI,
|
||||
COORDS
|
||||
COORDS,
|
||||
HAS,
|
||||
FORCED_ID, PRESENCE, COMPOSITE_UNIQUE, RESOURCE_TAGS, PROVENANCE
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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++) {
|
||||
|
|
|
@ -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);
|
||||
|
@ -457,6 +459,35 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
|
|||
assertEquals(0, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
|
||||
assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread());
|
||||
}
|
||||
|
||||
|
||||
@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
|
||||
|
|
|
@ -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"));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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")));
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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"))));
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -639,4 +639,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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue