From 99a4b2c29e5c9fe13c0f3861ff7400fded08c70a Mon Sep 17 00:00:00 2001 From: jamesagnew Date: Sat, 5 Mar 2016 20:46:04 -0500 Subject: [PATCH] Refactor JPA SearchBuilder --- .../fhir/model/api/IQueryParameterType.java | 6 +- .../model/base/composite/BaseCodingDt.java | 2 +- .../base/composite/BaseIdentifierDt.java | 2 +- .../model/base/composite/BaseQuantityDt.java | 2 +- .../ca/uhn/fhir/model/primitive/StringDt.java | 2 +- .../ca/uhn/fhir/rest/param/BaseParam.java | 9 +- .../uhn/fhir/rest/param/InternalCodingDt.java | 3 +- .../ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java | 9 +- .../fhir/jpa/dao/BaseHapiFhirResourceDao.java | 7 +- .../ca/uhn/fhir/jpa/dao/IFhirResourceDao.java | 4 +- .../ca/uhn/fhir/jpa/dao/SearchBuilder.java | 693 +++++++++--------- .../java/ca/uhn/fhir/jpa/entity/Search.java | 2 +- .../uhn/fhir/jpa/term/TerminologySvcImpl.java | 9 +- .../FhirResourceDaoDstu2SearchNoFtTest.java | 124 +++- .../dao/dstu2/FhirResourceDaoDstu2Test.java | 2 +- .../dao/dstu3/FhirResourceDaoDstu3Test.java | 2 +- .../ca/uhn/fhir/testmodel/IdentifierDt.java | 2 +- pom.xml | 9 + src/site/site.xml | 16 +- 19 files changed, 516 insertions(+), 389 deletions(-) diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IQueryParameterType.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IQueryParameterType.java index 330ccc021d3..a01261ea202 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IQueryParameterType.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IQueryParameterType.java @@ -69,8 +69,10 @@ public interface IQueryParameterType { /** * If set to non-null value, indicates that this parameter has been populated with a "[name]:missing=true" or "[name]:missing=false" vale - * instead of a normal value + * instead of a normal value + * + * @return Returns a reference to this for easier method chaining */ - void setMissing(Boolean theMissing); + IQueryParameterType setMissing(Boolean theMissing); } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/base/composite/BaseCodingDt.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/base/composite/BaseCodingDt.java index 501db2575dc..8292495b262 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/base/composite/BaseCodingDt.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/base/composite/BaseCodingDt.java @@ -194,7 +194,7 @@ public abstract class BaseCodingDt extends BaseIdentifiableElement implements IC */ @Deprecated @Override - public void setMissing(Boolean theMissing) { + public IQueryParameterType setMissing(Boolean theMissing) { throw new UnsupportedOperationException("get/setMissing is not supported in StringDt. Use {@link StringParam} instead if you need this functionality"); } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/base/composite/BaseIdentifierDt.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/base/composite/BaseIdentifierDt.java index fc6fce34471..968d72979dd 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/base/composite/BaseIdentifierDt.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/base/composite/BaseIdentifierDt.java @@ -135,7 +135,7 @@ public abstract class BaseIdentifierDt extends BaseIdentifiableElement implement */ @Deprecated @Override - public void setMissing(Boolean theMissing) { + public IQueryParameterType setMissing(Boolean theMissing) { throw new UnsupportedOperationException("get/setMissing is not supported in StringDt. Use {@link StringParam} instead if you need this functionality"); } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/base/composite/BaseQuantityDt.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/base/composite/BaseQuantityDt.java index 2ee214a5526..b1565b0e225 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/base/composite/BaseQuantityDt.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/base/composite/BaseQuantityDt.java @@ -231,7 +231,7 @@ public abstract class BaseQuantityDt extends BaseIdentifiableElement implements */ @Deprecated @Override - public void setMissing(Boolean theMissing) { + public IQueryParameterType setMissing(Boolean theMissing) { throw new UnsupportedOperationException("get/setMissing is not supported in StringDt. Use {@link StringParam} instead if you need this functionality"); } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/StringDt.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/StringDt.java index 83cb0755576..9ccfabed60d 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/StringDt.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/StringDt.java @@ -144,7 +144,7 @@ public class StringDt extends BasePrimitive implements IQueryParameterTy */ @Deprecated @Override - public void setMissing(Boolean theMissing) { + public IQueryParameterType setMissing(Boolean theMissing) { throw new UnsupportedOperationException("get/setMissing is not supported in StringDt. Use {@link StringParam} instead if you need this functionality"); } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/BaseParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/BaseParam.java index 8ded069de12..9791ec71c60 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/BaseParam.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/BaseParam.java @@ -61,11 +61,16 @@ abstract class BaseParam implements IQueryParameterType { } /** - * If set to non-null value, indicates that this parameter has been populated with a "[name]:missing=true" or "[name]:missing=false" vale instead of a normal value + * If set to non-null value, indicates that this parameter has been populated + * with a "[name]:missing=true" or "[name]:missing=false" vale instead of a + * normal value + * + * @return Returns a reference to this for easier method chaining */ @Override - public void setMissing(Boolean theMissing) { + public BaseParam setMissing(Boolean theMissing) { myMissing = theMissing; + return this; } @Override diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/InternalCodingDt.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/InternalCodingDt.java index 9e613aa9a37..b87d2a64a1b 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/InternalCodingDt.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/InternalCodingDt.java @@ -24,6 +24,7 @@ import java.util.List; import ca.uhn.fhir.model.api.ICompositeDatatype; import ca.uhn.fhir.model.api.IElement; +import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.model.api.annotation.Child; import ca.uhn.fhir.model.api.annotation.Description; import ca.uhn.fhir.model.base.composite.BaseCodingDt; @@ -297,7 +298,7 @@ public class InternalCodingDt extends BaseCodingDt implements ICompositeDatatype } @Override - public void setMissing(Boolean theMissing) { + public IQueryParameterType setMissing(Boolean theMissing) { throw new UnsupportedOperationException(); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java index 9285b207d59..8d075138965 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java @@ -936,7 +936,6 @@ public abstract class BaseHapiFhirDao implements IDao { * @param theTag * The tag * @return Returns true if the tag should be removed - * @see Updates to Tags, Profiles, and Security Labels for a description of the logic that the default behaviour folows. */ @SuppressWarnings("unused") protected void postPersist(ResourceTable theEntity, T theResource) { @@ -966,7 +965,7 @@ public abstract class BaseHapiFhirDao implements IDao { } IFhirResourceDao dao = getDao(theResourceType); - Set ids = dao.searchForIdsWithAndOr(paramMap, new HashSet(), paramMap.getLastUpdated()); + Set ids = dao.searchForIdsWithAndOr(paramMap, paramMap.getLastUpdated()); return ids; } @@ -1168,14 +1167,16 @@ public abstract class BaseHapiFhirDao implements IDao { * The default implementation removes any profile declarations, but leaves tags and security labels in place. * Subclasses may choose to override and change this behaviour. *

+ *

+ * See Updates to Tags, Profiles, and Security + * Labels for a description of the logic that the default behaviour folows. + *

* * @param theEntity * The entity being updated (Do not modify the entity! Undefined behaviour will occur!) * @param theTag * The tag * @return Retturns true if the tag should be removed - * @see Updates to Tags, Profiles, and Security - * Labels for a description of the logic that the default behaviour folows. */ protected boolean shouldDroppedTagBeRemovedOnUpdate(ResourceTable theEntity, ResourceTag theTag) { if (theTag.getTag().getTagType() == TagTypeEnum.PROFILE) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java index 728243c432b..77cd069baf0 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java @@ -932,7 +932,7 @@ public abstract class BaseHapiFhirResourceDao extends B for (Entry nextEntry : theParams.entrySet()) { map.add(nextEntry.getKey(), (nextEntry.getValue())); } - return searchForIdsWithAndOr(map, null, null); + return searchForIdsWithAndOr(map, null); } @Override @@ -941,10 +941,11 @@ public abstract class BaseHapiFhirResourceDao extends B } @Override - public Set searchForIdsWithAndOr(SearchParameterMap theParams, Collection theInitialPids, DateRangeParam theLastUpdated) { + public Set searchForIdsWithAndOr(SearchParameterMap theParams, DateRangeParam theLastUpdated) { SearchBuilder builder = new SearchBuilder(getContext(), myEntityManager, myPlatformTransactionManager, mySearchDao, mySearchResultDao, this, myResourceIndexedSearchParamUriDao); builder.setType(getResourceType(), getResourceName()); - return builder.searchForIdsWithAndOr(theParams, theInitialPids, theLastUpdated); + builder.searchForIdsWithAndOr(theParams, theLastUpdated); + return builder.doGetPids(); } @SuppressWarnings("unchecked") diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDao.java index adef7dcc5aa..69278a45c94 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDao.java @@ -1,7 +1,5 @@ package ca.uhn.fhir.jpa.dao; -import java.util.Collection; - /* * #%L * HAPI FHIR JPA Server @@ -164,7 +162,7 @@ public interface IFhirResourceDao extends IDao { Set searchForIds(String theParameterName, IQueryParameterType theValue); - Set searchForIdsWithAndOr(SearchParameterMap theParams, Collection theInitialPids, DateRangeParam theLastUpdated); + Set searchForIdsWithAndOr(SearchParameterMap theParams, DateRangeParam theLastUpdated); DaoMethodOutcome update(T theResource, RequestDetails theRequestDetails); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java index e83ed5ebd9b..7b6b2f14b37 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java @@ -39,6 +39,7 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import java.util.UUID; import javax.persistence.EntityManager; import javax.persistence.Tuple; @@ -85,6 +86,7 @@ import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamUri; import ca.uhn.fhir.jpa.entity.ResourceLink; import ca.uhn.fhir.jpa.entity.ResourceTable; import ca.uhn.fhir.jpa.entity.ResourceTag; +import ca.uhn.fhir.jpa.entity.Search; import ca.uhn.fhir.jpa.entity.TagDefinition; import ca.uhn.fhir.jpa.entity.TagTypeEnum; import ca.uhn.fhir.jpa.util.StopWatch; @@ -134,6 +136,12 @@ public class SearchBuilder { private ISearchResultDao mySearchResultDao; private IResourceIndexedSearchParamUriDao myResourceIndexedSearchParamUriDao; + private Search mySearchEntity; + + private boolean myHaveFlushedSearch; + + private SearchParameterMap myParams; + public SearchBuilder(FhirContext theFhirContext, EntityManager theEntityManager, PlatformTransactionManager thePlatformTransactionManager, ISearchDao theSearchDao, ISearchResultDao theSearchResultDao, BaseHapiFhirDao theDao, IResourceIndexedSearchParamUriDao theResourceIndexedSearchParamUriDao) { myContext = theFhirContext; myEntityManager = theEntityManager; @@ -144,7 +152,7 @@ public class SearchBuilder { myResourceIndexedSearchParamUriDao = theResourceIndexedSearchParamUriDao; } - private Set addPredicateComposite(RuntimeSearchParam theParamDef, Set thePids, List theNextAnd) { + private void addPredicateComposite(RuntimeSearchParam theParamDef, List theNextAnd) { // TODO: fail if missing is set for a composite query CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); @@ -158,34 +166,30 @@ public class SearchBuilder { } CompositeParam cp = (CompositeParam) or; + List predicates = new ArrayList(); + predicates.add(builder.equal(from.get("myResourceType"), myResourceName)); + RuntimeSearchParam left = theParamDef.getCompositeOf().get(0); IQueryParameterType leftValue = cp.getLeftValue(); - Predicate leftPredicate = createCompositeParamPart(builder, from, left, leftValue); + predicates.add(createCompositeParamPart(builder, from, left, leftValue)); RuntimeSearchParam right = theParamDef.getCompositeOf().get(1); IQueryParameterType rightValue = cp.getRightValue(); - Predicate rightPredicate = createCompositeParamPart(builder, from, right, rightValue); + predicates.add(createCompositeParamPart(builder, from, right, rightValue)); - Predicate type = builder.equal(from.get("myResourceType"), myResourceName); - if (thePids.size() > 0) { - Predicate inPids = (from.get("myResourcePid").in(thePids)); - cq.where(builder.and(type, leftPredicate, rightPredicate, inPids)); - } else { - cq.where(builder.and(type, leftPredicate, rightPredicate)); - } + doCreateIdPredicate(predicates, from.get("myId").as(Long.class)); + cq.where(builder.and(toArray(predicates))); TypedQuery q = myEntityManager.createQuery(cq); - return new HashSet(q.getResultList()); + doSetPids(new HashSet(q.getResultList())); } - private Set addPredicateDate(String theParamName, Set thePids, List theList) { - if (theList == null || theList.isEmpty()) { - return thePids; - } + private void addPredicateDate(String theParamName, List theList) { if (Boolean.TRUE.equals(theList.get(0).getMissing())) { - return addPredicateParamMissing(thePids, "myParamsDate", theParamName, ResourceIndexedSearchParamDate.class); + addPredicateParamMissing("myParamsDate", theParamName, ResourceIndexedSearchParamDate.class); + return; } CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); @@ -206,22 +210,21 @@ public class SearchBuilder { Predicate masterCodePredicate = builder.or(toArray(codePredicates)); - Predicate type = builder.equal(from.get("myResourceType"), myResourceName); - Predicate name = builder.equal(from.get("myParamName"), theParamName); - if (thePids.size() > 0) { - Predicate inPids = (from.get("myResourcePid").in(thePids)); - cq.where(builder.and(type, name, masterCodePredicate, inPids)); - } else { - cq.where(builder.and(type, name, masterCodePredicate)); - } + List predicates = new ArrayList(); + predicates.add(builder.equal(from.get("myResourceType"), myResourceName)); + predicates.add(builder.equal(from.get("myParamName"), theParamName)); + doCreateIdPredicate(predicates, from.get("myResourcePid").as(Long.class)); + predicates.add(masterCodePredicate); + + cq.where(builder.and(toArray(predicates))); TypedQuery q = myEntityManager.createQuery(cq); - return new HashSet(q.getResultList()); + doSetPids(new HashSet(q.getResultList())); } - private Set addPredicateId(Set theExistingPids, Set thePids, DateRangeParam theLastUpdated) { + private void addPredicateId(Set thePids, DateRangeParam theLastUpdated) { if (thePids == null || thePids.isEmpty()) { - return Collections.emptySet(); + return; } CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); @@ -232,25 +235,23 @@ public class SearchBuilder { List predicates = new ArrayList(); predicates.add(builder.equal(from.get("myResourceType"), myResourceName)); predicates.add(from.get("myId").in(thePids)); + doCreateIdPredicate(predicates, from.get("myId").as(Long.class)); predicates.addAll(createLastUpdatedPredicates(theLastUpdated, builder, from)); - + cq.where(toArray(predicates)); TypedQuery q = myEntityManager.createQuery(cq); HashSet found = new HashSet(q.getResultList()); - if (!theExistingPids.isEmpty()) { - theExistingPids.retainAll(found); - return theExistingPids; - } else { - return found; + doSetPids(found); + } + + private void doCreateIdPredicate(List thePredicates, Expression theExpression) { + if (myPids != null) { + thePredicates.add(theExpression.in(myPids)); } } - private Set addPredicateLanguage(Set thePids, List> theList, DateRangeParam theLastUpdated) { - Set retVal = thePids; - if (theList == null || theList.isEmpty()) { - return retVal; - } + private void addPredicateLanguage(List> theList, DateRangeParam theLastUpdated) { for (List nextList : theList) { CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); @@ -272,30 +273,27 @@ public class SearchBuilder { } if (values.isEmpty()) { - return retVal; + continue; } List predicates = new ArrayList(); predicates.add(builder.equal(from.get("myResourceType"), myResourceName)); predicates.add(from.get("myLanguage").as(String.class).in(values)); - - if (retVal.size() > 0) { - Predicate inPids = (from.get("myId").in(retVal)); - predicates.add(inPids); - } + doCreateIdPredicate(predicates, from.get("myId").as(Long.class)); predicates.add(builder.isNull(from.get("myDeleted"))); cq.where(toArray(predicates)); TypedQuery q = myEntityManager.createQuery(cq); - retVal = new HashSet(q.getResultList()); - if (retVal.isEmpty()) { - return retVal; + HashSet pids = new HashSet(q.getResultList()); + doSetPids(pids); + if (doHaveNoResults()) { + return; } } - return retVal; + return; } private boolean addPredicateMissingFalseIfPresent(CriteriaBuilder theBuilder, String theParamName, Root from, List codePredicates, IQueryParameterType nextOr) { @@ -326,13 +324,11 @@ public class SearchBuilder { return missingFalse; } - private Set addPredicateNumber(String theParamName, Set thePids, List theList) { - if (theList == null || theList.isEmpty()) { - return thePids; - } - + private void addPredicateNumber(String theParamName, List theList) { + if (Boolean.TRUE.equals(theList.get(0).getMissing())) { - return addPredicateParamMissing(thePids, "myParamsNumber", theParamName, ResourceIndexedSearchParamNumber.class); + addPredicateParamMissing("myParamsNumber", theParamName, ResourceIndexedSearchParamNumber.class); + return; } CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); @@ -353,7 +349,7 @@ public class SearchBuilder { BigDecimal value = param.getValue(); if (value == null) { - return thePids; + continue; } final Expression fromObj = from.get("myValue"); @@ -370,22 +366,19 @@ public class SearchBuilder { } - Predicate masterCodePredicate = builder.or(toArray(codePredicates)); - - Predicate type = builder.equal(from.get("myResourceType"), myResourceName); - Predicate name = builder.equal(from.get("myParamName"), theParamName); - if (thePids.size() > 0) { - Predicate inPids = (from.get("myResourcePid").in(thePids)); - cq.where(builder.and(type, name, masterCodePredicate, inPids)); - } else { - cq.where(builder.and(type, name, masterCodePredicate)); - } + List predicates = new ArrayList(); + predicates.add(builder.equal(from.get("myResourceType"), myResourceName)); + predicates.add(builder.equal(from.get("myParamName"), theParamName)); + predicates.add(builder.or(toArray(codePredicates))); + doCreateIdPredicate(predicates, from.get("myResourcePid").as(Long.class)); + + cq.where(builder.and(toArray(predicates))); TypedQuery q = myEntityManager.createQuery(cq); - return new HashSet(q.getResultList()); + doSetPids(new HashSet(q.getResultList())); } - private Set addPredicateParamMissing(Set thePids, String joinName, String theParamName, Class theParamTable) { + private void addPredicateParamMissing(String joinName, String theParamName, Class theParamTable) { CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); CriteriaQuery cq = builder.createQuery(Long.class); Root from = cq.from(ResourceTable.class); @@ -398,26 +391,24 @@ public class SearchBuilder { Predicate subQtype = builder.equal(subQfrom.get("myResourceType"), myResourceName); subQ.where(builder.and(subQtype, subQname)); - Predicate joinPredicate = builder.not(builder.in(from.get("myId")).value(subQ)); - Predicate typePredicate = builder.equal(from.get("myResourceType"), myResourceName); - Predicate notDeletedPredicate = builder.isNull(from.get("myDeleted")); - - if (thePids.size() > 0) { - Predicate inPids = (from.get("myId").in(thePids)); - cq.where(builder.and(inPids, typePredicate, joinPredicate, notDeletedPredicate)); - } else { - cq.where(builder.and(typePredicate, joinPredicate, notDeletedPredicate)); - } + List predicates = new ArrayList(); + predicates.add(builder.not(builder.in(from.get("myId")).value(subQ))); + predicates.add(builder.equal(from.get("myResourceType"), myResourceName)); + predicates.add(builder.isNull(from.get("myDeleted"))); + doCreateIdPredicate(predicates, from.get("myId").as(Long.class)); + + cq.where(builder.and(toArray(predicates))); ourLog.info("Adding :missing qualifier for parameter '{}'", theParamName); TypedQuery q = myEntityManager.createQuery(cq); List resultList = q.getResultList(); + HashSet retVal = new HashSet(resultList); - return retVal; + doSetPids(retVal); } - private Set addPredicateParamMissingResourceLink(Set thePids, String joinName, String theParamName) { + private void addPredicateParamMissingResourceLink(String joinName, String theParamName) { CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); CriteriaQuery cq = builder.createQuery(Long.class); Root from = cq.from(ResourceTable.class); @@ -431,29 +422,22 @@ public class SearchBuilder { Predicate path = createResourceLinkPathPredicate(theParamName, builder, subQfrom); subQ.where(path); - Predicate joinPredicate = builder.not(builder.in(from.get("myId")).value(subQ)); - Predicate typePredicate = builder.equal(from.get("myResourceType"), myResourceName); + List predicates = new ArrayList(); + doCreateIdPredicate(predicates, from.get("myId").as(Long.class)); + predicates.add(builder.not(builder.in(from.get("myId")).value(subQ))); + predicates.add(builder.equal(from.get("myResourceType"), myResourceName)); - if (thePids.size() > 0) { - Predicate inPids = (from.get("myId").in(thePids)); - cq.where(builder.and(inPids, typePredicate, joinPredicate)); - } else { - cq.where(builder.and(typePredicate, joinPredicate)); - } + cq.where(builder.and(toArray(predicates))); TypedQuery q = myEntityManager.createQuery(cq); List resultList = q.getResultList(); - HashSet retVal = new HashSet(resultList); - return retVal; + doSetPids(new HashSet(resultList)); } - private Set addPredicateQuantity(String theParamName, Set thePids, List theList) { - if (theList == null || theList.isEmpty()) { - return thePids; - } - + private void addPredicateQuantity(String theParamName, List theList) { if (Boolean.TRUE.equals(theList.get(0).getMissing())) { - return addPredicateParamMissing(thePids, "myParamsQuantity", theParamName, ResourceIndexedSearchParamQuantity.class); + addPredicateParamMissing("myParamsQuantity", theParamName, ResourceIndexedSearchParamQuantity.class); + return; } CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); @@ -523,31 +507,24 @@ public class SearchBuilder { } } - Predicate masterCodePredicate = builder.or(toArray(codePredicates)); + List predicates = new ArrayList(); + predicates.add(builder.equal(from.get("myResourceType"), myResourceName)); + predicates.add(builder.equal(from.get("myParamName"), theParamName)); + predicates.add(builder.or(toArray(codePredicates))); + doCreateIdPredicate(predicates, from.get("myResourcePid").as(Long.class)); - Predicate type = builder.equal(from.get("myResourceType"), myResourceName); - Predicate name = builder.equal(from.get("myParamName"), theParamName); - if (thePids.size() > 0) { - Predicate inPids = (from.get("myResourcePid").in(thePids)); - cq.where(builder.and(type, name, masterCodePredicate, inPids)); - } else { - cq.where(builder.and(type, name, masterCodePredicate)); - } + cq.where(builder.and(toArray(predicates))); TypedQuery q = myEntityManager.createQuery(cq); - return new HashSet(q.getResultList()); + doSetPids(new HashSet(q.getResultList())); } - private Set addPredicateReference(String theParamName, Set thePids, List theList) { + private void addPredicateReference(String theParamName, List theList) { assert theParamName.contains(".") == false; - Set pidsToRetain = thePids; - if (theList == null || theList.isEmpty()) { - return pidsToRetain; - } - if (Boolean.TRUE.equals(theList.get(0).getMissing())) { - return addPredicateParamMissingResourceLink(thePids, "myResourceLinks", theParamName); + addPredicateParamMissingResourceLink("myResourceLinks", theParamName); + return; } CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); @@ -677,27 +654,21 @@ public class SearchBuilder { } - Predicate masterCodePredicate = builder.or(toArray(codePredicates)); + List predicates = new ArrayList(); + predicates.add(createResourceLinkPathPredicate(theParamName, builder, from)); + predicates.add(builder.or(toArray(codePredicates))); + doCreateIdPredicate(predicates, from.get("mySourceResourcePid").as(Long.class)); - Predicate type = createResourceLinkPathPredicate(theParamName, builder, from); - if (pidsToRetain.size() > 0) { - Predicate inPids = (from.get("mySourceResourcePid").in(pidsToRetain)); - cq.where(builder.and(type, masterCodePredicate, inPids)); - } else { - cq.where(builder.and(type, masterCodePredicate)); - } + cq.where(builder.and(toArray(predicates))); TypedQuery q = myEntityManager.createQuery(cq); - return new HashSet(q.getResultList()); + doSetPids(new HashSet(q.getResultList())); } - private Set addPredicateString(String theParamName, Set thePids, List theList) { - if (theList == null || theList.isEmpty()) { - return thePids; - } - + private void addPredicateString(String theParamName, List theList) { if (Boolean.TRUE.equals(theList.get(0).getMissing())) { - return addPredicateParamMissing(thePids, "myParamsString", theParamName, ResourceIndexedSearchParamString.class); + addPredicateParamMissing("myParamsString", theParamName, ResourceIndexedSearchParamString.class); + return; } CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); @@ -716,27 +687,19 @@ public class SearchBuilder { codePredicates.add(singleCode); } - Predicate masterCodePredicate = builder.or(toArray(codePredicates)); - - Predicate type = builder.equal(from.get("myResourceType"), myResourceName); - Predicate name = builder.equal(from.get("myParamName"), theParamName); - if (thePids.size() > 0) { - Predicate inPids = (from.get("myResourcePid").in(thePids)); - cq.where(builder.and(type, name, masterCodePredicate, inPids)); - } else { - cq.where(builder.and(type, name, masterCodePredicate)); - } + List predicates = new ArrayList(); + predicates.add(builder.equal(from.get("myResourceType"), myResourceName)); + predicates.add(builder.equal(from.get("myParamName"), theParamName)); + predicates.add(builder.or(toArray(codePredicates))); + doCreateIdPredicate(predicates, from.get("myResourcePid").as(Long.class)); + + cq.where(builder.and(toArray(predicates))); TypedQuery q = myEntityManager.createQuery(cq); - return new HashSet(q.getResultList()); + doSetPids(new HashSet(q.getResultList())); } - private Set addPredicateTag(Set thePids, List> theList, String theParamName, DateRangeParam theLastUpdated) { - Set pids = thePids; - if (theList == null || theList.isEmpty()) { - return pids; - } - + private void addPredicateTag(List> theList, String theParamName, DateRangeParam theLastUpdated) { TagTypeEnum tagType; if (Constants.PARAM_TAG.equals(theParamName)) { tagType = TagTypeEnum.TAG; @@ -815,29 +778,23 @@ public class SearchBuilder { andPredicates.addAll(createLastUpdatedPredicates(theLastUpdated, builder, defJoin)); } + doCreateIdPredicate(andPredicates, from.get("myResourceId").as(Long.class)); Predicate masterCodePredicate = builder.and(toArray(andPredicates)); - if (pids.size() > 0) { - Predicate inPids = (from.get("myResourceId").in(pids)); - cq.where(builder.and(masterCodePredicate, inPids)); - } else { - cq.where(masterCodePredicate); - } + cq.where(masterCodePredicate); TypedQuery q = myEntityManager.createQuery(cq); - pids = new HashSet(q.getResultList()); + Set pids = new HashSet(q.getResultList()); + doSetPids(pids); } - return pids; } - private Set addPredicateToken(String theParamName, Set thePids, List theList) { - if (theList == null || theList.isEmpty()) { - return thePids; - } + private void addPredicateToken(String theParamName, List theList) { if (Boolean.TRUE.equals(theList.get(0).getMissing())) { - return addPredicateParamMissing(thePids, "myParamsToken", theParamName, ResourceIndexedSearchParamToken.class); + addPredicateParamMissing("myParamsToken", theParamName, ResourceIndexedSearchParamToken.class); + return; } CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); @@ -854,7 +811,8 @@ public class SearchBuilder { if (nextOr instanceof TokenParam) { TokenParam id = (TokenParam) nextOr; if (id.isText()) { - return addPredicateString(theParamName, thePids, theList); + addPredicateString(theParamName, theList); + continue; } } @@ -862,28 +820,26 @@ public class SearchBuilder { codePredicates.add(singleCode); } - Predicate masterCodePredicate = builder.or(toArray(codePredicates)); - - Predicate type = builder.equal(from.get("myResourceType"), myResourceName); - Predicate name = builder.equal(from.get("myParamName"), theParamName); - if (thePids.size() > 0) { - Predicate inPids = (from.get("myResourcePid").in(thePids)); - cq.where(builder.and(type, name, masterCodePredicate, inPids)); - } else { - cq.where(builder.and(type, name, masterCodePredicate)); + if (codePredicates.isEmpty()) { + return; } + + List predicates = new ArrayList(); + predicates.add(builder.equal(from.get("myResourceType"), myResourceName)); + predicates.add(builder.equal(from.get("myParamName"), theParamName)); + predicates.add(builder.or(toArray(codePredicates))); + doCreateIdPredicate(predicates, from.get("myResourcePid").as(Long.class)); + + cq.where(builder.and(toArray(predicates))); TypedQuery q = myEntityManager.createQuery(cq); - return new HashSet(q.getResultList()); + doSetPids(new HashSet(q.getResultList())); } - private Set addPredicateUri(String theParamName, Set thePids, List theList) { - if (theList == null || theList.isEmpty()) { - return thePids; - } - + private void addPredicateUri(String theParamName, List theList) { if (Boolean.TRUE.equals(theList.get(0).getMissing())) { - return addPredicateParamMissing(thePids, "myParamsUri", theParamName, ResourceIndexedSearchParamUri.class); + addPredicateParamMissing("myParamsUri", theParamName, ResourceIndexedSearchParamUri.class); + return; } CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); @@ -904,7 +860,7 @@ public class SearchBuilder { String value = param.getValue(); if (value == null) { - return thePids; + continue; } Path fromObj = from.get("myUri"); @@ -954,22 +910,20 @@ public class SearchBuilder { } if (codePredicates.isEmpty()) { - return new HashSet(); + doSetPids(new HashSet()); + return; } - Predicate masterCodePredicate = builder.or(toArray(codePredicates)); - - Predicate type = builder.equal(from.get("myResourceType"), myResourceName); - Predicate name = builder.equal(from.get("myParamName"), theParamName); - if (thePids.size() > 0) { - Predicate inPids = (from.get("myResourcePid").in(thePids)); - cq.where(builder.and(type, name, masterCodePredicate, inPids)); - } else { - cq.where(builder.and(type, name, masterCodePredicate)); - } + List predicates = new ArrayList(); + predicates.add(builder.equal(from.get("myResourceType"), myResourceName)); + predicates.add(builder.equal(from.get("myParamName"), theParamName)); + predicates.add(builder.or(toArray(codePredicates))); + doCreateIdPredicate(predicates, from.get("myResourcePid").as(Long.class)); + + cq.where(builder.and(toArray(predicates))); TypedQuery q = myEntityManager.createQuery(cq); - return new HashSet(q.getResultList()); + doSetPids(new HashSet(q.getResultList())); } private Predicate createCompositeParamPart(CriteriaBuilder builder, Root from, RuntimeSearchParam left, IQueryParameterType leftValue) { @@ -1111,10 +1065,6 @@ public class SearchBuilder { return num; } - // private Set addPredicateComposite(String theParamName, Set thePids, List theList) { - // } - private Predicate createPredicateString(IQueryParameterType theParameter, String theParamName, CriteriaBuilder theBuilder, From theFrom) { String rawSearchTerm; if (theParameter instanceof TokenParam) { @@ -1317,17 +1267,34 @@ public class SearchBuilder { createSort(theBuilder, theFrom, theSort.getChain(), theOrders, thePredicates); } - private List filterResourceIdsByLastUpdated(Collection thePids, final DateRangeParam theLastUpdated) { + private void filterResourceIdsByLastUpdated(final DateRangeParam theLastUpdated) { CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); CriteriaQuery cq = builder.createQuery(Long.class); Root from = cq.from(ResourceTable.class); cq.select(from.get("myId").as(Long.class)); List lastUpdatedPredicates = createLastUpdatedPredicates(theLastUpdated, builder, from); - lastUpdatedPredicates.add(0, from.get("myId").in(thePids)); - + doCreateIdPredicate(lastUpdatedPredicates, from.get("myId").as(Long.class)); + cq.where(SearchBuilder.toArray(lastUpdatedPredicates)); TypedQuery query = myEntityManager.createQuery(cq); + + List resultList = query.getResultList(); + doSetPids(resultList); + } + + private List filterResourceIdsByLastUpdated(final DateRangeParam theLastUpdated, Collection thePids) { + CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); + CriteriaQuery cq = builder.createQuery(Long.class); + Root from = cq.from(ResourceTable.class); + cq.select(from.get("myId").as(Long.class)); + + List lastUpdatedPredicates = createLastUpdatedPredicates(theLastUpdated, builder, from); + lastUpdatedPredicates.add(from.get("myId").as(Long.class).in(thePids)); + + cq.where(SearchBuilder.toArray(lastUpdatedPredicates)); + TypedQuery query = myEntityManager.createQuery(cq); + List resultList = query.getResultList(); return resultList; } @@ -1484,7 +1451,7 @@ public class SearchBuilder { } if (theLastUpdated != null && (theLastUpdated.getLowerBoundAsInstant() != null || theLastUpdated.getUpperBoundAsInstant() != null)) { - pidsToInclude = new HashSet(filterResourceIdsByLastUpdated(pidsToInclude, theLastUpdated)); + pidsToInclude = new HashSet(filterResourceIdsByLastUpdated(theLastUpdated, pidsToInclude)); } for (Long next : pidsToInclude) { if (original.contains(next) == false && allAdded.contains(next) == false) { @@ -1503,8 +1470,8 @@ public class SearchBuilder { return allAdded; } - private List processSort(final SearchParameterMap theParams, Collection theLoadPids) { - final List pids; + private void processSort(final SearchParameterMap theParams) { + // Set loadPids = theLoadPids; if (theParams.getSort() != null && isNotBlank(theParams.getSort().getParamName())) { List orders = new ArrayList(); @@ -1512,10 +1479,16 @@ public class SearchBuilder { CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); CriteriaQuery cq = builder.createTupleQuery(); Root from = cq.from(ResourceTable.class); - predicates.add(from.get("myId").in(theLoadPids)); + + doCreateIdPredicate(predicates, from.get("myId").as(Long.class)); + createSort(builder, from, theParams.getSort(), orders, predicates); + if (orders.size() > 0) { - Collection originalPids = theLoadPids; + + // TODO: why do we need the existing list for this join to work? + Collection originalPids = myPids; + LinkedHashSet loadPids = new LinkedHashSet(); cq.multiselect(from.get("myId").as(Long.class)); cq.where(toArray(predicates)); @@ -1529,7 +1502,7 @@ public class SearchBuilder { ourLog.debug("Sort PID order is now: {}", loadPids); - pids = new ArrayList(loadPids); + ArrayList pids = new ArrayList(loadPids); // Any ressources which weren't matched by the sort get added to the bottom for (Long next : originalPids) { @@ -1538,25 +1511,24 @@ public class SearchBuilder { } } - } else { - pids = toList(theLoadPids); - } - } else { - pids = toList(theLoadPids); + doSetPids(pids); + } } - return pids; + } public IBundleProvider search(final SearchParameterMap theParams) { + myParams = theParams; StopWatch w = new StopWatch(); - final InstantDt now = InstantDt.withCurrentTime(); + doInitializeSearch(); + DateRangeParam lu = theParams.getLastUpdated(); if (lu != null && lu.isEmpty()) { lu = null; } - Collection loadPids; +// Collection loadPids; if (theParams.getEverythingMode() != null) { Long pid = null; @@ -1565,13 +1537,14 @@ public class SearchBuilder { pid = BaseHapiFhirDao.translateForcedIdToPid(new IdDt(idParm.getValue()), myEntityManager); } - loadPids = new HashSet(); if (theParams.containsKey(Constants.PARAM_CONTENT) || theParams.containsKey(Constants.PARAM_TEXT)) { List pids = mySearchDao.everything(myResourceName, theParams); - // if (pid != null) { - // loadPids.add(pid); - // } - loadPids.addAll(pids); + if (pids.isEmpty()) { + return doReturnProvider(); + } + + doSetPids(pids); + } else { CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); CriteriaQuery cq = builder.createTupleQuery(); @@ -1588,49 +1561,55 @@ public class SearchBuilder { cq.multiselect(from.get("myId").as(Long.class), join.get("mySourceResourcePid").as(Long.class)); TypedQuery query = myEntityManager.createQuery(cq); + Set pids = new HashSet(); for (Tuple next : query.getResultList()) { - loadPids.add(next.get(0, Long.class)); + pids.add(next.get(0, Long.class)); Long nextLong = next.get(1, Long.class); if (nextLong != null) { - loadPids.add(nextLong); + pids.add(nextLong); } } + doSetPids(pids); + } } else if (theParams.isEmpty()) { - loadPids = new HashSet(); TypedQuery query = createSearchAllByTypeQuery(lu); lu = null; - for (Tuple next : query.getResultList()) { + List resultList = query.getResultList(); + if (resultList.isEmpty()) { + return doReturnProvider(); + } + + Set loadPids = new HashSet(); + for (Tuple next : resultList) { loadPids.add(next.get(0, Long.class)); } - if (loadPids.isEmpty()) { - return new SimpleBundleProvider(); - } + doSetPids(loadPids); } else { - List searchResultPids; if (mySearchDao == null) { if (theParams.containsKey(Constants.PARAM_TEXT)) { throw new InvalidRequestException("Fulltext search is not enabled on this service, can not process parameter: " + Constants.PARAM_TEXT); } else if (theParams.containsKey(Constants.PARAM_CONTENT)) { throw new InvalidRequestException("Fulltext search is not enabled on this service, can not process parameter: " + Constants.PARAM_CONTENT); } - searchResultPids = null; } else { - searchResultPids = mySearchDao.search(myResourceName, theParams); + List searchResultPids = mySearchDao.search(myResourceName, theParams); + if (searchResultPids != null) { + if (searchResultPids.isEmpty()) { + return doReturnProvider(); + } + doSetPids(searchResultPids); + } } - if (theParams.isEmpty()) { - loadPids = searchResultPids; - } else { - loadPids = searchForIdsWithAndOr(theParams, searchResultPids, lu); + + if (!theParams.isEmpty()) { + searchForIdsWithAndOr(theParams, lu); } - if (loadPids.isEmpty()) { - return new SimpleBundleProvider(); - } - + } // // Load _include and _revinclude before filter and sort in everything mode @@ -1642,82 +1621,118 @@ public class SearchBuilder { // } // } + if (doHaveNoResults()) { + return doReturnProvider(); + } + // Handle _lastUpdated if (lu != null) { - List resultList = filterResourceIdsByLastUpdated(loadPids, lu); - loadPids.clear(); - for (Long next : resultList) { - loadPids.add(next); - } + filterResourceIdsByLastUpdated(lu); - if (loadPids.isEmpty()) { - return new SimpleBundleProvider(); + if (doHaveNoResults()) { + return doReturnProvider(); } } + // Handle sorting if any was provided - final List pids = processSort(theParams, loadPids); - - // Load _revinclude resources - final Set revIncludedPids; - if (theParams.getEverythingMode() == null) { - if (theParams.getRevIncludes() != null && theParams.getRevIncludes().isEmpty() == false) { - revIncludedPids = loadReverseIncludes(pids, theParams.getRevIncludes(), true, lu); - } else { - revIncludedPids = new HashSet(); - } - } else { - revIncludedPids = new HashSet(); - } - - ourLog.debug("Search returned PIDs: {}", pids); - - final int totalCount = pids.size(); - - IBundleProvider retVal = new IBundleProvider() { - @Override - public InstantDt getPublished() { - return now; - } - - @Override - public List getResources(final int theFromIndex, final int theToIndex) { - TransactionTemplate template = new TransactionTemplate(myPlatformTransactionManager); - return template.execute(new TransactionCallback>() { - @Override - public List doInTransaction(TransactionStatus theStatus) { - List pidsSubList = pids.subList(theFromIndex, theToIndex); - - // Load includes - pidsSubList = new ArrayList(pidsSubList); - revIncludedPids.addAll(loadReverseIncludes(pidsSubList, theParams.getIncludes(), false, theParams.getLastUpdated())); - - // Execute the query and make sure we return distinct results - List resources = new ArrayList(); - loadResourcesByPid(pidsSubList, resources, revIncludedPids, false); - - return resources; - } - - }); - } - - @Override - public Integer preferredPageSize() { - return theParams.getCount(); - } - - @Override - public int size() { - return totalCount; - } - }; + processSort(theParams); ourLog.info(" {} on {} in {}ms", new Object[] { myResourceName, theParams, w.getMillisAndRestart() }); - return retVal; + return doReturnProvider(); } - public Set searchForIdsWithAndOr(SearchParameterMap theParams, Collection theInitialPids, DateRangeParam theLastUpdated) { + private IBundleProvider doReturnProvider() { + if (myPids == null) { + return new SimpleBundleProvider(); + } else { + final ArrayList pids; + if (!(myPids instanceof List)) { + pids = new ArrayList(myPids); + } else { + pids = (ArrayList) myPids; + } + +// // Load _revinclude resources +// final Set revIncludedPids; +// if (myParams.getEverythingMode() == null) { +// if (myParams.getRevIncludes() != null && myParams.getRevIncludes().isEmpty() == false) { +// revIncludedPids = loadReverseIncludes(pids, myParams.getRevIncludes(), true, myParams.getLastUpdated()); +// } else { +// revIncludedPids = new HashSet(); +// } +// } else { +// revIncludedPids = new HashSet(); +// } + + return new IBundleProvider() { + @Override + public InstantDt getPublished() { + return mySearchStarted; + } + + @Override + public List getResources(final int theFromIndex, final int theToIndex) { + TransactionTemplate template = new TransactionTemplate(myPlatformTransactionManager); + return template.execute(new TransactionCallback>() { + @Override + public List doInTransaction(TransactionStatus theStatus) { + List pidsSubList = pids.subList(theFromIndex, theToIndex); + + // Load includes + pidsSubList = new ArrayList(pidsSubList); + + Set revIncludedPids = new HashSet(); + if (myParams.getEverythingMode() == null) { + revIncludedPids.addAll(loadReverseIncludes(pidsSubList, myParams.getRevIncludes(), true, myParams.getLastUpdated())); + } + revIncludedPids.addAll(loadReverseIncludes(pidsSubList, myParams.getIncludes(), false, myParams.getLastUpdated())); + + // Execute the query and make sure we return distinct results + List resources = new ArrayList(); + loadResourcesByPid(pidsSubList, resources, revIncludedPids, false); + + return resources; + } + + }); + } + + @Override + public Integer preferredPageSize() { + return myParams.getCount(); + } + + @Override + public int size() { + return pids.size(); + } + }; + + } + } + + private Collection myPids; + + private InstantDt mySearchStarted; + + private void doSetPids(Collection thePids) { + myPids = thePids; + } + + private void doInitializeSearch() { + assert mySearchEntity == null; + + mySearchStarted = InstantDt.withCurrentTime(); + +// mySearchEntity = new Search(); +// mySearchEntity.setUuid(UUID.randomUUID().toString()); +// myEntityManager.persist(mySearchEntity); +// +// myHaveFlushedSearch = false; + } + + public void searchForIdsWithAndOr(SearchParameterMap theParams, DateRangeParam theLastUpdated) { SearchParameterMap params = theParams; if (params == null) { params = new SearchParameterMap(); @@ -1725,11 +1740,6 @@ public class SearchBuilder { RuntimeResourceDefinition resourceDef = myContext.getResourceDefinition(myResourceType); - Set pids = new HashSet(); - if (theInitialPids != null) { - pids.addAll(theInitialPids); - } - for (Entry>> nextParamEntry : params.entrySet()) { String nextParamName = nextParamEntry.getKey(); if (nextParamName.equals(BaseResource.SP_RES_ID)) { @@ -1757,30 +1767,25 @@ public class SearchBuilder { } } if (joinPids.isEmpty()) { - return new HashSet(); + doSetPids(new HashSet()); + return; } } - pids = addPredicateId(pids, joinPids, theLastUpdated); - if (pids.isEmpty()) { - return new HashSet(); - } - - if (pids.isEmpty()) { - pids.addAll(joinPids); - } else { - pids.retainAll(joinPids); + addPredicateId(joinPids, theLastUpdated); + if (doHaveNoResults()) { + return; } } } } else if (nextParamName.equals(BaseResource.SP_RES_LANGUAGE)) { - pids = addPredicateLanguage(pids, nextParamEntry.getValue(), theLastUpdated); + addPredicateLanguage(nextParamEntry.getValue(), theLastUpdated); } else if (nextParamName.equals(Constants.PARAM_TAG) || nextParamName.equals(Constants.PARAM_PROFILE) || nextParamName.equals(Constants.PARAM_SECURITY)) { - pids = addPredicateTag(pids, nextParamEntry.getValue(), nextParamName, theLastUpdated); + addPredicateTag(nextParamEntry.getValue(), nextParamName, theLastUpdated); } else { @@ -1789,74 +1794,82 @@ public class SearchBuilder { switch (nextParamDef.getParamType()) { case DATE: for (List nextAnd : nextParamEntry.getValue()) { - pids = addPredicateDate(nextParamName, pids, nextAnd); - if (pids.isEmpty()) { - return new HashSet(); + addPredicateDate(nextParamName, nextAnd); + if (doHaveNoResults()) { + return; } } break; case QUANTITY: for (List nextAnd : nextParamEntry.getValue()) { - pids = addPredicateQuantity(nextParamName, pids, nextAnd); - if (pids.isEmpty()) { - return new HashSet(); + addPredicateQuantity(nextParamName, nextAnd); + if (doHaveNoResults()) { + return; } } break; case REFERENCE: for (List nextAnd : nextParamEntry.getValue()) { - pids = addPredicateReference(nextParamName, pids, nextAnd); - if (pids.isEmpty()) { - return new HashSet(); + addPredicateReference(nextParamName, nextAnd); + if (doHaveNoResults()) { + return; } } break; case STRING: for (List nextAnd : nextParamEntry.getValue()) { - pids = addPredicateString(nextParamName, pids, nextAnd); - if (pids.isEmpty()) { - return new HashSet(); + addPredicateString(nextParamName, nextAnd); + if (doHaveNoResults()) { + return; } } break; case TOKEN: for (List nextAnd : nextParamEntry.getValue()) { - pids = addPredicateToken(nextParamName, pids, nextAnd); - if (pids.isEmpty()) { - return new HashSet(); + addPredicateToken(nextParamName, nextAnd); + if (doHaveNoResults()) { + return; } } break; case NUMBER: for (List nextAnd : nextParamEntry.getValue()) { - pids = addPredicateNumber(nextParamName, pids, nextAnd); - if (pids.isEmpty()) { - return new HashSet(); + addPredicateNumber(nextParamName, nextAnd); + if (doHaveNoResults()) { + return; } } break; case COMPOSITE: for (List nextAnd : nextParamEntry.getValue()) { - pids = addPredicateComposite(nextParamDef, pids, nextAnd); - if (pids.isEmpty()) { - return new HashSet(); + addPredicateComposite(nextParamDef, nextAnd); + if (doHaveNoResults()) { + return; } } break; case URI: for (List nextAnd : nextParamEntry.getValue()) { - pids = addPredicateUri(nextParamName, pids, nextAnd); - if (pids.isEmpty()) { - return new HashSet(); + addPredicateUri(nextParamName, nextAnd); + if (doHaveNoResults()) { + return; } } break; } } } + + if (doHaveNoResults()) { + return; + } + } - return pids; + } + + private boolean doHaveNoResults() { + return myPids != null && myPids.isEmpty(); } public void setType(Class theResourceType, String theResourceName) { @@ -1943,4 +1956,8 @@ public class SearchBuilder { return thePredicates.toArray(new Predicate[thePredicates.size()]); } + public Set doGetPids() { + return new HashSet(myPids); + } + } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/Search.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/Search.java index d5a6f135e94..6411e970cb3 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/Search.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/Search.java @@ -53,13 +53,13 @@ public class Search implements Serializable { @GeneratedValue(strategy = GenerationType.AUTO, generator="SEQ_SEARCH") @SequenceGenerator(name="SEQ_SEARCH", sequenceName="SEQ_SEARCH") - @Id @Column(name = "PID") private Long myId; @Column(name="TOTAL_COUNT") private int myTotalCount; + @Id @Column(name="SEARCH_UUID", length=40, nullable=false) private String myUuid; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TerminologySvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TerminologySvcImpl.java index 5e5a6702830..dbdd18201c5 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TerminologySvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TerminologySvcImpl.java @@ -4,10 +4,9 @@ import java.util.HashSet; import java.util.IdentityHashMap; import java.util.Set; -import javax.transaction.Transactional; -import javax.transaction.Transactional.TxType; - import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.ValidationUtils; import ca.uhn.fhir.context.FhirContext; @@ -44,7 +43,7 @@ public class TerminologySvcImpl implements ITerminologySvc { @Override - @Transactional(value=TxType.REQUIRED) + @Transactional(propagation=Propagation.REQUIRED) public void storeNewCodeSystemVersion(String theSystemUri, TermCodeSystemVersion theCodeSystem) { ourLog.info("Storing code system"); @@ -108,7 +107,7 @@ public class TerminologySvcImpl implements ITerminologySvc { theConceptsStack.remove(theConcept); } - @Transactional(value=TxType.REQUIRED) + @Transactional(propagation=Propagation.REQUIRED) @Override public Set findCodesBelow(Long theCodeSystemResourcePid, Long theCodeSystemVersionPid, String theCode) { TermCodeSystemVersion codeSystem = myCodeSystemVersionDao.findByCodeSystemResourceAndVersion(theCodeSystemResourcePid, theCodeSystemVersionPid); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2SearchNoFtTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2SearchNoFtTest.java index 8af3159e324..f63e0efea56 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2SearchNoFtTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2SearchNoFtTest.java @@ -395,6 +395,60 @@ public class FhirResourceDaoDstu2SearchNoFtTest extends BaseJpaDstu2Test { } + @SuppressWarnings("unused") + @Test + public void testSearchResourceReferenceMissing() { + IIdType oid1; + { + Organization org = new Organization(); + org.setName("ORG1"); + oid1 = myOrganizationDao.create(org, new ServletRequestDetails()).getId().toUnqualifiedVersionless(); + } + + IIdType pid1; + { + Patient patient = new Patient(); + patient.addName().addFamily("FAMILY1"); + pid1 = myPatientDao.create(patient, new ServletRequestDetails()).getId().toUnqualifiedVersionless(); + } + IIdType pid2; + { + Patient patient = new Patient(); + patient.addName().addFamily("FAMILY1"); + patient.getManagingOrganization().setReference(oid1); + pid2 = myPatientDao.create(patient, new ServletRequestDetails()).getId().toUnqualifiedVersionless(); + } + IIdType pid3; + { + Patient patient = new Patient(); + patient.addName().addFamily("FAMILY2"); + pid3 = myPatientDao.create(patient, new ServletRequestDetails()).getId().toUnqualifiedVersionless(); + } + IIdType pid4; + { + Patient patient = new Patient(); + patient.addName().addFamily("FAMILY2"); + patient.getManagingOrganization().setReference(oid1); + pid4 = myPatientDao.create(patient, new ServletRequestDetails()).getId().toUnqualifiedVersionless(); + } + + SearchParameterMap params; + + params = new SearchParameterMap(); + params.add(Patient.SP_NAME, new StringParam("FAMILY1")); + params.add(Patient.SP_ORGANIZATION, new ReferenceParam().setMissing(true)); + assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(params)), containsInAnyOrder(pid1)); + + params = new SearchParameterMap(); + params.add(Patient.SP_ORGANIZATION, new ReferenceParam().setMissing(true)); + assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(params)), containsInAnyOrder(pid1, pid3)); + + params = new SearchParameterMap(); + params.add(Patient.SP_NAME, new StringParam("FAMILY9999")); + params.add(Patient.SP_ORGANIZATION, new ReferenceParam().setMissing(true)); + assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(params)), empty()); + } + @Test public void testSearchByIdParamOr() { IIdType id1; @@ -1290,6 +1344,23 @@ public class FhirResourceDaoDstu2SearchNoFtTest extends BaseJpaDstu2Test { patient.addName().addFamily("Tester").addGiven("testSearchTokenParam2"); myPatientDao.create(patient, new ServletRequestDetails()); + { + SearchParameterMap map = new SearchParameterMap(); + map.add(Patient.SP_LANGUAGE, new TokenParam(null, "testSearchTokenParamCode", true)); + assertEquals(0, myPatientDao.search(map).size()); + } + { + SearchParameterMap map = new SearchParameterMap(); + map.add(Patient.SP_LANGUAGE, new TokenParam(null, "testSearchTokenParamCode", true)); + map.add(Patient.SP_IDENTIFIER, new IdentifierDt("urn:system", "testSearchTokenParam001")); + assertEquals(0, myPatientDao.search(map).size()); + } + { + SearchParameterMap map = new SearchParameterMap(); + map.add(Patient.SP_LANGUAGE, new TokenParam(null, "testSearchTokenParamComText", true)); + map.add(Patient.SP_IDENTIFIER, new IdentifierDt("urn:system", "testSearchTokenParam001")); + assertEquals(1, myPatientDao.search(map).size()); + } { SearchParameterMap map = new SearchParameterMap(); map.add(Patient.SP_IDENTIFIER, new IdentifierDt("urn:system", "testSearchTokenParam001")); @@ -1307,11 +1378,6 @@ public class FhirResourceDaoDstu2SearchNoFtTest extends BaseJpaDstu2Test { map.add(Patient.SP_LANGUAGE, new IdentifierDt("testSearchTokenParamSystem", "testSearchTokenParamCode")); assertEquals(1, myPatientDao.search(map).size()); } - { - SearchParameterMap map = new SearchParameterMap(); - map.add(Patient.SP_LANGUAGE, new TokenParam(null, "testSearchTokenParamCode", true)); - assertEquals(0, myPatientDao.search(map).size()); - } { // Complete match SearchParameterMap map = new SearchParameterMap(); @@ -1896,15 +1962,6 @@ public class FhirResourceDaoDstu2SearchNoFtTest extends BaseJpaDstu2Test { notMissing = myPatientDao.create(patient, new ServletRequestDetails()).getId().toUnqualifiedVersionless(); } // String Param - { - HashMap params = new HashMap(); - StringParam param = new StringParam(); - param.setMissing(false); - params.put(Patient.SP_FAMILY, param); - List patients = toUnqualifiedVersionlessIds(myPatientDao.search(params)); - assertThat(patients, not(containsInRelativeOrder(missing))); - assertThat(patients, containsInRelativeOrder(notMissing)); - } { Map params = new HashMap(); StringParam param = new StringParam(); @@ -1914,8 +1971,47 @@ public class FhirResourceDaoDstu2SearchNoFtTest extends BaseJpaDstu2Test { assertThat(patients, containsInRelativeOrder(missing)); assertThat(patients, not(containsInRelativeOrder(notMissing))); } + { + HashMap params = new HashMap(); + StringParam param = new StringParam(); + param.setMissing(false); + params.put(Patient.SP_FAMILY, param); + List patients = toUnqualifiedVersionlessIds(myPatientDao.search(params)); + assertThat(patients, not(containsInRelativeOrder(missing))); + assertThat(patients, containsInRelativeOrder(notMissing)); + } } + /** + * See #310 + */ + @Test + @Ignore + public void testSearchMultiMatches() { + + for (int i = 0; i < 100; i++) { + Practitioner p = new Practitioner(); + p.addAddress().addLine("FOO"); + IIdType pid = myPractitionerDao.create(p, mySrd).getId().toUnqualifiedVersionless(); + + Patient pt = new Patient(); + pt.addCareProvider().setReference(pid); + IIdType ptid = myPatientDao.create(pt, mySrd).getId().toUnqualifiedVersionless(); + + Observation obs = new Observation(); + obs.setSubject(new ResourceReferenceDt(ptid)); + myObservationDao.create(obs, mySrd); + } + + SearchParameterMap map = new SearchParameterMap(); + map.addInclude(new Include("Patient:careprovider")); + map.addRevInclude(new Include("Observation:patient")); + + myPatientDao.search(map); + + } + + @Test public void testSearchWithNoResults() { Device dev = new Device(); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2Test.java index 3e430822d0d..8101cd676eb 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2Test.java @@ -2186,7 +2186,7 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test { map.add(Organization.SP_NAME, new StringParam("X" + methodName + "X")); map.setRevIncludes(Collections.singleton(Patient.INCLUDE_ORGANIZATION)); IBundleProvider resultsP = myOrganizationDao.search(map); - assertEquals(2, resultsP.size()); + assertEquals(1, resultsP.size()); List results = resultsP.getResources(0, resultsP.size()); assertEquals(2, results.size()); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3Test.java index dae0984d07c..face966346f 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3Test.java @@ -2250,7 +2250,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test { map.add(Organization.SP_NAME, new StringParam("X" + methodName + "X")); map.setRevIncludes(Collections.singleton(Patient.INCLUDE_ORGANIZATION)); IBundleProvider resultsP = myOrganizationDao.search(map); - assertEquals(2, resultsP.size()); + assertEquals(1, resultsP.size()); List results = resultsP.getResources(0, resultsP.size()); assertEquals(2, results.size()); diff --git a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/testmodel/IdentifierDt.java b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/testmodel/IdentifierDt.java index b3a32d08ebf..1e0d1a80941 100644 --- a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/testmodel/IdentifierDt.java +++ b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/testmodel/IdentifierDt.java @@ -424,7 +424,7 @@ public class IdentifierDt */ @Deprecated @Override - public void setMissing(Boolean theMissing) { + public IQueryParameterType setMissing(Boolean theMissing) { throw new UnsupportedOperationException("get/setMissing is not supported in StringDt. Use {@link StringParam} instead if you need this functionality"); } diff --git a/pom.xml b/pom.xml index 6c86e76ede7..8012bb44495 100644 --- a/pom.xml +++ b/pom.xml @@ -846,6 +846,10 @@ org.apache.maven.scm maven-scm-api + + org.apache.maven.doxia + doxia-core + org.apache.maven.doxia doxia-module-markdown @@ -1263,6 +1267,11 @@ maven-scm-api 1.9.4 + + org.apache.maven.doxia + doxia-core + 1.7 + org.apache.maven.doxia doxia-module-markdown diff --git a/src/site/site.xml b/src/site/site.xml index 37b1bf3d54f..6537101dd2a 100644 --- a/src/site/site.xml +++ b/src/site/site.xml @@ -32,14 +32,13 @@ + - - - - - - +