From d1667487c264673559e8b0084ec4d043aa0ac117 Mon Sep 17 00:00:00 2001 From: James Agnew Date: Thu, 21 Mar 2019 21:57:38 +0100 Subject: [PATCH] Clean up unique composite search params --- .../uhn/fhir/model/api/IQueryParameterOr.java | 4 +- .../ca/uhn/fhir/rest/param/DateParam.java | 42 +-- .../fhir/rest/param/TokenAndListParam.java | 12 +- .../fhir/jpa/dao/BaseHapiFhirResourceDao.java | 2 +- .../fhir/jpa/dao/FulltextSearchSvcImpl.java | 6 +- .../ca/uhn/fhir/jpa/dao/SearchBuilder.java | 250 ++++++++---------- .../dao/index/DaoSearchParamSynchronizer.java | 19 +- ...rchParamWithInlineReferencesExtractor.java | 33 +-- .../jpa/util/BaseCaptureQueriesListener.java | 13 +- .../CircularQueueCaptureQueriesListener.java | 12 + .../ca/uhn/fhir/jpa/util/ExpungeOptions.java | 8 +- ...hirResourceDaoR4UniqueSearchParamTest.java | 226 ++++++++++++++-- .../ca/uhn/fhir/jpa/util/LoggingRule.java | 2 +- .../jpa/searchparam/SearchParameterMap.java | 28 +- .../ResourceIndexedSearchParams.java | 134 +++++----- .../extractor/ResourceLinkExtractor.java | 10 +- .../SearchParamExtractorService.java | 18 +- .../registry/BaseSearchParamRegistry.java | 7 +- .../matcher/CriteriaResourceMatcher.java | 19 +- src/changes/changes.xml | 10 + 20 files changed, 517 insertions(+), 338 deletions(-) diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IQueryParameterOr.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IQueryParameterOr.java index 61cc608112b..29ed7a172f7 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IQueryParameterOr.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IQueryParameterOr.java @@ -29,8 +29,8 @@ import ca.uhn.fhir.rest.api.QualifiedParamList; public interface IQueryParameterOr extends Serializable { - public void setValuesAsQueryTokens(FhirContext theContext, String theParamName, QualifiedParamList theParameters); + void setValuesAsQueryTokens(FhirContext theContext, String theParamName, QualifiedParamList theParameters); - public List getValuesAsQueryTokens(); + List getValuesAsQueryTokens(); } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/DateParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/DateParam.java index 246d3b8d099..75834370ee1 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/DateParam.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/DateParam.java @@ -23,30 +23,27 @@ package ca.uhn.fhir.rest.param; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.model.api.IQueryParameterOr; import ca.uhn.fhir.model.api.TemporalPrecisionEnum; -import ca.uhn.fhir.model.api.annotation.SimpleSetter; import ca.uhn.fhir.model.primitive.BaseDateTimeDt; import ca.uhn.fhir.model.primitive.DateDt; import ca.uhn.fhir.model.primitive.DateTimeDt; -import ca.uhn.fhir.model.primitive.InstantDt; -import ca.uhn.fhir.parser.DataFormatException; import ca.uhn.fhir.rest.api.QualifiedParamList; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; -import ca.uhn.fhir.util.ObjectUtil; import ca.uhn.fhir.util.ValidateUtil; -import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; -import org.apache.commons.lang3.time.DateUtils; import org.hl7.fhir.instance.model.api.IPrimitiveType; -import java.util.*; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Objects; import static org.apache.commons.lang3.StringUtils.isNotBlank; public class DateParam extends BaseParamWithPrefix implements /*IQueryParameterType , */IQueryParameterOr { private static final long serialVersionUID = 1L; - + private final DateParamDateTimeHolder myValue = new DateParamDateTimeHolder(); /** @@ -119,9 +116,7 @@ public class DateParam extends BaseParamWithPrefix implements /*IQuer b.append(ParameterUtil.escapeWithDefault(getPrefix().getValue())); } - if (myValue != null) { - b.append(ParameterUtil.escapeWithDefault(myValue.getValueAsString())); - } + b.append(ParameterUtil.escapeWithDefault(myValue.getValueAsString())); return b.toString(); } @@ -132,38 +127,15 @@ public class DateParam extends BaseParamWithPrefix implements /*IQuer } public TemporalPrecisionEnum getPrecision() { - if (myValue != null) { return myValue.getPrecision(); - } - return null; } public Date getValue() { - if (myValue != null) { return myValue.getValue(); - } - return null; - } - - public DateTimeDt getValueAsDateTimeDt() { - if (myValue == null) { - return null; - } - return new DateTimeDt(myValue.getValue()); - } - - public InstantDt getValueAsInstantDt() { - if (myValue == null) { - return null; - } - return new InstantDt(myValue.getValue()); } public String getValueAsString() { - if (myValue != null) { return myValue.getValueAsString(); - } - return null; } @Override @@ -260,7 +232,7 @@ public class DateParam extends BaseParamWithPrefix implements /*IQuer /** * Constructor */ - public DateParamDateTimeHolder() { + DateParamDateTimeHolder() { super(); } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/TokenAndListParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/TokenAndListParam.java index bb88b131ab2..3256944c587 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/TokenAndListParam.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/TokenAndListParam.java @@ -36,9 +36,17 @@ public class TokenAndListParam extends BaseAndListParam { return this; } - public TokenAndListParam addAnd(TokenParam theValue) { + /** + * @param theValue The OR values + * @return Returns a reference to this for convenient chaining + */ + public TokenAndListParam addAnd(TokenParam... theValue) { Validate.notNull(theValue, "theValue must not be null"); - addValue(new TokenOrListParam().add(theValue)); + TokenOrListParam orListParam = new TokenOrListParam(); + for (TokenParam next : theValue) { + orListParam.add(next); + } + addValue(orListParam); return this; } } 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 e1ad90288bd..f5575c693ca 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 @@ -1071,7 +1071,7 @@ public abstract class BaseHapiFhirResourceDao extends B public IBundleProvider search(final SearchParameterMap theParams, RequestDetails theRequestDetails, HttpServletResponse theServletResponse) { if (myDaoConfig.getIndexMissingFields() == DaoConfig.IndexEnabledEnum.DISABLED) { - for (List> nextAnds : theParams.values()) { + for (List> nextAnds : theParams.values()) { for (List nextOrs : nextAnds) { for (IQueryParameterType next : nextOrs) { if (next.getMissing() != null) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FulltextSearchSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FulltextSearchSvcImpl.java index ea202d0f5a4..a29529c931f 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FulltextSearchSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FulltextSearchSvcImpl.java @@ -81,7 +81,7 @@ public class FulltextSearchSvcImpl implements IFulltextSearchSvc { super(); } - private void addTextSearch(QueryBuilder theQueryBuilder, BooleanJunction theBoolean, List> theTerms, String theFieldName, String theFieldNameEdgeNGram, String theFieldNameNGram) { + private void addTextSearch(QueryBuilder theQueryBuilder, BooleanJunction theBoolean, List> theTerms, String theFieldName, String theFieldNameEdgeNGram, String theFieldNameNGram) { if (theTerms == null) { return; } @@ -171,13 +171,13 @@ public class FulltextSearchSvcImpl implements IFulltextSearchSvc { /* * Handle _content parameter (resource body content) */ - List> contentAndTerms = theParams.remove(Constants.PARAM_CONTENT); + List> contentAndTerms = theParams.remove(Constants.PARAM_CONTENT); addTextSearch(qb, bool, contentAndTerms, "myContentText", "myContentTextEdgeNGram", "myContentTextNGram"); /* * Handle _text parameter (resource narrative content) */ - List> textAndTerms = theParams.remove(Constants.PARAM_TEXT); + List> textAndTerms = theParams.remove(Constants.PARAM_TEXT); addTextSearch(qb, bool, textAndTerms, "myNarrativeText", "myNarrativeTextEdgeNGram", "myNarrativeTextNGram"); if (theReferencingPid != null) { 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 0bf8a3a0aaf..604fb8f1500 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 @@ -9,9 +9,9 @@ package ca.uhn.fhir.jpa.dao; * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -37,7 +37,6 @@ import ca.uhn.fhir.jpa.searchparam.JpaRuntimeSearchParam; import ca.uhn.fhir.jpa.searchparam.MatchUrlService; import ca.uhn.fhir.jpa.searchparam.ResourceMetaParams; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; -import ca.uhn.fhir.jpa.searchparam.extractor.ResourceIndexedSearchParams; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; import ca.uhn.fhir.jpa.term.IHapiTerminologySvc; import ca.uhn.fhir.jpa.term.VersionIndependentConcept; @@ -91,7 +90,6 @@ import java.math.BigDecimal; import java.math.MathContext; import java.util.*; import java.util.Map.Entry; -import java.util.function.Function; import java.util.stream.Collectors; import static org.apache.commons.lang3.StringUtils.*; @@ -158,6 +156,7 @@ public class SearchBuilder implements ISearchBuilder { private int myFetchSize; private Integer myMaxResultsToFetch; private Set myPidSet; + private boolean myHaveIndexJoins = false; /** * Constructor @@ -213,7 +212,7 @@ public class SearchBuilder implements ISearchBuilder { } - private void addPredicateHas(List> theHasParameters) { + private void addPredicateHas(List> theHasParameters) { for (List nextOrList : theHasParameters) { @@ -274,7 +273,7 @@ public class SearchBuilder implements ISearchBuilder { } } - private void addPredicateLanguage(List> theList) { + private void addPredicateLanguage(List> theList) { for (List nextList : theList) { Set values = new HashSet<>(); @@ -286,7 +285,7 @@ public class SearchBuilder implements ISearchBuilder { } values.add(nextValue); } else { - throw new InternalErrorException("Lanugage parameter must be of type " + StringParam.class.getCanonicalName() + " - Got " + next.getClass().getCanonicalName()); + throw new InternalErrorException("Language parameter must be of type " + StringParam.class.getCanonicalName() + " - Got " + next.getClass().getCanonicalName()); } } @@ -586,7 +585,7 @@ public class SearchBuilder implements ISearchBuilder { Root subQfrom = subQ.from(ResourceTable.class); subQ.select(subQfrom.get("myId").as(Long.class)); - List> andOrParams = new ArrayList<>(); + List> andOrParams = new ArrayList<>(); andOrParams.add(theOrValues); /* @@ -641,7 +640,7 @@ public class SearchBuilder implements ISearchBuilder { return chainValue; } - private void addPredicateResourceId(List> theValues) { + private void addPredicateResourceId(List> theValues) { for (List nextValue : theValues) { Set orPids = new HashSet<>(); for (IQueryParameterType next : nextValue) { @@ -701,7 +700,7 @@ public class SearchBuilder implements ISearchBuilder { } - private void addPredicateTag(List> theList, String theParamName) { + private void addPredicateTag(List> theList, String theParamName) { TagTypeEnum tagType; if (Constants.PARAM_TAG.equals(theParamName)) { tagType = TagTypeEnum.TAG; @@ -1013,13 +1012,7 @@ public class SearchBuilder implements ISearchBuilder { } @SuppressWarnings("unchecked") - private Join createOrReuseJoin(JoinEnum theType, String theSearchParameterName) { - JoinKey key = new JoinKey(theSearchParameterName, theType); - return (Join) myIndexJoins.computeIfAbsent(key, k -> createJoin(theType, theSearchParameterName)); - } - - @SuppressWarnings("unchecked") - private Join createJoin(JoinEnum theType, String theSearchParameterName) { + private Join createJoin(JoinEnum theType, String theSearchParameterName) { Join join = null; switch (theType) { case DATE: @@ -1044,6 +1037,11 @@ public class SearchBuilder implements ISearchBuilder { join = myResourceTableRoot.join("myParamsToken", JoinType.LEFT); break; } + + JoinKey key = new JoinKey(theSearchParameterName, theType); + myIndexJoins.put(key, join); + myHaveIndexJoins = true; + return (Join) join; } @@ -1531,51 +1529,6 @@ public class SearchBuilder implements ISearchBuilder { myBuilder = myEntityManager.getCriteriaBuilder(); mySearchUuid = theSearchUuid; - /* - * Check if there is a unique key associated with the set - * of parameters passed in - */ - ourLog.debug("Checking for unique index for query: {}", theParams.toNormalizedQueryString(myContext)); - if (myDaoConfig.isUniqueIndexesEnabled()) { - if (myParams.getIncludes().isEmpty()) { - if (myParams.getRevIncludes().isEmpty()) { - if (myParams.getEverythingMode() == null) { - if (myParams.isAllParametersHaveNoModifier()) { - Set paramNames = theParams.keySet(); - if (paramNames.isEmpty() == false) { - List searchParams = mySearchParamRegistry.getActiveUniqueSearchParams(myResourceName, paramNames); - if (searchParams.size() > 0) { - List> params = new ArrayList<>(); - for (Entry>> nextParamNameToValues : theParams.entrySet()) { - String nextParamName = nextParamNameToValues.getKey(); - nextParamName = UrlUtil.escapeUrlParam(nextParamName); - for (List nextAnd : nextParamNameToValues.getValue()) { - ArrayList nextValueList = new ArrayList<>(); - params.add(nextValueList); - for (IQueryParameterType nextOr : nextAnd) { - String nextOrValue = nextOr.getValueAsQueryToken(myContext); - nextOrValue = UrlUtil.escapeUrlParam(nextOrValue); - nextValueList.add(nextParamName + "=" + nextOrValue); - } - } - } - - Set uniqueQueryStrings = ResourceIndexedSearchParams.extractCompositeStringUniquesValueChains(myResourceName, params); - if (ourTrackHandlersForUnitTest) { - ourLastHandlerParamsForUnitTest = theParams; - ourLastHandlerMechanismForUnitTest = HandlerTypeEnum.UNIQUE_INDEX; - ourLastHandlerThreadForUnitTest = Thread.currentThread().getName(); - } - return new UniqueIndexIterator(uniqueQueryStrings); - - } - } - } - } - } - } - } - if (ourTrackHandlersForUnitTest) { ourLastHandlerParamsForUnitTest = theParams; ourLastHandlerMechanismForUnitTest = HandlerTypeEnum.STANDARD_QUERY; @@ -1602,31 +1555,6 @@ public class SearchBuilder implements ISearchBuilder { if (sort != null) { assert !theCount; -// outerQuery = myBuilder.createQuery(Long.class); -// Root outerQueryFrom = outerQuery.from(ResourceTable.class); -// -// List orders = Lists.newArrayList(); -// List predicates = Lists.newArrayList(); -// -// createSort(myBuilder, outerQueryFrom, sort, orders, predicates); -// if (orders.size() > 0) { -// outerQuery.orderBy(orders); -// } -// -// Subquery subQ = outerQuery.subquery(Long.class); -// Root subQfrom = subQ.from(ResourceTable.class); -// -// myResourceTableQuery = subQ; -// myResourceTableRoot = subQfrom; -// -// Expression selectExpr = subQfrom.get("myId").as(Long.class); -// subQ.select(selectExpr); -// -// predicates.add(0, myBuilder.in(outerQueryFrom.get("myId").as(Long.class)).value(subQ)); -// -// outerQuery.multiselect(outerQueryFrom.get("myId").as(Long.class)); -// outerQuery.where(predicates.toArray(new Predicate[0])); - outerQuery = myBuilder.createQuery(Long.class); myResourceTableQuery = outerQuery; myResourceTableRoot = myResourceTableQuery.from(ResourceTable.class); @@ -1644,7 +1572,6 @@ public class SearchBuilder implements ISearchBuilder { outerQuery.orderBy(orders); } - } else { outerQuery = myBuilder.createQuery(Long.class); @@ -1713,7 +1640,7 @@ public class SearchBuilder implements ISearchBuilder { * If we have any joins to index tables, we get this behaviour already guaranteed so we don't * need an explicit predicate for it. */ - if (myIndexJoins.isEmpty()) { + if (!myHaveIndexJoins) { if (myParams.getEverythingMode() == null) { myPredicates.add(myBuilder.equal(myResourceTableRoot.get("myResourceType"), myResourceName)); } @@ -2186,17 +2113,111 @@ public class SearchBuilder implements ISearchBuilder { private void searchForIdsWithAndOr(@Nonnull SearchParameterMap theParams) { myParams = theParams; + // Remove any empty parameters theParams.clean(); - for (Entry>> nextParamEntry : myParams.entrySet()) { - String nextParamName = nextParamEntry.getKey(); - List> andOrParams = nextParamEntry.getValue(); - searchForIdsWithAndOr(myResourceName, nextParamName, andOrParams); + /* + * Check if there is a unique key associated with the set + * of parameters passed in + */ + boolean couldBeEligibleForCompositeUniqueSpProcessing = + myDaoConfig.isUniqueIndexesEnabled() && + myParams.getEverythingMode() == null && + myParams.isAllParametersHaveNoModifier(); + if (couldBeEligibleForCompositeUniqueSpProcessing) { + + // Since we're going to remove elements below + theParams.values().forEach(nextAndList -> ensureSubListsAreWritable(nextAndList)); + + List activeUniqueSearchParams = mySearchParamRegistry.getActiveUniqueSearchParams(myResourceName, theParams.keySet()); + if (activeUniqueSearchParams.size() > 0) { + + StringBuilder sb = new StringBuilder(); + sb.append(myResourceName); + sb.append("?"); + + boolean first = true; + + ArrayList keys = new ArrayList<>(theParams.keySet()); + Collections.sort(keys); + for (String nextParamName : keys) { + List> nextValues = theParams.get(nextParamName); + + nextParamName = UrlUtil.escapeUrlParam(nextParamName); + if (nextValues.get(0).size() != 1) { + sb = null; + break; + } + + // Reference params are only eligible for using a composite index if they + // are qualified + RuntimeSearchParam nextParamDef = mySearchParamRegistry.getActiveSearchParam(myResourceName, nextParamName); + if (nextParamDef.getParamType() == RestSearchParameterTypeEnum.REFERENCE) { + ReferenceParam param = (ReferenceParam) nextValues.get(0).get(0); + if (isBlank(param.getResourceType())) { + sb = null; + break; + } + } + + List nextAnd = nextValues.remove(0); + IQueryParameterType nextOr = nextAnd.remove(0); + String nextOrValue = nextOr.getValueAsQueryToken(myContext); + nextOrValue = UrlUtil.escapeUrlParam(nextOrValue); + + if (first) { + first = false; + } else { + sb.append('&'); + } + + sb.append(nextParamName).append('=').append(nextOrValue); + + } + + if (sb != null) { + String indexString = sb.toString(); + ourLog.debug("Checking for unique index for query: {}", indexString); + if (ourTrackHandlersForUnitTest) { + ourLastHandlerMechanismForUnitTest = HandlerTypeEnum.UNIQUE_INDEX; + } + addPredicateCompositeStringUnique(theParams, indexString); + } + } + } + + // Handle each parameter + for (Entry>> nextParamEntry : myParams.entrySet()) { + String nextParamName = nextParamEntry.getKey(); + List> andOrParams = nextParamEntry.getValue(); + searchForIdsWithAndOr(myResourceName, nextParamName, andOrParams); } } - private void searchForIdsWithAndOr(String theResourceName, String theParamName, List> theAndOrParams) { + + private void ensureSubListsAreWritable(List> theListOfLists) { + for (int i = 0; i < theListOfLists.size(); i++) { + List oldSubList = theListOfLists.get(i); + if (!(oldSubList instanceof ArrayList)) { + List newSubList = new ArrayList<>(oldSubList); + theListOfLists.set(i, newSubList); + } + } + } + + private void addPredicateCompositeStringUnique(@Nonnull SearchParameterMap theParams, String theIndexdString) { + myHaveIndexJoins = true; + + Join join = myResourceTableRoot.join("myParamsCompositeStringUnique", JoinType.LEFT); + Predicate predicate = myBuilder.equal(join.get("myIndexString"), theIndexdString); + myPredicates.add(predicate); + + // Remove any empty parameters remaining after this + theParams.clean(); + } + + private void searchForIdsWithAndOr(String theResourceName, String theParamName, List> theAndOrParams) { if (theAndOrParams.isEmpty()) { return; @@ -2566,47 +2587,6 @@ public class SearchBuilder implements ISearchBuilder { } - private class UniqueIndexIterator implements IResultIterator { - private final Set myUniqueQueryStrings; - private Iterator myWrap = null; - - UniqueIndexIterator(Set theUniqueQueryStrings) { - myUniqueQueryStrings = theUniqueQueryStrings; - } - - private void ensureHaveQuery() { - if (myWrap == null) { - ourLog.debug("Searching for unique index matches over {} candidate query strings", myUniqueQueryStrings.size()); - StopWatch sw = new StopWatch(); - Collection resourcePids = myResourceIndexedCompositeStringUniqueDao.findResourcePidsByQueryStrings(myUniqueQueryStrings); - ourLog.debug("Found {} unique index matches in {}ms", resourcePids.size(), sw.getMillis()); - myWrap = resourcePids.iterator(); - } - } - - @Override - public boolean hasNext() { - ensureHaveQuery(); - return myWrap.hasNext(); - } - - @Override - public Long next() { - ensureHaveQuery(); - return myWrap.next(); - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } - - @Override - public int getSkippedCount() { - return 0; - } - - } private static class CountQueryIterator implements Iterator { private final TypedQuery myQuery; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DaoSearchParamSynchronizer.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DaoSearchParamSynchronizer.java index f0f4a70cc2e..988ca7159a1 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DaoSearchParamSynchronizer.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DaoSearchParamSynchronizer.java @@ -23,7 +23,6 @@ package ca.uhn.fhir.jpa.dao.index; import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.model.entity.*; import ca.uhn.fhir.jpa.searchparam.extractor.ResourceIndexedSearchParams; -import org.apache.commons.lang3.Validate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -44,17 +43,17 @@ public class DaoSearchParamSynchronizer { public void synchronizeSearchParamsToDatabase(ResourceIndexedSearchParams theParams, ResourceTable theEntity, ResourceIndexedSearchParams existingParams) { - synchronize(theParams, theEntity, theParams.stringParams, existingParams.stringParams); - synchronize(theParams, theEntity, theParams.tokenParams, existingParams.tokenParams); - synchronize(theParams, theEntity, theParams.numberParams, existingParams.numberParams); - synchronize(theParams, theEntity, theParams.quantityParams, existingParams.quantityParams); - synchronize(theParams, theEntity, theParams.dateParams, existingParams.dateParams); - synchronize(theParams, theEntity, theParams.uriParams, existingParams.uriParams); - synchronize(theParams, theEntity, theParams.coordsParams, existingParams.coordsParams); - synchronize(theParams, theEntity, theParams.links, existingParams.links); + synchronize(theParams, theEntity, theParams.myStringParams, existingParams.myStringParams); + synchronize(theParams, theEntity, theParams.myTokenParams, existingParams.myTokenParams); + synchronize(theParams, theEntity, theParams.myNumberParams, existingParams.myNumberParams); + synchronize(theParams, theEntity, theParams.myQuantityParams, existingParams.myQuantityParams); + synchronize(theParams, theEntity, theParams.myDateParams, existingParams.myDateParams); + synchronize(theParams, theEntity, theParams.myUriParams, existingParams.myUriParams); + synchronize(theParams, theEntity, theParams.myCoordsParams, existingParams.myCoordsParams); + synchronize(theParams, theEntity, theParams.myLinks, existingParams.myLinks); // make sure links are indexed - theEntity.setResourceLinks(theParams.links); + theEntity.setResourceLinks(theParams.myLinks); } private void synchronize(ResourceIndexedSearchParams theParams, ResourceTable theEntity, Collection theNewParms, Collection theExistingParms) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/SearchParamWithInlineReferencesExtractor.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/SearchParamWithInlineReferencesExtractor.java index 00eaf381447..b0248911841 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/SearchParamWithInlineReferencesExtractor.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/SearchParamWithInlineReferencesExtractor.java @@ -106,9 +106,9 @@ public class SearchParamWithInlineReferencesExtractor { */ for (Iterator existingLinkIter = existingParams.getResourceLinks().iterator(); existingLinkIter.hasNext(); ) { ResourceLink nextExisting = existingLinkIter.next(); - if (theParams.links.remove(nextExisting)) { + if (theParams.myLinks.remove(nextExisting)) { existingLinkIter.remove(); - theParams.links.add(nextExisting); + theParams.myLinks.add(nextExisting); } } @@ -133,28 +133,28 @@ public class SearchParamWithInlineReferencesExtractor { Collection linksForCompositePartWantPaths = null; switch (nextCompositeOf.getParamType()) { case NUMBER: - paramsListForCompositePart = theParams.numberParams; + paramsListForCompositePart = theParams.myNumberParams; break; case DATE: - paramsListForCompositePart = theParams.dateParams; + paramsListForCompositePart = theParams.myDateParams; break; case STRING: - paramsListForCompositePart = theParams.stringParams; + paramsListForCompositePart = theParams.myStringParams; break; case TOKEN: - paramsListForCompositePart = theParams.tokenParams; + paramsListForCompositePart = theParams.myTokenParams; break; case REFERENCE: - linksForCompositePart = theParams.links; - linksForCompositePartWantPaths = new HashSet<>(); - linksForCompositePartWantPaths.addAll(nextCompositeOf.getPathsSplit()); + linksForCompositePart = theParams.myLinks; + linksForCompositePartWantPaths = new HashSet<>(nextCompositeOf.getPathsSplit()); break; case QUANTITY: - paramsListForCompositePart = theParams.quantityParams; + paramsListForCompositePart = theParams.myQuantityParams; break; case URI: - paramsListForCompositePart = theParams.uriParams; + paramsListForCompositePart = theParams.myUriParams; break; + case SPECIAL: case COMPOSITE: case HAS: break; @@ -189,11 +189,13 @@ public class SearchParamWithInlineReferencesExtractor { } } - Set queryStringsToPopulate = theParams.extractCompositeStringUniquesValueChains(resourceType, partsChoices); + Set queryStringsToPopulate = ResourceIndexedSearchParams.extractCompositeStringUniquesValueChains(resourceType, partsChoices); for (String nextQueryString : queryStringsToPopulate) { if (isNotBlank(nextQueryString)) { - theParams.compositeStringUniques.add(new ResourceIndexedCompositeStringUnique(theEntity, nextQueryString)); + // FIXME: JA change to trace + ourLog.info("Adding composite unique SP: {}", nextQueryString); + theParams.myCompositeStringUniques.add(new ResourceIndexedCompositeStringUnique(theEntity, nextQueryString)); } } } @@ -205,7 +207,6 @@ public class SearchParamWithInlineReferencesExtractor { * Handle references within the resource that are match URLs, for example references like "Patient?identifier=foo". These match URLs are resolved and replaced with the ID of the * matching resource. */ - public void extractInlineReferences(IBaseResource theResource) { if (!myDaoConfig.isAllowInlineMatchUrlReferences()) { return; @@ -258,12 +259,12 @@ public class SearchParamWithInlineReferencesExtractor { // Store composite string uniques if (myDaoConfig.isUniqueIndexesEnabled()) { - for (ResourceIndexedCompositeStringUnique next : myDaoSearchParamSynchronizer.subtract(existingParams.compositeStringUniques, theParams.compositeStringUniques)) { + for (ResourceIndexedCompositeStringUnique next : myDaoSearchParamSynchronizer.subtract(existingParams.myCompositeStringUniques, theParams.myCompositeStringUniques)) { ourLog.debug("Removing unique index: {}", next); myEntityManager.remove(next); theEntity.getParamsCompositeStringUnique().remove(next); } - for (ResourceIndexedCompositeStringUnique next : myDaoSearchParamSynchronizer.subtract(theParams.compositeStringUniques, existingParams.compositeStringUniques)) { + for (ResourceIndexedCompositeStringUnique next : myDaoSearchParamSynchronizer.subtract(theParams.myCompositeStringUniques, existingParams.myCompositeStringUniques)) { if (myDaoConfig.isUniqueIndexesCheckedBeforeSave()) { ResourceIndexedCompositeStringUnique existing = myResourceIndexedCompositeStringUniqueDao.findByQueryString(next.getIndexString()); if (existing != null) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/BaseCaptureQueriesListener.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/BaseCaptureQueriesListener.java index 49fcdf2dc5a..b5144d1c2c9 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/BaseCaptureQueriesListener.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/BaseCaptureQueriesListener.java @@ -100,9 +100,16 @@ public abstract class BaseCaptureQueriesListener implements ProxyDataSourceBuild if (theInlineParams) { List nextParams = new ArrayList<>(myParams); - while (retVal.contains("?") && nextParams.size() > 0) { - int idx = retVal.indexOf("?"); - retVal = retVal.substring(0, idx) + "'" + nextParams.remove(0) + "'" + retVal.substring(idx + 1); + + int idx = 0; + while (nextParams.size() > 0) { + idx = retVal.indexOf("?", idx); + if (idx == -1) { + break; + } + String nextSubstitution = "'" + nextParams.remove(0) + "'"; + retVal = retVal.substring(0, idx) + nextSubstitution + retVal.substring(idx + 1); + idx += nextSubstitution.length(); } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/CircularQueueCaptureQueriesListener.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/CircularQueueCaptureQueriesListener.java index 4003d49e723..50889dd0338 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/CircularQueueCaptureQueriesListener.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/CircularQueueCaptureQueriesListener.java @@ -104,6 +104,18 @@ public class CircularQueueCaptureQueriesListener extends BaseCaptureQueriesListe ourLog.info("Select Queries:\n{}", String.join("\n", queries)); } + /** + * Log first captured SELECT query + */ + public void logFirstSelectQueryForCurrentThread() { + String firstSelectQuery = getSelectQueriesForCurrentThread() + .stream() + .findFirst() + .map(CircularQueueCaptureQueriesListener::formatQueryAsSql) + .orElse("NONE FOUND"); + ourLog.info("First select Query:\n{}", firstSelectQuery); + } + /** * Log all captured INSERT queries */ diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/ExpungeOptions.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/ExpungeOptions.java index 3b886768f3a..efc3142acce 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/ExpungeOptions.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/ExpungeOptions.java @@ -32,10 +32,10 @@ public class ExpungeOptions { @Override public String toString() { return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE) - .append("myLimit", myLimit) - .append("myExpungeOldVersions", myExpungeOldVersions) - .append("myExpungeDeletedResources", myExpungeDeletedResources) - .append("myExpungeEverything", myExpungeEverything) + .append("limit", myLimit) + .append("oldVersions", myExpungeOldVersions) + .append("deletedResources", myExpungeDeletedResources) + .append("everything", myExpungeEverything) .toString(); } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4UniqueSearchParamTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4UniqueSearchParamTest.java index c8cbded65c3..83060ae0f5f 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4UniqueSearchParamTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4UniqueSearchParamTest.java @@ -10,6 +10,8 @@ import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.param.DateParam; +import ca.uhn.fhir.rest.param.ReferenceParam; +import ca.uhn.fhir.rest.param.TokenAndListParam; import ca.uhn.fhir.rest.param.TokenParam; import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException; @@ -44,6 +46,8 @@ import static org.junit.Assert.*; public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4UniqueSearchParamTest.class); + @Autowired + private ISearchParamRegistry mySearchParamRegistry; @After public void after() { @@ -101,7 +105,6 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test { SearchBuilder.resetLastHandlerMechanismForUnitTest(); } - private void createUniqueIndexCoverageBeneficiary() { SearchParameter sp = new SearchParameter(); sp.setId("SearchParameter/coverage-beneficiary"); @@ -141,7 +144,6 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test { mySearchParamRegistry.forceRefresh(); } - private void createUniqueIndexObservationSubject() { SearchParameter sp = new SearchParameter(); @@ -170,7 +172,6 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test { mySearchParamRegistry.forceRefresh(); } - private void createUniqueIndexPatientIdentifier() { SearchParameter sp = new SearchParameter(); @@ -199,7 +200,6 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test { mySearchParamRegistry.forceRefresh(); } - private void createUniqueIndexPatientIdentifierCount1() { SearchParameter sp = new SearchParameter(); @@ -331,9 +331,209 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test { assertEquals("gender", params.get(0).getCompositeOf().get(1).getName()); } + @Test + public void testDoubleMatchingOnAnd_Search() { + createUniqueIndexPatientIdentifier(); + + Patient pt = new Patient(); + pt.setActive(true); + pt.addIdentifier().setSystem("urn").setValue("111"); + pt.addIdentifier().setSystem("urn").setValue("222"); + String id1 = myPatientDao.create(pt).getId().toUnqualifiedVersionless().getValue(); + + pt = new Patient(); + pt.setActive(true); + pt.addIdentifier().setSystem("urn").setValue("333"); + String id2 = myPatientDao.create(pt).getId().toUnqualifiedVersionless().getValue(); + + pt = new Patient(); + pt.setActive(false); + pt.addIdentifier().setSystem("urn").setValue("444"); + myPatientDao.create(pt); + + String unformattedSql; + + // Two AND values + myCaptureQueriesListener.clear(); + SearchParameterMap sp = new SearchParameterMap(); + sp.setLoadSynchronous(true); + sp.add("identifier", + new TokenAndListParam() + .addAnd(new TokenParam("urn", "111")) + .addAnd(new TokenParam("urn", "222")) + ); + IBundleProvider outcome = myPatientDao.search(sp); + myCaptureQueriesListener.logFirstSelectQueryForCurrentThread(); + assertThat(toUnqualifiedVersionlessIdValues(outcome), containsInAnyOrder(id1)); + unformattedSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false); + assertThat(unformattedSql, stringContainsInOrder( + "IDX_STRING='Patient?identifier=urn%7C111'", + "HASH_SYS_AND_VALUE in ('-3122824860083758210')" + )); + assertThat(unformattedSql, not(containsString(("RES_DELETED_AT")))); + assertThat(unformattedSql, not(containsString(("RES_TYPE")))); + + // Two OR values on the same resource - Currently composite SPs don't work for this + myCaptureQueriesListener.clear(); + sp = new SearchParameterMap(); + sp.setLoadSynchronous(true); + sp.add("identifier", + new TokenAndListParam() + .addAnd(new TokenParam("urn", "111"), new TokenParam("urn", "222")) + ); + outcome = myPatientDao.search(sp); + myCaptureQueriesListener.logFirstSelectQueryForCurrentThread(); + assertThat(toUnqualifiedVersionlessIdValues(outcome), containsInAnyOrder(id1)); + unformattedSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false); + assertThat(unformattedSql, containsString("HASH_SYS_AND_VALUE in ('4101160957635429999' , '-3122824860083758210')")); + assertThat(unformattedSql, not(containsString(("IDX_STRING")))); + assertThat(unformattedSql, not(containsString(("RES_DELETED_AT")))); + assertThat(unformattedSql, not(containsString(("RES_TYPE")))); + + // Not matching the composite SP at all + myCaptureQueriesListener.clear(); + sp = new SearchParameterMap(); + sp.setLoadSynchronous(true); + sp.add("active", + new TokenAndListParam() + .addAnd(new TokenParam(null, "true")) + ); + outcome = myPatientDao.search(sp); + myCaptureQueriesListener.logFirstSelectQueryForCurrentThread(); + assertThat(toUnqualifiedVersionlessIdValues(outcome), containsInAnyOrder(id1, id2)); + unformattedSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false); + assertThat(unformattedSql, not(containsString(("IDX_STRING")))); + assertThat(unformattedSql, not(containsString(("RES_DELETED_AT")))); + assertThat(unformattedSql, not(containsString(("RES_TYPE")))); + + } @Test - public void testDoubleMatching() { + public void testDoubleMatchingOnAnd_Search2() { + SearchParameter sp; + + sp = new SearchParameter(); + sp.setStatus(PublicationStatus.ACTIVE); + sp.setCode("patient"); + sp.setName("patient"); + sp.setType(Enumerations.SearchParamType.REFERENCE); + sp.addBase(ServiceRequest.class.getName()); + sp.setExpression("ServiceRequest.subject.where(resolve() is Patient)"); + String patientParamId = mySearchParameterDao.create(sp).getId().toUnqualifiedVersionless().getValue(); + + sp = new SearchParameter(); + sp.setStatus(PublicationStatus.ACTIVE); + sp.setCode("performer"); + sp.setName("performer"); + sp.setType(Enumerations.SearchParamType.REFERENCE); + sp.addBase(ServiceRequest.class.getName()); + sp.setExpression("ServiceRequest.performer"); + String performerParamId = mySearchParameterDao.create(sp).getId().toUnqualifiedVersionless().getValue(); + + sp = new SearchParameter(); + sp.setStatus(PublicationStatus.ACTIVE); + sp.setCode("identifier"); + sp.setName("identifier"); + sp.setType(Enumerations.SearchParamType.TOKEN); + sp.addBase(ServiceRequest.class.getName()); + sp.setExpression("ServiceRequest.identifier"); + String identifierParamId = mySearchParameterDao.create(sp).getId().toUnqualifiedVersionless().getValue(); + + sp = new SearchParameter(); + sp.setId("SearchParameter/patient-uniq-identifier"); + sp.setCode("procreq-patient-performer-identifier"); + sp.setExpression("ServiceRequest.patient"); + sp.setType(Enumerations.SearchParamType.COMPOSITE); + sp.setStatus(PublicationStatus.ACTIVE); + sp.addBase("ServiceRequest"); + sp.addComponent() + .setExpression("ServiceRequest") + .setDefinition(patientParamId); // SearchParameter?base=ServiceRequest&name=patient + sp.addComponent() + .setExpression("ServiceRequest") + .setDefinition(performerParamId); // SearchParameter?base=ServiceRequest&name=performer + sp.addComponent() + .setExpression("ServiceRequest") + .setDefinition(identifierParamId); // SearchParameter?base=ServiceRequest&name=identifier + sp.addExtension() + .setUrl(SearchParamConstants.EXT_SP_UNIQUE) + .setValue(new BooleanType(true)); + mySearchParameterDao.create(sp); + mySearchParamRegistry.forceRefresh(); + + // Now create matching/non-matching resources + Patient pt = new Patient(); + pt.setActive(true); + IIdType ptId = myPatientDao.create(pt).getId().toUnqualifiedVersionless(); + + Practitioner pract = new Practitioner(); + pract.setActive(true); + IIdType practId = myPractitionerDao.create(pract).getId().toUnqualifiedVersionless(); + + ServiceRequest sr = new ServiceRequest(); + sr.addIdentifier().setSystem("sys").setValue("111"); + sr.addIdentifier().setSystem("sys").setValue("222"); + sr.setSubject(new Reference(ptId)); + sr.addPerformer(new Reference(practId)); + String srId = myServiceRequestDao.create(sr).getId().toUnqualifiedVersionless().getValue(); + + sr = new ServiceRequest(); + sr.addIdentifier().setSystem("sys").setValue("888"); + sr.addIdentifier().setSystem("sys").setValue("999"); + sr.setSubject(new Reference(ptId)); + sr.addPerformer(new Reference(practId)); + myServiceRequestDao.create(sr).getId().toUnqualifiedVersionless().getValue(); + + String unformattedSql; + + // Use qualified references + myCaptureQueriesListener.clear(); + SearchParameterMap map = new SearchParameterMap(); + map.setLoadSynchronous(true); + map.add("identifier", + new TokenAndListParam() + .addAnd(new TokenParam("sys", "111")) + .addAnd(new TokenParam("sys", "222")) + ); + map.add("patient", new ReferenceParam(ptId.getValue())); + map.add("performer", new ReferenceParam(practId.getValue())); + IBundleProvider outcome = myServiceRequestDao.search(map); + myCaptureQueriesListener.logFirstSelectQueryForCurrentThread(); + assertThat(toUnqualifiedVersionlessIdValues(outcome), containsInAnyOrder(srId)); + unformattedSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false); + assertThat(unformattedSql, stringContainsInOrder( + "IDX_STRING='ServiceRequest?identifier=sys%7C111&patient=Patient%2F" + ptId.getIdPart() + "&performer=Practitioner%2F"+ practId.getIdPart() +"'", + "HASH_SYS_AND_VALUE in ('6795110643554413877')" + )); + assertThat(unformattedSql, not(containsString(("RES_DELETED_AT")))); + assertThat(unformattedSql, not(containsString(("RES_TYPE")))); + + // Don't use qualified references + myCaptureQueriesListener.clear(); + map = new SearchParameterMap(); + map.setLoadSynchronous(true); + map.add("identifier", + new TokenAndListParam() + .addAnd(new TokenParam("sys", "111")) + .addAnd(new TokenParam("sys", "222")) + ); + map.add("patient", new ReferenceParam(ptId.getIdPart())); + map.add("performer", new ReferenceParam(practId.getIdPart())); + outcome = myServiceRequestDao.search(map); + myCaptureQueriesListener.logFirstSelectQueryForCurrentThread(); + assertThat(toUnqualifiedVersionlessIdValues(outcome), containsInAnyOrder(srId)); + unformattedSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false); + assertThat(unformattedSql, stringContainsInOrder( + "SRC_PATH in ('ServiceRequest.subject.where(resolve() is Patient)')", + "SRC_PATH in ('ServiceRequest.performer')" + )); + assertThat(unformattedSql, not(containsString(("RES_DELETED_AT")))); + assertThat(unformattedSql, not(containsString(("RES_TYPE")))); + + } + + @Test + public void testDoubleMatchingOnOr_ConditionalCreate() { createUniqueIndexPatientIdentifier(); Patient pt = new Patient(); @@ -369,12 +569,10 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test { .setIfNoneExist("/Patient?identifier=urn|111,urn|222"); mySystemDao.transaction(mySrd, input); - new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(@Nonnull TransactionStatus status) { - List all = myResourceIndexedCompositeStringUniqueDao.findAll(); - assertEquals(2, all.size()); - } + // Make sure entries are saved + runInTransaction(() -> { + List all = myResourceIndexedCompositeStringUniqueDao.findAll(); + assertEquals(2, all.size()); }); } @@ -424,10 +622,6 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test { } - @Autowired - private ISearchParamRegistry mySearchParamRegistry; - - @Test public void testDuplicateUniqueValuesAreReIndexed() { myDaoConfig.setSchedulingDisabled(true); @@ -767,12 +961,14 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test { pt2.setBirthDateElement(new DateType("2011-01-02")); myPatientDao.create(pt2).getId().toUnqualifiedVersionless(); + myCaptureQueriesListener.clear(); SearchBuilder.resetLastHandlerMechanismForUnitTest(); SearchParameterMap params = new SearchParameterMap(); params.setLoadSynchronousUpTo(100); params.add("gender", new TokenParam("http://hl7.org/fhir/administrative-gender", "male")); params.add("birthdate", new DateParam("2011-01-01")); IBundleProvider results = myPatientDao.search(params); + myCaptureQueriesListener.logFirstSelectQueryForCurrentThread(); assertThat(toUnqualifiedVersionlessIdValues(results), containsInAnyOrder(id1.getValue())); assertEquals(SearchBuilder.getLastHandlerParamsForUnitTest(), SearchBuilder.HandlerTypeEnum.UNIQUE_INDEX, SearchBuilder.getLastHandlerMechanismForUnitTest()); } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/util/LoggingRule.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/util/LoggingRule.java index 824f1d34057..9848b321ccb 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/util/LoggingRule.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/util/LoggingRule.java @@ -49,7 +49,7 @@ public class LoggingRule implements TestRule { try { statement.evaluate(); } catch (final Throwable e) { - logger.info(MessageFormat.format("Exception thrown in test case [{0}]", description.getDisplayName()), e); + logger.info(MessageFormat.format("Exception thrown in test case [{0}]: {1}", description.getDisplayName(), e.toString())); throw e; } finally { logger.info(MessageFormat.format("Finished test case [{0}]", description.getDisplayName())); diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/SearchParameterMap.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/SearchParameterMap.java index c955334cb60..5d5f66b203a 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/SearchParameterMap.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/SearchParameterMap.java @@ -45,7 +45,7 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; public class SearchParameterMap implements Serializable { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchParameterMap.class); - private final HashMap>> mySearchParameterMap = new LinkedHashMap<>(); + private final HashMap>> mySearchParameterMap = new LinkedHashMap<>(); private static final long serialVersionUID = 1L; @@ -107,7 +107,7 @@ public class SearchParameterMap implements Serializable { if (next == null) { continue; } - get(theName).add(next.getValuesAsQueryTokens()); + get(theName).add((List) next.getValuesAsQueryTokens()); } } @@ -119,10 +119,10 @@ public class SearchParameterMap implements Serializable { put(theName, new ArrayList<>()); } - get(theName).add(theOr.getValuesAsQueryTokens()); + get(theName).add((List) theOr.getValuesAsQueryTokens()); } - public Collection>> values() { + public Collection>> values() { return mySearchParameterMap.values(); } @@ -272,8 +272,8 @@ public class SearchParameterMap implements Serializable { * This will only return true if all parameters have no modifier of any kind */ public boolean isAllParametersHaveNoModifier() { - for (List> nextParamName : values()) { - for (List nextAnd : nextParamName) { + for (List> nextParamName : values()) { + for (List nextAnd : nextParamName) { for (IQueryParameterType nextOr : nextAnd) { if (isNotBlank(nextOr.getQueryParameterQualifier())) { return false; @@ -319,7 +319,7 @@ public class SearchParameterMap implements Serializable { Collections.sort(keys); for (String nextKey : keys) { - List> nextValuesAndsIn = get(nextKey); + List> nextValuesAndsIn = get(nextKey); List> nextValuesAndsOut = new ArrayList<>(); for (List nextValuesAndIn : nextValuesAndsIn) { @@ -448,9 +448,9 @@ public class SearchParameterMap implements Serializable { } public void clean() { - for (Map.Entry>> nextParamEntry : this.entrySet()) { + for (Map.Entry>> nextParamEntry : this.entrySet()) { String nextParamName = nextParamEntry.getKey(); - List> andOrParams = nextParamEntry.getValue(); + List> andOrParams = nextParamEntry.getValue(); clean(nextParamName, andOrParams); } } @@ -458,7 +458,7 @@ public class SearchParameterMap implements Serializable { /* * Filter out */ - private void clean(String theParamName, List> theAndOrParams) { + private void clean(String theParamName, List> theAndOrParams) { for (int andListIdx = 0; andListIdx < theAndOrParams.size(); andListIdx++) { List nextOrList = theAndOrParams.get(andListIdx); @@ -603,11 +603,11 @@ public class SearchParameterMap implements Serializable { // Wrapper methods - public List> get(String theName) { + public List> get(String theName) { return mySearchParameterMap.get(theName); } - private void put(String theName, List> theParams) { + private void put(String theName, List> theParams) { mySearchParameterMap.put(theName, theParams); } @@ -623,11 +623,11 @@ public class SearchParameterMap implements Serializable { return mySearchParameterMap.isEmpty(); } - public Set>>> entrySet() { + public Set>>> entrySet() { return mySearchParameterMap.entrySet(); } - public List> remove(String theName) { + public List> remove(String theName) { return mySearchParameterMap.remove(theName); } } diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceIndexedSearchParams.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceIndexedSearchParams.java index 2be31c4a0f6..edda27656ba 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceIndexedSearchParams.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceIndexedSearchParams.java @@ -37,86 +37,86 @@ import static org.apache.commons.lang3.StringUtils.compare; public final class ResourceIndexedSearchParams { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResourceIndexedSearchParams.class); - final public Collection stringParams = new ArrayList<>(); - final public Collection tokenParams = new HashSet<>(); - final public Collection numberParams = new ArrayList<>(); - final public Collection quantityParams = new ArrayList<>(); - final public Collection dateParams = new ArrayList<>(); - final public Collection uriParams = new ArrayList<>(); - final public Collection coordsParams = new ArrayList<>(); + final public Collection myStringParams = new ArrayList<>(); + final public Collection myTokenParams = new HashSet<>(); + final public Collection myNumberParams = new ArrayList<>(); + final public Collection myQuantityParams = new ArrayList<>(); + final public Collection myDateParams = new ArrayList<>(); + final public Collection myUriParams = new ArrayList<>(); + final public Collection myCoordsParams = new ArrayList<>(); - final public Collection compositeStringUniques = new HashSet<>(); - final public Collection links = new HashSet<>(); - final public Set populatedResourceLinkParameters = new HashSet<>(); + final public Collection myCompositeStringUniques = new HashSet<>(); + final public Collection myLinks = new HashSet<>(); + final public Set myPopulatedResourceLinkParameters = new HashSet<>(); public ResourceIndexedSearchParams() { } public ResourceIndexedSearchParams(ResourceTable theEntity) { if (theEntity.isParamsStringPopulated()) { - stringParams.addAll(theEntity.getParamsString()); + myStringParams.addAll(theEntity.getParamsString()); } if (theEntity.isParamsTokenPopulated()) { - tokenParams.addAll(theEntity.getParamsToken()); + myTokenParams.addAll(theEntity.getParamsToken()); } if (theEntity.isParamsNumberPopulated()) { - numberParams.addAll(theEntity.getParamsNumber()); + myNumberParams.addAll(theEntity.getParamsNumber()); } if (theEntity.isParamsQuantityPopulated()) { - quantityParams.addAll(theEntity.getParamsQuantity()); + myQuantityParams.addAll(theEntity.getParamsQuantity()); } if (theEntity.isParamsDatePopulated()) { - dateParams.addAll(theEntity.getParamsDate()); + myDateParams.addAll(theEntity.getParamsDate()); } if (theEntity.isParamsUriPopulated()) { - uriParams.addAll(theEntity.getParamsUri()); + myUriParams.addAll(theEntity.getParamsUri()); } if (theEntity.isParamsCoordsPopulated()) { - coordsParams.addAll(theEntity.getParamsCoords()); + myCoordsParams.addAll(theEntity.getParamsCoords()); } if (theEntity.isHasLinks()) { - links.addAll(theEntity.getResourceLinks()); + myLinks.addAll(theEntity.getResourceLinks()); } if (theEntity.isParamsCompositeStringUniquePresent()) { - compositeStringUniques.addAll(theEntity.getParamsCompositeStringUnique()); + myCompositeStringUniques.addAll(theEntity.getParamsCompositeStringUnique()); } } public Collection getResourceLinks() { - return links; + return myLinks; } public void setParamsOn(ResourceTable theEntity) { - theEntity.setParamsString(stringParams); - theEntity.setParamsStringPopulated(stringParams.isEmpty() == false); - theEntity.setParamsToken(tokenParams); - theEntity.setParamsTokenPopulated(tokenParams.isEmpty() == false); - theEntity.setParamsNumber(numberParams); - theEntity.setParamsNumberPopulated(numberParams.isEmpty() == false); - theEntity.setParamsQuantity(quantityParams); - theEntity.setParamsQuantityPopulated(quantityParams.isEmpty() == false); - theEntity.setParamsDate(dateParams); - theEntity.setParamsDatePopulated(dateParams.isEmpty() == false); - theEntity.setParamsUri(uriParams); - theEntity.setParamsUriPopulated(uriParams.isEmpty() == false); - theEntity.setParamsCoords(coordsParams); - theEntity.setParamsCoordsPopulated(coordsParams.isEmpty() == false); - theEntity.setParamsCompositeStringUniquePresent(compositeStringUniques.isEmpty() == false); - theEntity.setResourceLinks(links); - theEntity.setHasLinks(links.isEmpty() == false); + theEntity.setParamsString(myStringParams); + theEntity.setParamsStringPopulated(myStringParams.isEmpty() == false); + theEntity.setParamsToken(myTokenParams); + theEntity.setParamsTokenPopulated(myTokenParams.isEmpty() == false); + theEntity.setParamsNumber(myNumberParams); + theEntity.setParamsNumberPopulated(myNumberParams.isEmpty() == false); + theEntity.setParamsQuantity(myQuantityParams); + theEntity.setParamsQuantityPopulated(myQuantityParams.isEmpty() == false); + theEntity.setParamsDate(myDateParams); + theEntity.setParamsDatePopulated(myDateParams.isEmpty() == false); + theEntity.setParamsUri(myUriParams); + theEntity.setParamsUriPopulated(myUriParams.isEmpty() == false); + theEntity.setParamsCoords(myCoordsParams); + theEntity.setParamsCoordsPopulated(myCoordsParams.isEmpty() == false); + theEntity.setParamsCompositeStringUniquePresent(myCompositeStringUniques.isEmpty() == false); + theEntity.setResourceLinks(myLinks); + theEntity.setHasLinks(myLinks.isEmpty() == false); } public void setUpdatedTime(Date theUpdateTime) { - setUpdatedTime(stringParams, theUpdateTime); - setUpdatedTime(numberParams, theUpdateTime); - setUpdatedTime(quantityParams, theUpdateTime); - setUpdatedTime(dateParams, theUpdateTime); - setUpdatedTime(uriParams, theUpdateTime); - setUpdatedTime(coordsParams, theUpdateTime); - setUpdatedTime(tokenParams, theUpdateTime); + setUpdatedTime(myStringParams, theUpdateTime); + setUpdatedTime(myNumberParams, theUpdateTime); + setUpdatedTime(myQuantityParams, theUpdateTime); + setUpdatedTime(myDateParams, theUpdateTime); + setUpdatedTime(myUriParams, theUpdateTime); + setUpdatedTime(myCoordsParams, theUpdateTime); + setUpdatedTime(myTokenParams, theUpdateTime); } private void setUpdatedTime(Collection theParams, Date theUpdateTime) { @@ -215,7 +215,7 @@ public final class ResourceIndexedSearchParams { } public Set getPopulatedResourceLinkParameters() { - return populatedResourceLinkParameters; + return myPopulatedResourceLinkParameters; } public boolean matchParam(String theResourceName, String theParamName, RuntimeSearchParam theParamDef, IQueryParameterType theParam) { @@ -225,22 +225,22 @@ public final class ResourceIndexedSearchParams { Collection resourceParams; switch (theParamDef.getParamType()) { case TOKEN: - resourceParams = tokenParams; + resourceParams = myTokenParams; break; case QUANTITY: - resourceParams = quantityParams; + resourceParams = myQuantityParams; break; case STRING: - resourceParams = stringParams; + resourceParams = myStringParams; break; case NUMBER: - resourceParams = numberParams; + resourceParams = myNumberParams; break; case URI: - resourceParams = uriParams; + resourceParams = myUriParams; break; case DATE: - resourceParams = dateParams; + resourceParams = myDateParams; break; case REFERENCE: return matchResourceLinks(theResourceName, theParamName, theParam, theParamDef.getPath()); @@ -267,7 +267,7 @@ public final class ResourceIndexedSearchParams { resourceLinkMatches(theResourceName, resourceLink, theParamName, theParamPath) && resourceIdMatches(resourceLink, reference); - return links.stream().anyMatch(namedParamPredicate); + return myLinks.stream().anyMatch(namedParamPredicate); } private boolean resourceIdMatches(ResourceLink theResourceLink, ReferenceParam theReference) { @@ -293,25 +293,25 @@ public final class ResourceIndexedSearchParams { @Override public String toString() { return "ResourceIndexedSearchParams{" + - "stringParams=" + stringParams + - ", tokenParams=" + tokenParams + - ", numberParams=" + numberParams + - ", quantityParams=" + quantityParams + - ", dateParams=" + dateParams + - ", uriParams=" + uriParams + - ", coordsParams=" + coordsParams + - ", compositeStringUniques=" + compositeStringUniques + - ", links=" + links + + "stringParams=" + myStringParams + + ", tokenParams=" + myTokenParams + + ", numberParams=" + myNumberParams + + ", quantityParams=" + myQuantityParams + + ", dateParams=" + myDateParams + + ", uriParams=" + myUriParams + + ", coordsParams=" + myCoordsParams + + ", compositeStringUniques=" + myCompositeStringUniques + + ", links=" + myLinks + '}'; } public void findMissingSearchParams(ModelConfig theModelConfig, ResourceTable theEntity, Set> theActiveSearchParams) { - findMissingSearchParams(theModelConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.STRING, stringParams); - findMissingSearchParams(theModelConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.NUMBER, numberParams); - findMissingSearchParams(theModelConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.QUANTITY, quantityParams); - findMissingSearchParams(theModelConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.DATE, dateParams); - findMissingSearchParams(theModelConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.URI, uriParams); - findMissingSearchParams(theModelConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.TOKEN, tokenParams); + findMissingSearchParams(theModelConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.STRING, myStringParams); + findMissingSearchParams(theModelConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.NUMBER, myNumberParams); + findMissingSearchParams(theModelConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.QUANTITY, myQuantityParams); + findMissingSearchParams(theModelConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.DATE, myDateParams); + findMissingSearchParams(theModelConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.URI, myUriParams); + findMissingSearchParams(theModelConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.TOKEN, myTokenParams); } @SuppressWarnings("unchecked") diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceLinkExtractor.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceLinkExtractor.java index 042237d9fd2..092f3650ff1 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceLinkExtractor.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceLinkExtractor.java @@ -74,7 +74,7 @@ public class ResourceLinkExtractor { extractResourceLinks(theParams, theEntity, theResource, theUpdateTime, theResourceLinkResolver, resourceType, nextSpDef, theFailOnInvalidReference); } - theEntity.setHasLinks(theParams.links.size() > 0); + theEntity.setHasLinks(theParams.myLinks.size() > 0); } private void extractResourceLinks(ResourceIndexedSearchParams theParams, ResourceTable theEntity, IBaseResource theResource, Date theUpdateTime, IResourceLinkResolver theResourceLinkResolver, String theResourceType, RuntimeSearchParam nextSpDef, boolean theFailOnInvalidReference) { @@ -155,11 +155,11 @@ public class ResourceLinkExtractor { } } - theParams.populatedResourceLinkParameters.add(nextSpDef.getName()); + theParams.myPopulatedResourceLinkParameters.add(nextSpDef.getName()); if (LogicalReferenceHelper.isLogicalReference(myModelConfig, nextId)) { ResourceLink resourceLink = new ResourceLink(nextPathAndRef.getPath(), theEntity, nextId, theUpdateTime); - if (theParams.links.add(resourceLink)) { + if (theParams.myLinks.add(resourceLink)) { ourLog.debug("Indexing remote resource reference URL: {}", nextId); } return; @@ -195,7 +195,7 @@ public class ResourceLinkExtractor { throw new InvalidRequestException(msg); } else { ResourceLink resourceLink = new ResourceLink(nextPathAndRef.getPath(), theEntity, nextId, theUpdateTime); - if (theParams.links.add(resourceLink)) { + if (theParams.myLinks.add(resourceLink)) { ourLog.debug("Indexing remote resource reference URL: {}", nextId); } return; @@ -217,7 +217,7 @@ public class ResourceLinkExtractor { theResourceLinkResolver.validateTypeOrThrowException(type); ResourceLink resourceLink = createResourceLink(theEntity, theUpdateTime, theResourceLinkResolver, nextSpDef, theNextPathsUnsplit, nextPathAndRef, nextId, typeString, type, id); if (resourceLink == null) return; - theParams.links.add(resourceLink); + theParams.myLinks.add(resourceLink); } private ResourceLink createResourceLink(ResourceTable theEntity, Date theUpdateTime, IResourceLinkResolver theResourceLinkResolver, RuntimeSearchParam nextSpDef, String theNextPathsUnsplit, PathAndRef nextPathAndRef, IIdType theNextId, String theTypeString, Class theType, String theId) { diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorService.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorService.java index a4f3e939d73..c6bbb3557b8 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorService.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorService.java @@ -37,20 +37,20 @@ public class SearchParamExtractorService { private ISearchParamExtractor mySearchParamExtractor; public void extractFromResource(ResourceIndexedSearchParams theParams, ResourceTable theEntity, IBaseResource theResource) { - theParams.stringParams.addAll(extractSearchParamStrings(theEntity, theResource)); - theParams.numberParams.addAll(extractSearchParamNumber(theEntity, theResource)); - theParams.quantityParams.addAll(extractSearchParamQuantity(theEntity, theResource)); - theParams.dateParams.addAll(extractSearchParamDates(theEntity, theResource)); - theParams.uriParams.addAll(extractSearchParamUri(theEntity, theResource)); - theParams.coordsParams.addAll(extractSearchParamCoords(theEntity, theResource)); + theParams.myStringParams.addAll(extractSearchParamStrings(theEntity, theResource)); + theParams.myNumberParams.addAll(extractSearchParamNumber(theEntity, theResource)); + theParams.myQuantityParams.addAll(extractSearchParamQuantity(theEntity, theResource)); + theParams.myDateParams.addAll(extractSearchParamDates(theEntity, theResource)); + theParams.myUriParams.addAll(extractSearchParamUri(theEntity, theResource)); + theParams.myCoordsParams.addAll(extractSearchParamCoords(theEntity, theResource)); - ourLog.trace("Storing date indexes: {}", theParams.dateParams); + ourLog.trace("Storing date indexes: {}", theParams.myDateParams); for (BaseResourceIndexedSearchParam next : extractSearchParamTokens(theEntity, theResource)) { if (next instanceof ResourceIndexedSearchParamToken) { - theParams.tokenParams.add((ResourceIndexedSearchParamToken) next); + theParams.myTokenParams.add((ResourceIndexedSearchParamToken) next); } else { - theParams.stringParams.add((ResourceIndexedSearchParamString) next); + theParams.myStringParams.add((ResourceIndexedSearchParamString) next); } } } diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/BaseSearchParamRegistry.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/BaseSearchParamRegistry.java index 087cc3035ad..8389b781344 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/BaseSearchParamRegistry.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/BaseSearchParamRegistry.java @@ -169,12 +169,7 @@ public abstract class BaseSearchParamRegistry implemen } if (next.getCompositeOf() != null) { - next.getCompositeOf().sort(new Comparator() { - @Override - public int compare(RuntimeSearchParam theO1, RuntimeSearchParam theO2) { - return StringUtils.compare(theO1.getName(), theO2.getName()); - } - }); + next.getCompositeOf().sort((theO1, theO2) -> StringUtils.compare(theO1.getName(), theO2.getName())); for (String nextBase : next.getBase()) { if (!activeParamNamesToUniqueSearchParams.containsKey(nextBase)) { activeParamNamesToUniqueSearchParams.put(nextBase, new HashMap<>()); diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/CriteriaResourceMatcher.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/CriteriaResourceMatcher.java index e93e23a6f4e..1ab7cc72ce8 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/CriteriaResourceMatcher.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/CriteriaResourceMatcher.java @@ -47,7 +47,6 @@ import java.util.function.Predicate; @Service public class CriteriaResourceMatcher { - private static final String CRITERIA = "CRITERIA"; @Autowired private MatchUrlService myMatchUrlService; @Autowired @@ -83,9 +82,9 @@ public class CriteriaResourceMatcher { return SubscriptionMatchResult.unsupportedFromParameterAndReason(Constants.PARAM_LASTUPDATED, SubscriptionMatchResult.STANDARD_PARAMETER); } - for (Map.Entry>> entry : searchParameterMap.entrySet()) { + for (Map.Entry>> entry : searchParameterMap.entrySet()) { String theParamName = entry.getKey(); - List> theAndOrParams = entry.getValue(); + List> theAndOrParams = entry.getValue(); SubscriptionMatchResult result = matchIdsWithAndOr(theParamName, theAndOrParams, resourceDefinition, theResource, theSearchParams); if (!result.matched()){ return result; @@ -95,7 +94,7 @@ public class CriteriaResourceMatcher { } // This method is modelled from SearchBuilder.searchForIdsWithAndOr() - private SubscriptionMatchResult matchIdsWithAndOr(String theParamName, List> theAndOrParams, RuntimeResourceDefinition theResourceDefinition, IBaseResource theResource, ResourceIndexedSearchParams theSearchParams) { + private SubscriptionMatchResult matchIdsWithAndOr(String theParamName, List> theAndOrParams, RuntimeResourceDefinition theResourceDefinition, IBaseResource theResource, ResourceIndexedSearchParams theSearchParams) { if (theAndOrParams.isEmpty()) { return SubscriptionMatchResult.successfulMatch(); } @@ -132,13 +131,13 @@ public class CriteriaResourceMatcher { } } - private boolean matchIdsAndOr(List> theAndOrParams, IBaseResource theResource) { + private boolean matchIdsAndOr(List> theAndOrParams, IBaseResource theResource) { if (theResource == null) { return true; } return theAndOrParams.stream().allMatch(nextAnd -> matchIdsOr(nextAnd, theResource)); } - private boolean matchIdsOr(List theOrParams, IBaseResource theResource) { + private boolean matchIdsOr(List theOrParams, IBaseResource theResource) { if (theResource == null) { return true; } @@ -149,7 +148,7 @@ public class CriteriaResourceMatcher { return theValue.equals(theId.getValue()) || theValue.equals(theId.getIdPart()); } - private SubscriptionMatchResult matchResourceParam(String theParamName, List> theAndOrParams, ResourceIndexedSearchParams theSearchParams, String theResourceName, RuntimeSearchParam theParamDef) { + private SubscriptionMatchResult matchResourceParam(String theParamName, List> theAndOrParams, ResourceIndexedSearchParams theSearchParams, String theResourceName, RuntimeSearchParam theParamDef) { if (theParamDef != null) { switch (theParamDef.getParamType()) { case QUANTITY: @@ -183,15 +182,15 @@ public class CriteriaResourceMatcher { return theNextAnd.stream().anyMatch(token -> theSearchParams.matchParam(theResourceName, theParamName, paramDef, token)); } - private boolean hasChain(List> theAndOrParams) { + private boolean hasChain(List> theAndOrParams) { return theAndOrParams.stream().flatMap(List::stream).anyMatch(param -> param instanceof ReferenceParam && ((ReferenceParam)param).getChain() != null); } - private boolean hasQualifiers(List> theAndOrParams) { + private boolean hasQualifiers(List> theAndOrParams) { return theAndOrParams.stream().flatMap(List::stream).anyMatch(param -> param.getQueryParameterQualifier() != null); } - private boolean hasPrefixes(List> theAndOrParams) { + private boolean hasPrefixes(List> theAndOrParams) { Predicate hasPrefixPredicate = param -> param instanceof BaseParamWithPrefix && ((BaseParamWithPrefix) param).getPrefix() != null; return theAndOrParams.stream().flatMap(List::stream).anyMatch(hasPrefixPredicate); diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 9908c38459b..34393052024 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -90,6 +90,16 @@ HapiLocalizer can now handle message patterns with braces that aren't a part of a message format expression. E.g. "Here is an {example}". + + JPA searches using a Composite Unique Index will now use that index for faster + searching even if the search has _includes and/or _sorts. Previously these two + features caused the search builder to skip using the index. + + + JPA searches using a Composite Unique Index did not return the correct results if + a REFERENCE search parameter was used with arguments that consisted of + unqualified resource IDs. +