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 1ceb779abfb..d187c159d54 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 @@ -28,6 +28,7 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; import java.math.BigDecimal; import java.math.MathContext; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Date; @@ -795,14 +796,9 @@ public class SearchBuilder { } CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); - CriteriaQuery cq = builder.createQuery(Long.class); - Root from = cq.from(ResourceTag.class); - List andPredicates = new ArrayList(); - andPredicates.add(builder.equal(from.get("myResourceType"), myResourceName)); - - List orPredicates = new ArrayList(); boolean paramInverted = false; + List> tokens = Lists.newArrayList(); for (IQueryParameterType nextOrParams : nextAndParams) { String code; String system; @@ -818,57 +814,66 @@ public class SearchBuilder { code = nextParam.getValue(); system = null; } - From defJoin = from.join("myTag"); - Predicate typePrediate = builder.equal(defJoin.get("myTagType"), tagType); - Predicate codePrediate = builder.equal(defJoin.get("myCode"), code); - if (isBlank(code)) { - continue; - } - if (isNotBlank(system)) { - Predicate systemPrediate = builder.equal(defJoin.get("mySystem"), system); - orPredicates.add(builder.and(typePrediate, systemPrediate, codePrediate)); - } else { - orPredicates.add(builder.and(typePrediate, codePrediate)); - } + if (isNotBlank(code)) { + tokens.add(Pair.of(system, code)); + } } - if (orPredicates.isEmpty() == false) { - Predicate tagOptions = builder.or(toArray(orPredicates)); - andPredicates.add(tagOptions); - } else { + + if (tokens.isEmpty()) { continue; } - - From defJoin; + if (paramInverted) { - Subquery subQ = cq.subquery(Long.class); - Root subQfrom = cq.from(ResourceTag.class); - subQ.select(subQfrom.get("myResourceId").as(Long.class)); - subQ.where(builder.and(toArray(andPredicates))); - + ourLog.debug("Searching for _tag:not"); + + CriteriaQuery cq = builder.createQuery(Long.class); Root newFrom = cq.from(ResourceTable.class); - cq.select(newFrom.get("myId").as(Long.class)).where(builder.not(builder.in(newFrom.get("myId")).value(subQ))); + Subquery subQ = cq.subquery(Long.class); + Root subQfrom = subQ.from(ResourceTag.class); + subQ.select(subQfrom.get("myResourceId").as(Long.class)); + + cq.select(newFrom.get("myId").as(Long.class)); + + List andPredicates = new ArrayList(); andPredicates = new ArrayList(); - andPredicates.add(builder.equal(from.get("myResourceType"), myResourceName)); - defJoin = newFrom; + andPredicates.add(builder.equal(newFrom.get("myResourceType"), myResourceName)); + andPredicates.add(builder.not(builder.in(newFrom.get("myId")).value(subQ))); + + Subquery defJoin = subQ.subquery(Long.class); + Root defJoinFrom = defJoin.from(TagDefinition.class); + defJoin.select(defJoinFrom.get("myId").as(Long.class)); + subQ.where(subQfrom.get("myTagId").as(Long.class).in(defJoin)); + + List orPredicates = createPredicateTagList(defJoinFrom, builder, tagType, tokens); + defJoin.where(toArray(orPredicates)); + + cq.where(toArray(andPredicates)); + TypedQuery q = myEntityManager.createQuery(cq); Set pids = new HashSet(q.getResultList()); doSetPids(pids); continue; - - - - } else { - defJoin = from.join("myResource"); - } + } - Predicate notDeletedPredicatePrediate = builder.isNull(defJoin.get("myDeleted")); + CriteriaQuery cq = builder.createQuery(Long.class); + Root from = cq.from(ResourceTag.class); + List andPredicates = new ArrayList(); + andPredicates.add(builder.equal(from.get("myResourceType"), myResourceName)); + From defJoin = from.join("myTag"); + + Join defJoin2 = from.join("myResource"); + + Predicate notDeletedPredicatePrediate = builder.isNull(defJoin2.get("myDeleted")); andPredicates.add(notDeletedPredicatePrediate); + List orPredicates = createPredicateTagList(defJoin, builder, tagType, tokens); + andPredicates.add(builder.or(toArray(orPredicates))); + if (theLastUpdated != null) { - andPredicates.addAll(createLastUpdatedPredicates(theLastUpdated, builder, defJoin)); + andPredicates.addAll(createLastUpdatedPredicates(theLastUpdated, builder, defJoin2)); } createPredicateResourceId(builder, cq, andPredicates, from.get("myResourceId").as(Long.class)); @@ -884,6 +889,22 @@ public class SearchBuilder { } + private List createPredicateTagList(Path theDefJoin, CriteriaBuilder theBuilder, TagTypeEnum theTagType, List> theTokens) { + Predicate typePrediate = theBuilder.equal(theDefJoin.get("myTagType"), theTagType); + + List orPredicates = Lists.newArrayList(); + for (Pair next : theTokens) { + Predicate codePrediate = theBuilder.equal(theDefJoin.get("myCode"), next.getRight()); + if (isNotBlank(next.getLeft())) { + Predicate systemPrediate = theBuilder.equal(theDefJoin.get("mySystem"), next.getLeft()); + orPredicates.add(theBuilder.and(typePrediate, systemPrediate, codePrediate)); + } else { + orPredicates.add(theBuilder.and(typePrediate, codePrediate)); + } + } + return orPredicates; + } + private void addPredicateToken(String theParamName, List theList) { if (Boolean.TRUE.equals(theList.get(0).getMissing())) { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SearchNoFtTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SearchNoFtTest.java index cd683b76cb8..8cc1ae6c481 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SearchNoFtTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SearchNoFtTest.java @@ -2117,6 +2117,20 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test { assertThat(patients, containsInAnyOrder(tag2id)); assertThat(patients, not(containsInAnyOrder(tag1id))); } + { + // Non existant tag + SearchParameterMap params = new SearchParameterMap(); + params.add("_tag", new TokenParam("urn:taglist", methodName + "FOO").setModifier(TokenParamModifier.NOT)); + List patients = toUnqualifiedVersionlessIds(myOrganizationDao.search(params)); + assertThat(patients, containsInAnyOrder(tag1id, tag2id)); + } + { + // Common tag + SearchParameterMap params = new SearchParameterMap(); + params.add("_tag", new TokenParam("urn:taglist", methodName + "1b").setModifier(TokenParamModifier.NOT)); + List patients = toUnqualifiedVersionlessIds(myOrganizationDao.search(params)); + assertThat(patients, empty()); + } } @Test diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 64f66129808..13a35d81460 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -398,6 +398,11 @@ Thanks to GitHub user @ipropper for reporting and providing a test case! + + JPA server now supports searching for _tag:not=[tag]]]> + which enables finding resources that to not have a given tag/profile/security tag. + Thanks to Lars Kristian Roland for the suggestion! +