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 43c194050f6..0cc3aa7cab7 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 @@ -545,16 +545,20 @@ public class SearchBuilder implements ISearchBuilder { List targetPids = myIdHelperService.translateForcedIdToPids(targetIds, theRequest); if (!targetPids.isEmpty()) { ourLog.debug("Searching for resource link with target PIDs: {}", targetPids); - Predicate pathPredicate = createResourceLinkPathPredicate(theResourceName, theParamName, join); - Predicate pidPredicate = join.get("myTargetResourcePid").in(targetPids); + Predicate pathPredicate = ((operation == null) || (operation == SearchFilterParser.CompareOperation.eq)) ? createResourceLinkPathPredicate(theResourceName, theParamName, join) + : createResourceLinkPathPredicate(theResourceName, theParamName, join).not(); + Predicate pidPredicate = ((operation == null) || (operation == SearchFilterParser.CompareOperation.eq)) ? join.get("myTargetResourcePid").in(targetPids) + : join.get("myTargetResourcePid").in(targetPids).not(); codePredicates.add(myBuilder.and(pathPredicate, pidPredicate)); } // Resources by fully qualified URL if (!targetQualifiedUrls.isEmpty()) { ourLog.debug("Searching for resource link with target URLs: {}", targetQualifiedUrls); - Predicate pathPredicate = createResourceLinkPathPredicate(theResourceName, theParamName, join); - Predicate pidPredicate = join.get("myTargetResourceUrl").in(targetQualifiedUrls); + Predicate pathPredicate = ((operation == null) || (operation == SearchFilterParser.CompareOperation.eq)) ? createResourceLinkPathPredicate(theResourceName, theParamName, join) + : createResourceLinkPathPredicate(theResourceName, theParamName, join).not(); + Predicate pidPredicate = ((operation == null) || (operation == SearchFilterParser.CompareOperation.eq)) ? join.get("myTargetResourceUrl").in(targetQualifiedUrls) + : join.get("myTargetResourceUrl").in(targetQualifiedUrls).not(); codePredicates.add(myBuilder.and(pathPredicate, pidPredicate)); } @@ -1717,35 +1721,39 @@ public class SearchBuilder implements ISearchBuilder { boolean exactMatch = theParameter instanceof StringParam && ((StringParam) theParameter).isExact(); if (exactMatch) { - // Exact match - Long hash = ResourceIndexedSearchParamString.calculateHashExact(theResourceName, theParamName, rawSearchTerm); return theBuilder.equal(theFrom.get("myHashExact").as(Long.class), hash); - } else { - // Normalized Match - String normalizedString = StringNormalizer.normalizeString(rawSearchTerm); String likeExpression; - if (theParameter instanceof StringParam && - ((StringParam) theParameter).isContains() && - myDaoConfig.isAllowContainsSearches()) { + if ((theParameter instanceof StringParam) && + (((((StringParam) theParameter).isContains()) && + (myCallingDao.getConfig().isAllowContainsSearches())) || + (operation == SearchFilterParser.CompareOperation.co))) { likeExpression = createLeftAndRightMatchLikeExpression(normalizedString); + } else if ((operation != SearchFilterParser.CompareOperation.ne) && + (operation != SearchFilterParser.CompareOperation.gt) && + (operation != SearchFilterParser.CompareOperation.lt) && + (operation != SearchFilterParser.CompareOperation.ge) && + (operation != SearchFilterParser.CompareOperation.le)) { + if (operation == SearchFilterParser.CompareOperation.ew) { + likeExpression = createRightMatchLikeExpression(normalizedString); + } else { + likeExpression = createLeftMatchLikeExpression(normalizedString); + } } else { - likeExpression = createLeftMatchLikeExpression(normalizedString); + likeExpression = normalizedString; } Predicate predicate; if ((operation == null) || (operation == SearchFilterParser.CompareOperation.sw) || - (operation == SearchFilterParser.CompareOperation.ew)) { - - Long hash = ResourceIndexedSearchParamString.calculateHashNormalized(myDaoConfig.getModelConfig(), theResourceName, theParamName, normalizedString); - Predicate hashCode = theBuilder.equal(theFrom.get("myHashNormalizedPrefix").as(Long.class), hash); + (operation == SearchFilterParser.CompareOperation.ew) || + (operation == SearchFilterParser.CompareOperation.co)) { Predicate singleCode = theBuilder.like(theFrom.get("myValueNormalized").as(String.class), likeExpression); - predicate = theBuilder.and(hashCode, singleCode); + predicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, singleCode); } else if (operation == SearchFilterParser.CompareOperation.eq) { Long hash = ResourceIndexedSearchParamString.calculateHashNormalized(myDaoConfig.getModelConfig(), theResourceName, theParamName, normalizedString); Predicate hashCode = theBuilder.equal(theFrom.get("myHashNormalizedPrefix").as(Long.class), hash); @@ -1857,11 +1865,9 @@ public class SearchBuilder implements ISearchBuilder { codes.addAll(myTerminologySvc.expandValueSet(code)); } else if (modifier == TokenParamModifier.ABOVE) { system = determineSystemIfMissing(theParamName, code, system); - validateHaveSystemAndCodeForToken(theParamName, code, system); codes.addAll(myTerminologySvc.findCodesAbove(system, code)); } else if (modifier == TokenParamModifier.BELOW) { system = determineSystemIfMissing(theParamName, code, system); - validateHaveSystemAndCodeForToken(theParamName, code, system); codes.addAll(myTerminologySvc.findCodesBelow(system, code)); } else { codes.add(new VersionIndependentConcept(system, code)); @@ -1904,19 +1910,6 @@ public class SearchBuilder implements ISearchBuilder { return retVal; } - private void validateHaveSystemAndCodeForToken(String theParamName, String theCode, String theSystem) { - String systemDesc = defaultIfBlank(theSystem, "(missing)"); - String codeDesc = defaultIfBlank(theCode, "(missing)"); - if (isBlank(theCode)) { - String msg = myContext.getLocalizer().getMessage(SearchBuilder.class, "invalidCodeMissingSystem", theParamName, systemDesc, codeDesc); - throw new InvalidRequestException(msg); - } - if (isBlank(theSystem)) { - String msg = myContext.getLocalizer().getMessage(SearchBuilder.class, "invalidCodeMissingCode", theParamName, systemDesc, codeDesc); - throw new InvalidRequestException(msg); - } - } - private Predicate addPredicateToken(String theResourceName, String theParamName, CriteriaBuilder theBuilder, From theFrom, List theTokens, TokenParamModifier theModifier, TokenModeEnum theTokenMode) { if (myDontUseHashesForSearch) { final Path systemExpression = theFrom.get("mySystem"); @@ -2071,7 +2064,6 @@ public class SearchBuilder implements ISearchBuilder { outerQuery.multiselect(myBuilder.countDistinct(myResourceTableRoot)); } else { outerQuery.multiselect(myResourceTableRoot.get("myId").as(Long.class)); - outerQuery.distinct(true); } } @@ -3141,8 +3133,6 @@ public class SearchBuilder implements ISearchBuilder { private final SearchRuntimeDetails mySearchRuntimeDetails; private final RequestDetails myRequest; - private final boolean myHaveRawSqlHooks; - private final boolean myHavePerftraceFoundIdHook; private boolean myFirst = true; private IncludesIterator myIncludesIterator; private Long myNext; @@ -3161,16 +3151,13 @@ public class SearchBuilder implements ISearchBuilder { if (myParams.getEverythingMode() != null) { myStillNeedToFetchIncludes = true; } - - myHavePerftraceFoundIdHook =JpaInterceptorBroadcaster.hasHooks(Pointcut.JPA_PERFTRACE_SEARCH_FOUND_ID, myInterceptorBroadcaster, myRequest); - myHaveRawSqlHooks = JpaInterceptorBroadcaster.hasHooks(Pointcut.JPA_PERFTRACE_RAW_SQL, myInterceptorBroadcaster, myRequest); - } private void fetchNext() { + boolean haveRawSqlHooks = JpaInterceptorBroadcaster.hasHooks(Pointcut.JPA_PERFTRACE_RAW_SQL, myInterceptorBroadcaster, myRequest); try { - if (myHaveRawSqlHooks) { + if (haveRawSqlHooks) { CurrentThreadCaptureQueriesListener.startCapturing(); } @@ -3211,13 +3198,6 @@ public class SearchBuilder implements ISearchBuilder { if (myNext == null) { while (myResultsIterator.hasNext()) { Long next = myResultsIterator.next(); - if (myHavePerftraceFoundIdHook) { - HookParams params = new HookParams() - .add(Integer.class, System.identityHashCode(this)) - .add(Object.class, next); - JpaInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, myRequest, Pointcut.JPA_PERFTRACE_SEARCH_FOUND_ID, params); - } - if (next != null) { if (myPidSet.add(next)) { myNext = next; @@ -3256,7 +3236,7 @@ public class SearchBuilder implements ISearchBuilder { mySearchRuntimeDetails.setFoundMatchesCount(myPidSet.size()); } finally { - if (myHaveRawSqlHooks) { + if (haveRawSqlHooks) { SqlQueryList capturedQueries = CurrentThreadCaptureQueriesListener.getCurrentQueueAndStopCapturing(); HookParams params = new HookParams() .add(RequestDetails.class, myRequest) diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4FilterTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4FilterTest.java index a7d3b665bb8..ff616b60cda 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4FilterTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4FilterTest.java @@ -204,6 +204,210 @@ public class FhirResourceDaoR4FilterTest extends BaseJpaR4Test { } + @Test + public void testStringComparatorNe() { + + Patient p = new Patient(); + p.addName().setFamily("Smith").addGiven("John"); + p.setActive(true); + String id1 = myPatientDao.create(p).getId().toUnqualifiedVersionless().getValue(); + + p = new Patient(); + p.addName().setFamily("Jones").addGiven("Frank"); + p.setActive(false); + String id2 = myPatientDao.create(p).getId().toUnqualifiedVersionless().getValue(); + + SearchParameterMap map; + List found; + + map = new SearchParameterMap(); + map.setLoadSynchronous(true); + map.add(Constants.PARAM_FILTER, new StringParam("family ne smith")); + found = toUnqualifiedVersionlessIdValues(myPatientDao.search(map)); + assertThat(found, containsInAnyOrder(id2)); + assertThat(found, containsInAnyOrder(Matchers.not(id1))); + + map = new SearchParameterMap(); + map.setLoadSynchronous(true); + map.add(Constants.PARAM_FILTER, new StringParam("family ne jones")); + found = toUnqualifiedVersionlessIdValues(myPatientDao.search(map)); + assertThat(found, containsInAnyOrder(id1)); + assertThat(found, containsInAnyOrder(Matchers.not(id2))); + + map = new SearchParameterMap(); + map.setLoadSynchronous(true); + map.add(Constants.PARAM_FILTER, new StringParam("given ne john")); + found = toUnqualifiedVersionlessIdValues(myPatientDao.search(map)); + assertThat(found, containsInAnyOrder(id2)); + assertThat(found, containsInAnyOrder(Matchers.not(id1))); + + map = new SearchParameterMap(); + map.setLoadSynchronous(true); + map.add(Constants.PARAM_FILTER, new StringParam("given ne frank")); + found = toUnqualifiedVersionlessIdValues(myPatientDao.search(map)); + assertThat(found, containsInAnyOrder(id1)); + assertThat(found, containsInAnyOrder(Matchers.not(id2))); + + } + + @Test + public void testReferenceComparatorNe() { + + Patient p = new Patient(); + p.addName().setFamily("Smith").addGiven("John"); + p.setActive(true); + IIdType ptId = myPatientDao.create(p).getId().toUnqualifiedVersionless(); + + p = new Patient(); + p.addName().setFamily("Smith").addGiven("John2"); + p.setActive(true); + IIdType ptId2 = myPatientDao.create(p).getId().toUnqualifiedVersionless(); + + CarePlan cp = new CarePlan(); + cp.getSubject().setReference(ptId.getValue()); + String cpId = myCarePlanDao.create(cp).getId().toUnqualifiedVersionless().getValue(); + + cp = new CarePlan(); + cp.addActivity().getDetail().addPerformer().setReference(ptId2.getValue()); + String cpId2 = myCarePlanDao.create(cp).getId().toUnqualifiedVersionless().getValue(); + + SearchParameterMap map; + List found; + + map = new SearchParameterMap(); + map.setLoadSynchronous(true); + map.add(Constants.PARAM_FILTER, new StringParam("subject ne " + ptId.getValue())); + found = toUnqualifiedVersionlessIdValues(myCarePlanDao.search(map)); + assertThat(found, containsInAnyOrder(cpId2)); + + map = new SearchParameterMap(); + map.setLoadSynchronous(true); + map.add(Constants.PARAM_FILTER, new StringParam("subject ne " + ptId.getIdPart())); + found = toUnqualifiedVersionlessIdValues(myCarePlanDao.search(map)); + assertThat(found, containsInAnyOrder(cpId2)); + + map = new SearchParameterMap(); + map.setLoadSynchronous(true); + map.add(Constants.PARAM_FILTER, new StringParam("(subject ne " + ptId.getIdPart() + ") and (performer ne " + ptId2.getValue() + ")")); + found = toUnqualifiedVersionlessIdValues(myCarePlanDao.search(map)); + assertThat(found, Matchers.empty()); + + } + + @Test + public void testStringComparatorCo() { + + Patient p = new Patient(); + p.addName().setFamily("Smith").addGiven("John"); + p.setActive(true); + String id1 = myPatientDao.create(p).getId().toUnqualifiedVersionless().getValue(); + + p = new Patient(); + p.addName().setFamily("Jones").addGiven("Frank"); + p.setActive(false); + String id2 = myPatientDao.create(p).getId().toUnqualifiedVersionless().getValue(); + + SearchParameterMap map; + List found; + + map = new SearchParameterMap(); + map.setLoadSynchronous(true); + map.add(Constants.PARAM_FILTER, new StringParam("name co smi")); + found = toUnqualifiedVersionlessIdValues(myPatientDao.search(map)); + assertThat(found, containsInAnyOrder(id1)); + + map = new SearchParameterMap(); + map.setLoadSynchronous(true); + map.add(Constants.PARAM_FILTER, new StringParam("name co smith")); + found = toUnqualifiedVersionlessIdValues(myPatientDao.search(map)); + assertThat(found, containsInAnyOrder(id1)); + + map = new SearchParameterMap(); + map.setLoadSynchronous(true); + map.add(Constants.PARAM_FILTER, new StringParam("given co frank")); + found = toUnqualifiedVersionlessIdValues(myPatientDao.search(map)); + assertThat(found, containsInAnyOrder(id2)); + + map = new SearchParameterMap(); + map.setLoadSynchronous(true); + map.add(Constants.PARAM_FILTER, new StringParam("family co jones")); + found = toUnqualifiedVersionlessIdValues(myPatientDao.search(map)); + assertThat(found, containsInAnyOrder(id2)); + + } + + @Test + public void testStringComparatorSw() { + + Patient p = new Patient(); + p.addName().setFamily("Smith").addGiven("John"); + p.setActive(true); + String id1 = myPatientDao.create(p).getId().toUnqualifiedVersionless().getValue(); + + p = new Patient(); + p.addName().setFamily("Jones").addGiven("Frank"); + p.setActive(false); + String id2 = myPatientDao.create(p).getId().toUnqualifiedVersionless().getValue(); + + SearchParameterMap map; + List found; + + map = new SearchParameterMap(); + map.setLoadSynchronous(true); + map.add(Constants.PARAM_FILTER, new StringParam("name sw smi")); + found = toUnqualifiedVersionlessIdValues(myPatientDao.search(map)); + assertThat(found, containsInAnyOrder(id1)); + + map = new SearchParameterMap(); + map.setLoadSynchronous(true); + map.add(Constants.PARAM_FILTER, new StringParam("name sw mi")); + found = toUnqualifiedVersionlessIdValues(myPatientDao.search(map)); + assertThat(found, Matchers.empty()); + + map = new SearchParameterMap(); + map.setLoadSynchronous(true); + map.add(Constants.PARAM_FILTER, new StringParam("given sw fr")); + found = toUnqualifiedVersionlessIdValues(myPatientDao.search(map)); + assertThat(found, containsInAnyOrder(id2)); + + } + + @Test + public void testStringComparatorEw() { + + Patient p = new Patient(); + p.addName().setFamily("Smith").addGiven("John"); + p.setActive(true); + String id1 = myPatientDao.create(p).getId().toUnqualifiedVersionless().getValue(); + + p = new Patient(); + p.addName().setFamily("Jones").addGiven("Frank"); + p.setActive(false); + String id2 = myPatientDao.create(p).getId().toUnqualifiedVersionless().getValue(); + + SearchParameterMap map; + List found; + + map = new SearchParameterMap(); + map.setLoadSynchronous(true); + map.add(Constants.PARAM_FILTER, new StringParam("family ew ith")); + found = toUnqualifiedVersionlessIdValues(myPatientDao.search(map)); + assertThat(found, containsInAnyOrder(id1)); + + map = new SearchParameterMap(); + map.setLoadSynchronous(true); + map.add(Constants.PARAM_FILTER, new StringParam("name ew it")); + found = toUnqualifiedVersionlessIdValues(myPatientDao.search(map)); + assertThat(found, Matchers.empty()); + + map = new SearchParameterMap(); + map.setLoadSynchronous(true); + map.add(Constants.PARAM_FILTER, new StringParam("given ew nk")); + found = toUnqualifiedVersionlessIdValues(myPatientDao.search(map)); + assertThat(found, containsInAnyOrder(id2)); + + } + @AfterClass public static void afterClassClearContext() { TestUtil.clearAllStaticFieldsForUnitTest();