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 72ec0251f27..ce96ce90e49 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 @@ -26,7 +26,6 @@ import ca.uhn.fhir.context.RuntimeSearchParam; import ca.uhn.fhir.interceptor.api.HookParams; import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; import ca.uhn.fhir.interceptor.api.Pointcut; -import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamUriDao; import ca.uhn.fhir.jpa.dao.data.IResourceSearchViewDao; import ca.uhn.fhir.jpa.dao.data.IResourceTagDao; import ca.uhn.fhir.jpa.dao.index.IdHelperService; @@ -38,10 +37,8 @@ import ca.uhn.fhir.jpa.model.entity.*; import ca.uhn.fhir.jpa.model.search.SearchRuntimeDetails; import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage; import ca.uhn.fhir.jpa.searchparam.JpaRuntimeSearchParam; -import ca.uhn.fhir.jpa.searchparam.MatchUrlService; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; -import ca.uhn.fhir.jpa.term.api.ITermReadSvc; import ca.uhn.fhir.jpa.util.*; import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.model.api.IResource; @@ -130,12 +127,9 @@ public class SearchBuilder implements ISearchBuilder { private List myAlsoIncludePids; private CriteriaBuilder myBuilder; private BaseHapiFhirDao myCallingDao; - private IndexJoins myIndexJoins = new IndexJoins(); private SearchParameterMap myParams; - private ArrayList myPredicates; private String myResourceName; private AbstractQuery myResourceTableQuery; - private Root myResourceTableRoot; private Class myResourceType; private String mySearchUuid; private int myFetchSize; @@ -143,6 +137,7 @@ public class SearchBuilder implements ISearchBuilder { private Set myPidSet; private boolean myHaveIndexJoins = false; private PredicateBuilder myPredicateBuilder; + private final QueryRoot myQueryRoot = new QueryRoot(); /** * Constructor @@ -227,8 +222,6 @@ public class SearchBuilder implements ISearchBuilder { } private TypedQuery createQuery(SortSpec sort, Integer theMaximumResults, boolean theCount, RequestDetails theRequest) { - myPredicates = new ArrayList<>(); - CriteriaQuery outerQuery; /* * Sort @@ -241,17 +234,16 @@ public class SearchBuilder implements ISearchBuilder { outerQuery = myBuilder.createQuery(Long.class); myResourceTableQuery = outerQuery; - myResourceTableRoot = myResourceTableQuery.from(ResourceTable.class); + myQueryRoot.push(myResourceTableQuery.from(ResourceTable.class)); if (theCount) { - outerQuery.multiselect(myBuilder.countDistinct(myResourceTableRoot)); + outerQuery.multiselect(myBuilder.countDistinct(myQueryRoot.getRoot())); } else { - outerQuery.multiselect(myResourceTableRoot.get("myId").as(Long.class)); + outerQuery.multiselect(myQueryRoot.get("myId").as(Long.class)); } List orders = Lists.newArrayList(); - List predicates = myPredicates; // Lists.newArrayList(); - createSort(myBuilder, myResourceTableRoot, sort, orders, predicates); + createSort(myBuilder, myQueryRoot, sort, orders); if (orders.size() > 0) { outerQuery.orderBy(orders); } @@ -260,18 +252,18 @@ public class SearchBuilder implements ISearchBuilder { outerQuery = myBuilder.createQuery(Long.class); myResourceTableQuery = outerQuery; - myResourceTableRoot = myResourceTableQuery.from(ResourceTable.class); + myQueryRoot.push(myResourceTableQuery.from(ResourceTable.class)); if (theCount) { - outerQuery.multiselect(myBuilder.countDistinct(myResourceTableRoot)); + outerQuery.multiselect(myBuilder.countDistinct(myQueryRoot.getRoot())); } else { - outerQuery.multiselect(myResourceTableRoot.get("myId").as(Long.class)); + outerQuery.multiselect(myQueryRoot.get("myId").as(Long.class)); outerQuery.distinct(true); } } if (myParams.getEverythingMode() != null) { - Join join = myResourceTableRoot.join("myResourceLinks", JoinType.LEFT); + Join join = myQueryRoot.join("myResourceLinks", JoinType.LEFT); if (myParams.get(IAnyResource.SP_RES_ID) != null) { StringParam idParm = (StringParam) myParams.get(IAnyResource.SP_RES_ID).get(0).get(0); @@ -280,11 +272,11 @@ public class SearchBuilder implements ISearchBuilder { myAlsoIncludePids = new ArrayList<>(1); } myAlsoIncludePids.add(pid); - myPredicates.add(myBuilder.equal(join.get("myTargetResourcePid").as(Long.class), pid.getIdAsLong())); + myQueryRoot.addPredicate(myBuilder.equal(join.get("myTargetResourcePid").as(Long.class), pid.getIdAsLong())); } else { Predicate targetTypePredicate = myBuilder.equal(join.get("myTargetResourceType").as(String.class), myResourceName); - Predicate sourceTypePredicate = myBuilder.equal(myResourceTableRoot.get("myResourceType").as(String.class), myResourceName); - myPredicates.add(myBuilder.or(sourceTypePredicate, targetTypePredicate)); + Predicate sourceTypePredicate = myBuilder.equal(myQueryRoot.get("myResourceType").as(String.class), myResourceName); + myQueryRoot.addPredicate(myBuilder.or(sourceTypePredicate, targetTypePredicate)); } } else { @@ -315,7 +307,7 @@ public class SearchBuilder implements ISearchBuilder { pids = Collections.singletonList(new ResourcePersistentId(-1L)); } - myPredicates.add(myResourceTableRoot.get("myId").as(Long.class).in(ResourcePersistentId.toLongList(pids))); + myQueryRoot.addPredicate(myQueryRoot.get("myId").as(Long.class).in(ResourcePersistentId.toLongList(pids))); } /* @@ -327,17 +319,17 @@ public class SearchBuilder implements ISearchBuilder { */ if (!myHaveIndexJoins) { if (myParams.getEverythingMode() == null) { - myPredicates.add(myBuilder.equal(myResourceTableRoot.get("myResourceType"), myResourceName)); + myQueryRoot.addPredicate(myBuilder.equal(myQueryRoot.get("myResourceType"), myResourceName)); } - myPredicates.add(myBuilder.isNull(myResourceTableRoot.get("myDeleted"))); + myQueryRoot.addPredicate(myBuilder.isNull(myQueryRoot.get("myDeleted"))); } // Last updated DateRangeParam lu = myParams.getLastUpdated(); - List lastUpdatedPredicates = createLastUpdatedPredicates(lu, myBuilder, myResourceTableRoot); - myPredicates.addAll(lastUpdatedPredicates); + List lastUpdatedPredicates = createLastUpdatedPredicates(lu, myBuilder, myQueryRoot.getRoot()); + myQueryRoot.addPredicates(lastUpdatedPredicates); - myResourceTableQuery.where(myBuilder.and(SearchBuilder.toArray(myPredicates))); + myResourceTableQuery.where(myBuilder.and(myQueryRoot.getPredicateArray())); /* * Now perform the search @@ -355,32 +347,32 @@ 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, Root theFrom, SortSpec theSort, List theOrders, List thePredicates) { + private boolean createSort(CriteriaBuilder theBuilder, QueryRoot theQueryRoot, SortSpec theSort, List theOrders) { if (theSort == null || isBlank(theSort.getParamName())) { return false; } if (IAnyResource.SP_RES_ID.equals(theSort.getParamName())) { - From forcedIdJoin = theFrom.join("myForcedId", JoinType.LEFT); + From forcedIdJoin = theQueryRoot.join("myForcedId", JoinType.LEFT); if (theSort.getOrder() == null || theSort.getOrder() == SortOrderEnum.ASC) { theOrders.add(theBuilder.asc(forcedIdJoin.get("myForcedId"))); - theOrders.add(theBuilder.asc(theFrom.get("myId"))); + theOrders.add(theBuilder.asc(theQueryRoot.get("myId"))); } else { theOrders.add(theBuilder.desc(forcedIdJoin.get("myForcedId"))); - theOrders.add(theBuilder.desc(theFrom.get("myId"))); + theOrders.add(theBuilder.desc(theQueryRoot.get("myId"))); } - return createSort(theBuilder, theFrom, theSort.getChain(), theOrders, thePredicates); + return createSort(theBuilder, theQueryRoot, theSort.getChain(), theOrders); } if (Constants.PARAM_LASTUPDATED.equals(theSort.getParamName())) { if (theSort.getOrder() == null || theSort.getOrder() == SortOrderEnum.ASC) { - theOrders.add(theBuilder.asc(theFrom.get("myUpdated"))); + theOrders.add(theBuilder.asc(theQueryRoot.get("myUpdated"))); } else { - theOrders.add(theBuilder.desc(theFrom.get("myUpdated"))); + theOrders.add(theBuilder.desc(theQueryRoot.get("myUpdated"))); } - return createSort(theBuilder, theFrom, theSort.getChain(), theOrders, thePredicates); + return createSort(theBuilder, theQueryRoot, theSort.getChain(), theOrders); } RuntimeResourceDefinition resourceDef = myContext.getResourceDefinition(myResourceName); @@ -441,20 +433,20 @@ 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 = myIndexJoins.get(key); + Join join = theQueryRoot.getIndexJoin(key); if (join == null) { - join = theFrom.join(joinAttrName, JoinType.LEFT); + join = theQueryRoot.join(joinAttrName, JoinType.LEFT); if (param.getParamType() == RestSearchParameterTypeEnum.REFERENCE) { - thePredicates.add(join.get("mySourcePath").as(String.class).in(param.getPathsSplit())); + theQueryRoot.addPredicate(join.get("mySourcePath").as(String.class).in(param.getPathsSplit())); } else { if (myDontUseHashesForSearch) { Predicate joinParam1 = theBuilder.equal(join.get("myParamName"), theSort.getParamName()); - thePredicates.add(joinParam1); + theQueryRoot.addPredicate(joinParam1); } else { Long hashIdentity = BaseResourceIndexedSearchParam.calculateHashIdentity(myResourceName, theSort.getParamName()); Predicate joinParam1 = theBuilder.equal(join.get("myHashIdentity"), hashIdentity); - thePredicates.add(joinParam1); + theQueryRoot.addPredicate(joinParam1); } } } else { @@ -469,7 +461,7 @@ public class SearchBuilder implements ISearchBuilder { } } - createSort(theBuilder, theFrom, theSort.getChain(), theOrders, thePredicates); + createSort(theBuilder, theQueryRoot, theSort.getChain(), theOrders); return true; } @@ -863,9 +855,9 @@ public class SearchBuilder implements ISearchBuilder { private void addPredicateCompositeStringUnique(@Nonnull SearchParameterMap theParams, String theIndexdString) { myHaveIndexJoins = true; - Join join = myResourceTableRoot.join("myParamsCompositeStringUnique", JoinType.LEFT); + Join join = myQueryRoot.join("myParamsCompositeStringUnique", JoinType.LEFT); Predicate predicate = myBuilder.equal(join.get("myIndexString"), theIndexdString); - myPredicates.add(predicate); + myQueryRoot.addPredicate(predicate); // Remove any empty parameters remaining after this theParams.clean(); @@ -901,16 +893,8 @@ public class SearchBuilder implements ISearchBuilder { return myBuilder; } - public Root getResourceTableRoot() { - return myResourceTableRoot; - } - - public IndexJoins getIndexJoins() { - return myIndexJoins; - } - - public ArrayList getPredicates() { - return myPredicates; + public QueryRoot getQueryRoot() { + return myQueryRoot; } public AbstractQuery getResourceTableQuery() { @@ -1220,13 +1204,13 @@ public class SearchBuilder implements ISearchBuilder { List lastUpdatedPredicates = createLastUpdatedPredicates(theLastUpdated, builder, from); lastUpdatedPredicates.add(from.get("myId").as(Long.class).in(ResourcePersistentId.toLongList(thePids))); - cq.where(SearchBuilder.toArray(lastUpdatedPredicates)); + cq.where(SearchBuilder.toPredicateArray(lastUpdatedPredicates)); TypedQuery query = theEntityManager.createQuery(cq); return ResourcePersistentId.fromLongList(query.getResultList()); } - private static Predicate[] toArray(List thePredicates) { + private static Predicate[] toPredicateArray(List thePredicates) { return thePredicates.toArray(new Predicate[0]); } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/BasePredicateBuilder.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/BasePredicateBuilder.java index 86a7281d916..f0689df948d 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/BasePredicateBuilder.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/BasePredicateBuilder.java @@ -34,9 +34,7 @@ abstract class BasePredicateBuilder { boolean myDontUseHashesForSearch; final BaseHapiFhirDao myCallingDao; final CriteriaBuilder myBuilder; - final Root myResourceTableRoot; - final IndexJoins myIndexJoins; - final ArrayList myPredicates; + final QueryRoot myQueryRoot; final Class myResourceType; final String myResourceName; final AbstractQuery myResourceTableQuery; @@ -46,9 +44,7 @@ abstract class BasePredicateBuilder { BasePredicateBuilder(SearchBuilder theSearchBuilder) { myCallingDao = theSearchBuilder.getCallingDao(); myBuilder = theSearchBuilder.getBuilder(); - myResourceTableRoot = theSearchBuilder.getResourceTableRoot(); - myIndexJoins = theSearchBuilder.getIndexJoins(); - myPredicates = theSearchBuilder.getPredicates(); + myQueryRoot = theSearchBuilder.getQueryRoot(); myResourceType = theSearchBuilder.getResourceType(); myResourceName = theSearchBuilder.getResourceName(); myResourceTableQuery = theSearchBuilder.getResourceTableQuery(); @@ -65,59 +61,59 @@ abstract class BasePredicateBuilder { Join join = null; switch (theType) { case DATE: - join = myResourceTableRoot.join("myParamsDate", JoinType.LEFT); + join = myQueryRoot.join("myParamsDate", JoinType.LEFT); break; case NUMBER: - join = myResourceTableRoot.join("myParamsNumber", JoinType.LEFT); + join = myQueryRoot.join("myParamsNumber", JoinType.LEFT); break; case QUANTITY: - join = myResourceTableRoot.join("myParamsQuantity", JoinType.LEFT); + join = myQueryRoot.join("myParamsQuantity", JoinType.LEFT); break; case REFERENCE: - join = myResourceTableRoot.join("myResourceLinks", JoinType.LEFT); + join = myQueryRoot.join("myResourceLinks", JoinType.LEFT); break; case STRING: - join = myResourceTableRoot.join("myParamsString", JoinType.LEFT); + join = myQueryRoot.join("myParamsString", JoinType.LEFT); break; case URI: - join = myResourceTableRoot.join("myParamsUri", JoinType.LEFT); + join = myQueryRoot.join("myParamsUri", JoinType.LEFT); break; case TOKEN: - join = myResourceTableRoot.join("myParamsToken", JoinType.LEFT); + join = myQueryRoot.join("myParamsToken", JoinType.LEFT); break; case COORDS: - join = myResourceTableRoot.join("myParamsCoords", JoinType.LEFT); + join = myQueryRoot.join("myParamsCoords", JoinType.LEFT); break; } SearchBuilderJoinKey key = new SearchBuilderJoinKey(theSearchParameterName, theType); - myIndexJoins.put(key, join); + myQueryRoot.putIndex(key, join); return (Join) join; } void addPredicateParamMissing(String theResourceName, String theParamName, boolean theMissing) { // if (myDontUseHashesForSearch) { -// Join paramPresentJoin = myResourceTableRoot.join("mySearchParamPresents", JoinType.LEFT); +// Join paramPresentJoin = myQueryRoot.join("mySearchParamPresents", JoinType.LEFT); // Join paramJoin = paramPresentJoin.join("mySearchParam", JoinType.LEFT); // -// myPredicates.add(myBuilder.equal(paramJoin.get("myResourceName"), theResourceName)); -// myPredicates.add(myBuilder.equal(paramJoin.get("myParamName"), theParamName)); -// myPredicates.add(myBuilder.equal(paramPresentJoin.get("myPresent"), !theMissing)); +// myQueryRoot.addPredicate(myBuilder.equal(paramJoin.get("myResourceName"), theResourceName)); +// myQueryRoot.addPredicate(myBuilder.equal(paramJoin.get("myParamName"), theParamName)); +// myQueryRoot.addPredicate(myBuilder.equal(paramPresentJoin.get("myPresent"), !theMissing)); // } - Join paramPresentJoin = myResourceTableRoot.join("mySearchParamPresents", JoinType.LEFT); + Join paramPresentJoin = myQueryRoot.join("mySearchParamPresents", JoinType.LEFT); Expression hashPresence = paramPresentJoin.get("myHashPresence").as(Long.class); Long hash = SearchParamPresent.calculateHashPresence(theResourceName, theParamName, !theMissing); - myPredicates.add(myBuilder.equal(hashPresence, hash)); + myQueryRoot.addPredicate(myBuilder.equal(hashPresence, hash)); } void addPredicateParamMissing(String theResourceName, String theParamName, boolean theMissing, Join theJoin) { - myPredicates.add(myBuilder.equal(theJoin.get("myResourceType"), theResourceName)); - myPredicates.add(myBuilder.equal(theJoin.get("myParamName"), theParamName)); - myPredicates.add(myBuilder.equal(theJoin.get("myMissing"), theMissing)); + myQueryRoot.addPredicate(myBuilder.equal(theJoin.get("myResourceType"), theResourceName)); + myQueryRoot.addPredicate(myBuilder.equal(theJoin.get("myParamName"), theParamName)); + myQueryRoot.addPredicate(myBuilder.equal(theJoin.get("myMissing"), theMissing)); } Predicate combineParamIndexPredicateWithParamNamePredicate(String theResourceName, String theParamName, From theFrom, Predicate thePredicate) { @@ -133,7 +129,6 @@ abstract class BasePredicateBuilder { return myBuilder.and(hashIdentityPredicate, thePredicate); } - Predicate createPredicateNumeric(String theResourceName, String theParamName, From theFrom, @@ -166,7 +161,7 @@ abstract class BasePredicateBuilder { num = builder.notEqual(thePath, theValue); break; case APPROXIMATE: - BigDecimal mul = calculateFuzzAmount(thePrefix, theValue); + BigDecimal mul = FuzzCalculator.calculateFuzzAmount(thePrefix, theValue); BigDecimal low = theValue.subtract(mul, MathContext.DECIMAL64); BigDecimal high = theValue.add(mul, MathContext.DECIMAL64); Predicate lowPred; @@ -189,28 +184,6 @@ abstract class BasePredicateBuilder { return combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, num); } - /** - * Figures out the tolerance for a search. For example, if the user is searching for 4.00, this method - * returns 0.005 because we shold actually match values which are - * 4 (+/-) 0.005 according to the FHIR specs. - */ - static BigDecimal calculateFuzzAmount(ParamPrefixEnum cmpValue, BigDecimal theValue) { - if (cmpValue == ParamPrefixEnum.APPROXIMATE) { - return theValue.multiply(new BigDecimal(0.1)); - } else { - String plainString = theValue.toPlainString(); - int dotIdx = plainString.indexOf('.'); - if (dotIdx == -1) { - return new BigDecimal(0.5); - } - - int precision = plainString.length() - (dotIdx); - double mul = Math.pow(10, -precision); - double val = mul * 5.0d; - return new BigDecimal(val); - } - } - static String createLeftAndRightMatchLikeExpression(String likeExpression) { return "%" + likeExpression.replace("%", "[%]") + "%"; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/FuzzCalculator.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/FuzzCalculator.java new file mode 100644 index 00000000000..5ad21b22ace --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/FuzzCalculator.java @@ -0,0 +1,29 @@ +package ca.uhn.fhir.jpa.dao.predicate; + +import ca.uhn.fhir.rest.param.ParamPrefixEnum; + +import java.math.BigDecimal; + +public interface FuzzCalculator { + /** + * Figures out the tolerance for a search. For example, if the user is searching for 4.00, this method + * returns 0.005 because we shold actually match values which are + * 4 (+/-) 0.005 according to the FHIR specs. + */ + static BigDecimal calculateFuzzAmount(ParamPrefixEnum cmpValue, BigDecimal theValue) { + if (cmpValue == ParamPrefixEnum.APPROXIMATE) { + return theValue.multiply(new BigDecimal(0.1)); + } else { + String plainString = theValue.toPlainString(); + int dotIdx = plainString.indexOf('.'); + if (dotIdx == -1) { + return new BigDecimal(0.5); + } + + int precision = plainString.length() - (dotIdx); + double mul = Math.pow(10, -precision); + double val = mul * 5.0d; + return new BigDecimal(val); + } + } +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderCoords.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderCoords.java index a04b2f65b82..5bd78b4bd2f 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderCoords.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderCoords.java @@ -129,7 +129,7 @@ public class PredicateBuilderCoords extends BasePredicateBuilder { } Predicate retVal = myBuilder.or(toArray(codePredicates)); - myPredicates.add(retVal); + myQueryRoot.addPredicate(retVal); return retVal; } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderDate.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderDate.java index 238c9a3349e..16438f6153e 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderDate.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderDate.java @@ -52,12 +52,10 @@ public class PredicateBuilderDate extends BasePredicateBuilder { } Predicate orPredicates = myBuilder.or(toArray(codePredicates)); - myPredicates.add(orPredicates); + myQueryRoot.addPredicate(orPredicates); return orPredicates; } - - public Predicate createPredicateDate(IQueryParameterType theParam, String theResourceName, String theParamName, diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderNumber.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderNumber.java index 6d4d4028073..08049df8c4c 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderNumber.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderNumber.java @@ -78,7 +78,7 @@ public class PredicateBuilderNumber extends BasePredicateBuilder { } Predicate predicate = myBuilder.or(toArray(codePredicates)); - myPredicates.add(predicate); + myQueryRoot.addPredicate(predicate); return predicate; } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderQuantity.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderQuantity.java index e4034666334..3c102337133 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderQuantity.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderQuantity.java @@ -47,7 +47,7 @@ public class PredicateBuilderQuantity extends BasePredicateBuilder { } Predicate retVal = myBuilder.or(toArray(codePredicates)); - myPredicates.add(retVal); + myQueryRoot.addPredicate(retVal); return retVal; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderReference.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderReference.java index 1c985b51982..6b3b09e25cf 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderReference.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderReference.java @@ -161,13 +161,13 @@ public class PredicateBuilderReference extends BasePredicateBuilder { if (codePredicates.size() > 0) { Predicate predicate = myBuilder.or(toArray(codePredicates)); - myPredicates.add(predicate); + myQueryRoot.addPredicate(predicate); return predicate; } else { // Add a predicate that will never match Predicate pidPredicate = join.get("myTargetResourcePid").in(-1L); - myPredicates.clear(); - myPredicates.add(pidPredicate); + myQueryRoot.clearPredicates(); + myQueryRoot.addPredicate(pidPredicate); return pidPredicate; } } @@ -305,7 +305,7 @@ public class PredicateBuilderReference extends BasePredicateBuilder { } Predicate predicate = myBuilder.or(toArray(theCodePredicates)); - myPredicates.add(predicate); + myQueryRoot.addPredicate(predicate); return predicate; } @@ -372,29 +372,22 @@ public class PredicateBuilderReference extends BasePredicateBuilder { * stack and run a subquery */ - Root stackRoot = myResourceTableRoot; - ArrayList stackPredicates = myPredicates; - IndexJoins stackIndexJoins = myIndexJoins; - - myResourceTableRoot = subQfrom; - myPredicates = Lists.newArrayList(); - myIndexJoins = new IndexJoins(); + myQueryRoot.push(subQfrom); + // FIXME KHS stack in all predicates // Create the subquery predicates - myPredicates.add(myBuilder.equal(myResourceTableRoot.get("myResourceType"), theSubResourceName)); - myPredicates.add(myBuilder.isNull(myResourceTableRoot.get("myDeleted"))); + myQueryRoot.addPredicate(myBuilder.equal(myQueryRoot.get("myResourceType"), theSubResourceName)); + myQueryRoot.addPredicate(myBuilder.isNull(myQueryRoot.get("myDeleted"))); if (theFoundChainMatch) { searchForIdsWithAndOr(theSubResourceName, theChain, andOrParams, theRequest); - subQ.where(toArray(myPredicates)); + subQ.where(myQueryRoot.getPredicateArray()); } /* * Pop the old query root and predicate list back */ - myResourceTableRoot = stackRoot; - myPredicates = stackPredicates; - myIndexJoins = stackIndexJoins; + myQueryRoot.pop(); return subQ; } @@ -508,12 +501,13 @@ public 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 holdPredicates = new ArrayList<>(myPredicates); + // FIXME KHS + ArrayList holdPredicates = new ArrayList<>(myQueryRoot.getPredicates()); Predicate filterPredicate = processFilter(filter, theResourceName, theRequest); - myPredicates.clear(); - myPredicates.addAll(holdPredicates); - myPredicates.add(filterPredicate); + myQueryRoot.clearPredicates(); + myQueryRoot.addPredicates(holdPredicates); + myQueryRoot.addPredicate(filterPredicate); } } @@ -705,13 +699,13 @@ public class PredicateBuilderReference extends BasePredicateBuilder { Predicate predicate = null; if ((operation == null) || (operation == SearchFilterParser.CompareOperation.eq)) { - predicate = myResourceTableRoot.get("myLanguage").as(String.class).in(values); + predicate = myQueryRoot.get("myLanguage").as(String.class).in(values); } else if (operation == SearchFilterParser.CompareOperation.ne) { - predicate = myResourceTableRoot.get("myLanguage").as(String.class).in(values).not(); + predicate = myQueryRoot.get("myLanguage").as(String.class).in(values).not(); } else { throw new InvalidRequestException("Unsupported operator specified in language query, only \"eq\" and \"ne\" are supported"); } - myPredicates.add(predicate); + myQueryRoot.addPredicate(predicate); if (operation != null) { return predicate; } @@ -732,7 +726,7 @@ public class PredicateBuilderReference extends BasePredicateBuilder { throw new InvalidRequestException(msg); } - Join join = myResourceTableRoot.join("myProvenance", JoinType.LEFT); + Join join = myQueryRoot.join("myProvenance", JoinType.LEFT); List codePredicates = new ArrayList<>(); @@ -752,7 +746,7 @@ public class PredicateBuilderReference extends BasePredicateBuilder { } Predicate retVal = myBuilder.or(toArray(codePredicates)); - myPredicates.add(retVal); + myQueryRoot.addPredicate(retVal); return retVal; } @@ -809,11 +803,11 @@ public class PredicateBuilderReference extends BasePredicateBuilder { Subquery subQ = myPredicateBuilder.createLinkSubquery(true, parameterName, targetResourceType, orValues, theRequest); - Join join = myResourceTableRoot.join("myResourceLinksAsTarget", JoinType.LEFT); + Join join = myQueryRoot.join("myResourceLinksAsTarget", JoinType.LEFT); Predicate pathPredicate = myPredicateBuilder.createResourceLinkPathPredicate(targetResourceType, paramReference, join); Predicate pidPredicate = join.get("mySourceResourcePid").in(subQ); Predicate andPredicate = myBuilder.and(pathPredicate, pidPredicate); - myPredicates.add(andPredicate); + myQueryRoot.addPredicate(andPredicate); } } @@ -828,11 +822,11 @@ public class PredicateBuilderReference extends BasePredicateBuilder { RuntimeSearchParam left = theParamDef.getCompositeOf().get(0); IQueryParameterType leftValue = cp.getLeftValue(); - myPredicates.add(createCompositeParamPart(theResourceName, myResourceTableRoot, left, leftValue)); + myQueryRoot.addPredicate(createCompositeParamPart(theResourceName, myQueryRoot.getRoot(), left, leftValue)); RuntimeSearchParam right = theParamDef.getCompositeOf().get(1); IQueryParameterType rightValue = cp.getRightValue(); - myPredicates.add(createCompositeParamPart(theResourceName, myResourceTableRoot, right, rightValue)); + myQueryRoot.addPredicate(createCompositeParamPart(theResourceName, myQueryRoot.getRoot(), right, rightValue)); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderResourceId.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderResourceId.java index 0627b2baf02..14988b7fda5 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderResourceId.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderResourceId.java @@ -35,10 +35,10 @@ public class PredicateBuilderResourceId extends BasePredicateBuilder { Predicate addPredicateResourceId(List> theValues, String theResourceName, SearchFilterParser.CompareOperation theOperation, RequestDetails theRequest) { - Predicate nextPredicate = createPredicateResourceId(myResourceTableRoot, theResourceName, theValues, theOperation, theRequest); + Predicate nextPredicate = createPredicateResourceId(myQueryRoot.getRoot(), theResourceName, theValues, theOperation, theRequest); if (nextPredicate != null) { - myPredicates.add(nextPredicate); + myQueryRoot.addPredicate(nextPredicate); return nextPredicate; } @@ -96,12 +96,12 @@ public class PredicateBuilderResourceId extends BasePredicateBuilder { default: case eq: codePredicates.add(theRoot.get("myId").as(Long.class).in(ResourcePersistentId.toLongList(allOrPids))); - codePredicates.add(myBuilder.equal(myResourceTableRoot.get("myResourceType"), theResourceName)); + codePredicates.add(myBuilder.equal(myQueryRoot.get("myResourceType"), theResourceName)); nextPredicate = myBuilder.and(toArray(codePredicates)); break; case ne: codePredicates.add(theRoot.get("myId").as(Long.class).in(ResourcePersistentId.toLongList(allOrPids)).not()); - codePredicates.add(myBuilder.equal(myResourceTableRoot.get("myResourceType"), theResourceName)); + codePredicates.add(myBuilder.equal(myQueryRoot.get("myResourceType"), theResourceName)); nextPredicate = myBuilder.and(toArray(codePredicates)); break; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderString.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderString.java index bb0d08be47c..d4c148cb1b7 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderString.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderString.java @@ -49,7 +49,7 @@ public class PredicateBuilderString extends BasePredicateBuilder { } Predicate retVal = myBuilder.or(toArray(codePredicates)); - myPredicates.add(retVal); + myQueryRoot.addPredicate(retVal); return retVal; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderTag.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderTag.java index 3c853285165..e5d3db0771c 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderTag.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderTag.java @@ -135,7 +135,13 @@ public class PredicateBuilderTag extends BasePredicateBuilder { Root subQfrom = subQ.from(ResourceTag.class); subQ.select(subQfrom.get("myResourceId").as(Long.class)); - myPredicates.add(myBuilder.not(myBuilder.in(myResourceTableRoot.get("myId")).value(subQ))); + myQueryRoot.addPredicate( + myBuilder.not( + myBuilder.in( + myQueryRoot.get("myId") + ).value(subQ) + ) + ); Subquery defJoin = subQ.subquery(Long.class); Root defJoinFrom = defJoin.from(TagDefinition.class); @@ -149,11 +155,11 @@ public class PredicateBuilderTag extends BasePredicateBuilder { continue; } - Join tagJoin = myResourceTableRoot.join("myTags", JoinType.LEFT); + Join tagJoin = myQueryRoot.join("myTags", JoinType.LEFT); From defJoin = tagJoin.join("myTag"); Predicate tagListPredicate = createPredicateTagList(defJoin, myBuilder, tagType, tokens); - myPredicates.add(tagListPredicate); + myQueryRoot.addPredicate(tagListPredicate); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderToken.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderToken.java index 77c83810b47..9731df3c082 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderToken.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderToken.java @@ -75,7 +75,7 @@ public class PredicateBuilderToken extends BasePredicateBuilder { codePredicates.addAll(singleCode); Predicate spPredicate = myBuilder.or(toArray(codePredicates)); - myPredicates.add(spPredicate); + myQueryRoot.addPredicate(spPredicate); return spPredicate; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderUri.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderUri.java index 3f9022f7c41..d0ead348e43 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderUri.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderUri.java @@ -140,7 +140,7 @@ public class PredicateBuilderUri extends BasePredicateBuilder { */ if (codePredicates.isEmpty()) { Predicate predicate = myBuilder.isNull(join.get("myMissing").as(String.class)); - myPredicates.add(predicate); + myQueryRoot.addPredicate(predicate); return null; } @@ -150,7 +150,7 @@ public class PredicateBuilderUri extends BasePredicateBuilder { theParamName, join, orPredicate); - myPredicates.add(outerPredicate); + myQueryRoot.addPredicate(outerPredicate); return outerPredicate; } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/QueryRoot.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/QueryRoot.java new file mode 100644 index 00000000000..ae01d24a80e --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/QueryRoot.java @@ -0,0 +1,65 @@ +package ca.uhn.fhir.jpa.dao.predicate; + +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 myQueryRootStack = new Stack<>(); + + public void push(Root theResourceTableRoot) { + myQueryRootStack.push(new QueryRootEntry(theResourceTableRoot)); + } + + private QueryRootEntry top() { + return myQueryRootStack.peek(); + } + + void pop() { + myQueryRootStack.pop(); + } + + public Root getRoot() { + return top().getRoot(); + } + + public Path get(String theAttributeName) { + return top().get(theAttributeName); + } + + public Join 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 thePredicates) { + top().addPredicates(thePredicates); + } + + public Predicate[] getPredicateArray() { + return top().getPredicateArray(); + } + + void putIndex(SearchBuilderJoinKey theKey, Join theJoin) { + top().putIndex(theKey, theJoin); + } + + void clearPredicates() { + top().clearPredicates(); + } + + // FIXME KHS don't leak + List getPredicates() { + return top().getPredicates(); + } +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/QueryRootEntry.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/QueryRootEntry.java new file mode 100644 index 00000000000..f0a84adbb23 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/QueryRootEntry.java @@ -0,0 +1,59 @@ +package ca.uhn.fhir.jpa.dao.predicate; + +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.List; + +public class QueryRootEntry { + private final Root myResourceTableRoot; + private final ArrayList myPredicates = new ArrayList<>(); + private final IndexJoins myIndexJoins = new IndexJoins(); + + public QueryRootEntry(Root theResourceTableRoot) { + myResourceTableRoot = theResourceTableRoot; + } + + public Root getRoot() { + return myResourceTableRoot; + } + + public Path get(String theAttributeName) { + return myResourceTableRoot.get(theAttributeName); + } + + public Join 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 thePredicates) { + myPredicates.addAll(thePredicates); + } + + public Predicate[] getPredicateArray() { + return myPredicates.toArray(new Predicate[0]); + } + + void putIndex(SearchBuilderJoinKey theKey, Join theJoin) { + myIndexJoins.put(theKey, theJoin); + } + + void clearPredicates() { + myPredicates.clear(); + } + + // FIXME KHS don't leak + List getPredicates() { + return myPredicates; + } +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/SearchBuilderTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/SearchBuilderTest.java index 10708c8a82d..96d27cb2899 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/SearchBuilderTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/SearchBuilderTest.java @@ -4,107 +4,25 @@ import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.model.entity.ResourceLink; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; -import ca.uhn.fhir.rest.param.ParamPrefixEnum; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnitRunner; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import javax.persistence.EntityManager; import javax.persistence.TypedQuery; -import java.math.BigDecimal; -import java.math.MathContext; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; -import static org.hamcrest.Matchers.startsWith; -import static org.junit.Assert.*; +import static org.junit.Assert.assertFalse; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class SearchBuilderTest { - private static final Logger ourLog = LoggerFactory.getLogger(SearchBuilderTest.class); - @Test - public void testCalculateMultiplierEqualNoDecimal() { - BigDecimal in = new BigDecimal("200"); - BigDecimal out = SearchBuilder.calculateFuzzAmount(ParamPrefixEnum.EQUAL, in); - ourLog.info(out.toPlainString()); - assertEquals("0.5", out.toPlainString()); - } - - @Test - public void testCalculateMultiplierEqualDecimalPrecision200_() { - BigDecimal in = new BigDecimal("200."); - BigDecimal out = SearchBuilder.calculateFuzzAmount(ParamPrefixEnum.EQUAL, in); - ourLog.info(out.toPlainString()); - assertEquals("0.5", out.toPlainString()); - } - - @Test - public void testCalculateMultiplierEqualDecimalPrecision123_010() { - BigDecimal in = new BigDecimal("123.010"); - BigDecimal out = SearchBuilder.calculateFuzzAmount(ParamPrefixEnum.EQUAL, in); - ourLog.info(out.toPlainString()); - assertThat(out.toPlainString(), startsWith("0.0005")); - - BigDecimal low = in.subtract(out, MathContext.DECIMAL64); - BigDecimal high = in.add(out, MathContext.DECIMAL64); - ourLog.info("{} <= {} <= {}", new Object[] {low.toPlainString(), in.toPlainString(), high.toPlainString()}); - } - - @Test - public void testCalculateMultiplierEqualDecimalPrecision200_0() { - BigDecimal in = new BigDecimal("200.0"); - BigDecimal out = SearchBuilder.calculateFuzzAmount(ParamPrefixEnum.EQUAL, in); - ourLog.info(out.toPlainString()); - assertThat(out.toPlainString(), startsWith("0.05000000")); - } - - @Test - public void testCalculateMultiplierEqualDecimalPrecision200_3() { - BigDecimal in = new BigDecimal("200.3"); - BigDecimal out = SearchBuilder.calculateFuzzAmount(ParamPrefixEnum.EQUAL, in); - ourLog.info(out.toPlainString()); - assertThat(out.toPlainString(), startsWith("0.05000000")); - } - - @Test - public void testCalculateMultiplierEqualDecimalPrecision200_300() { - BigDecimal in = new BigDecimal("200.300"); - BigDecimal out = SearchBuilder.calculateFuzzAmount(ParamPrefixEnum.EQUAL, in); - ourLog.info(out.toPlainString()); - assertThat(out.toPlainString(), startsWith("0.0005000000")); - } - - @Test - public void testCalculateMultiplierEqualDecimalPrecision200_30000000() { - BigDecimal in = new BigDecimal("200.30000000"); - BigDecimal out = SearchBuilder.calculateFuzzAmount(ParamPrefixEnum.EQUAL, in); - ourLog.info(out.toPlainString()); - assertThat(out.toPlainString(), startsWith("0.000000005000000")); - } - - @Test - public void testCalculateMultiplierEqualDecimalPrecision200_300000001() { - BigDecimal in = new BigDecimal("200.300000001"); - BigDecimal out = SearchBuilder.calculateFuzzAmount(ParamPrefixEnum.EQUAL, in); - ourLog.info(out.toPlainString()); - assertThat(out.toPlainString(), startsWith("0.0000000005000000")); - } - - @Test - public void testCalculateMultiplierApprox() { - BigDecimal in = new BigDecimal("200"); - BigDecimal out = SearchBuilder.calculateFuzzAmount(ParamPrefixEnum.APPROXIMATE, in); - ourLog.info(out.toPlainString()); - assertThat(out.toPlainString(), startsWith("20.000")); - } @Test public void testIncludeIterator() { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/predicate/FuzzCalculatorTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/predicate/FuzzCalculatorTest.java new file mode 100644 index 00000000000..7adea928342 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/predicate/FuzzCalculatorTest.java @@ -0,0 +1,94 @@ +package ca.uhn.fhir.jpa.dao.predicate; + +import ca.uhn.fhir.jpa.dao.SearchBuilder; +import ca.uhn.fhir.jpa.dao.SearchBuilderTest; +import ca.uhn.fhir.rest.param.ParamPrefixEnum; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.math.BigDecimal; +import java.math.MathContext; + +import static org.hamcrest.Matchers.startsWith; +import static org.junit.Assert.*; + +public class FuzzCalculatorTest { + private static final Logger ourLog = LoggerFactory.getLogger(SearchBuilderTest.class); + + @Test + public void testCalculateMultiplierEqualNoDecimal() { + BigDecimal in = new BigDecimal("200"); + BigDecimal out = FuzzCalculator.calculateFuzzAmount(ParamPrefixEnum.EQUAL, in); + ourLog.info(out.toPlainString()); + assertEquals("0.5", out.toPlainString()); + } + + @Test + public void testCalculateMultiplierEqualDecimalPrecision200_() { + BigDecimal in = new BigDecimal("200."); + BigDecimal out = FuzzCalculator.calculateFuzzAmount(ParamPrefixEnum.EQUAL, in); + ourLog.info(out.toPlainString()); + assertEquals("0.5", out.toPlainString()); + } + + @Test + public void testCalculateMultiplierEqualDecimalPrecision123_010() { + BigDecimal in = new BigDecimal("123.010"); + BigDecimal out = FuzzCalculator.calculateFuzzAmount(ParamPrefixEnum.EQUAL, in); + ourLog.info(out.toPlainString()); + assertThat(out.toPlainString(), startsWith("0.0005")); + + BigDecimal low = in.subtract(out, MathContext.DECIMAL64); + BigDecimal high = in.add(out, MathContext.DECIMAL64); + ourLog.info("{} <= {} <= {}", new Object[] {low.toPlainString(), in.toPlainString(), high.toPlainString()}); + } + + @Test + public void testCalculateMultiplierEqualDecimalPrecision200_0() { + BigDecimal in = new BigDecimal("200.0"); + BigDecimal out = FuzzCalculator.calculateFuzzAmount(ParamPrefixEnum.EQUAL, in); + ourLog.info(out.toPlainString()); + assertThat(out.toPlainString(), startsWith("0.05000000")); + } + + @Test + public void testCalculateMultiplierEqualDecimalPrecision200_3() { + BigDecimal in = new BigDecimal("200.3"); + BigDecimal out = FuzzCalculator.calculateFuzzAmount(ParamPrefixEnum.EQUAL, in); + ourLog.info(out.toPlainString()); + assertThat(out.toPlainString(), startsWith("0.05000000")); + } + + @Test + public void testCalculateMultiplierEqualDecimalPrecision200_300() { + BigDecimal in = new BigDecimal("200.300"); + BigDecimal out = FuzzCalculator.calculateFuzzAmount(ParamPrefixEnum.EQUAL, in); + ourLog.info(out.toPlainString()); + assertThat(out.toPlainString(), startsWith("0.0005000000")); + } + + @Test + public void testCalculateMultiplierEqualDecimalPrecision200_30000000() { + BigDecimal in = new BigDecimal("200.30000000"); + BigDecimal out = FuzzCalculator.calculateFuzzAmount(ParamPrefixEnum.EQUAL, in); + ourLog.info(out.toPlainString()); + assertThat(out.toPlainString(), startsWith("0.000000005000000")); + } + + @Test + public void testCalculateMultiplierEqualDecimalPrecision200_300000001() { + BigDecimal in = new BigDecimal("200.300000001"); + BigDecimal out = FuzzCalculator.calculateFuzzAmount(ParamPrefixEnum.EQUAL, in); + ourLog.info(out.toPlainString()); + assertThat(out.toPlainString(), startsWith("0.0000000005000000")); + } + + @Test + public void testCalculateMultiplierApprox() { + BigDecimal in = new BigDecimal("200"); + BigDecimal out = FuzzCalculator.calculateFuzzAmount(ParamPrefixEnum.APPROXIMATE, in); + ourLog.info(out.toPlainString()); + assertThat(out.toPlainString(), startsWith("20.000")); + } +}