Clean up unique composite search params

This commit is contained in:
James Agnew 2019-03-21 21:57:38 +01:00
parent 8d49b4e6d2
commit d1667487c2
20 changed files with 517 additions and 338 deletions

View File

@ -29,8 +29,8 @@ import ca.uhn.fhir.rest.api.QualifiedParamList;
public interface IQueryParameterOr<T extends IQueryParameterType> extends Serializable { public interface IQueryParameterOr<T extends IQueryParameterType> extends Serializable {
public void setValuesAsQueryTokens(FhirContext theContext, String theParamName, QualifiedParamList theParameters); void setValuesAsQueryTokens(FhirContext theContext, String theParamName, QualifiedParamList theParameters);
public List<T> getValuesAsQueryTokens(); List<T> getValuesAsQueryTokens();
} }

View File

@ -23,30 +23,27 @@ package ca.uhn.fhir.rest.param;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.IQueryParameterOr; import ca.uhn.fhir.model.api.IQueryParameterOr;
import ca.uhn.fhir.model.api.TemporalPrecisionEnum; 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.BaseDateTimeDt;
import ca.uhn.fhir.model.primitive.DateDt; import ca.uhn.fhir.model.primitive.DateDt;
import ca.uhn.fhir.model.primitive.DateTimeDt; 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.api.QualifiedParamList;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.util.ObjectUtil;
import ca.uhn.fhir.util.ValidateUtil; 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.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle; import org.apache.commons.lang3.builder.ToStringStyle;
import org.apache.commons.lang3.time.DateUtils;
import org.hl7.fhir.instance.model.api.IPrimitiveType; 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; import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class DateParam extends BaseParamWithPrefix<DateParam> implements /*IQueryParameterType , */IQueryParameterOr<DateParam> { public class DateParam extends BaseParamWithPrefix<DateParam> implements /*IQueryParameterType , */IQueryParameterOr<DateParam> {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private final DateParamDateTimeHolder myValue = new DateParamDateTimeHolder(); private final DateParamDateTimeHolder myValue = new DateParamDateTimeHolder();
/** /**
@ -119,9 +116,7 @@ public class DateParam extends BaseParamWithPrefix<DateParam> implements /*IQuer
b.append(ParameterUtil.escapeWithDefault(getPrefix().getValue())); b.append(ParameterUtil.escapeWithDefault(getPrefix().getValue()));
} }
if (myValue != null) { b.append(ParameterUtil.escapeWithDefault(myValue.getValueAsString()));
b.append(ParameterUtil.escapeWithDefault(myValue.getValueAsString()));
}
return b.toString(); return b.toString();
} }
@ -132,38 +127,15 @@ public class DateParam extends BaseParamWithPrefix<DateParam> implements /*IQuer
} }
public TemporalPrecisionEnum getPrecision() { public TemporalPrecisionEnum getPrecision() {
if (myValue != null) {
return myValue.getPrecision(); return myValue.getPrecision();
}
return null;
} }
public Date getValue() { public Date getValue() {
if (myValue != null) {
return myValue.getValue(); 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() { public String getValueAsString() {
if (myValue != null) {
return myValue.getValueAsString(); return myValue.getValueAsString();
}
return null;
} }
@Override @Override
@ -260,7 +232,7 @@ public class DateParam extends BaseParamWithPrefix<DateParam> implements /*IQuer
/** /**
* Constructor * Constructor
*/ */
public DateParamDateTimeHolder() { DateParamDateTimeHolder() {
super(); super();
} }

View File

@ -36,9 +36,17 @@ public class TokenAndListParam extends BaseAndListParam<TokenOrListParam> {
return this; 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"); 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; return this;
} }
} }

View File

@ -1071,7 +1071,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
public IBundleProvider search(final SearchParameterMap theParams, RequestDetails theRequestDetails, HttpServletResponse theServletResponse) { public IBundleProvider search(final SearchParameterMap theParams, RequestDetails theRequestDetails, HttpServletResponse theServletResponse) {
if (myDaoConfig.getIndexMissingFields() == DaoConfig.IndexEnabledEnum.DISABLED) { if (myDaoConfig.getIndexMissingFields() == DaoConfig.IndexEnabledEnum.DISABLED) {
for (List<List<? extends IQueryParameterType>> nextAnds : theParams.values()) { for (List<List<IQueryParameterType>> nextAnds : theParams.values()) {
for (List<? extends IQueryParameterType> nextOrs : nextAnds) { for (List<? extends IQueryParameterType> nextOrs : nextAnds) {
for (IQueryParameterType next : nextOrs) { for (IQueryParameterType next : nextOrs) {
if (next.getMissing() != null) { if (next.getMissing() != null) {

View File

@ -81,7 +81,7 @@ public class FulltextSearchSvcImpl implements IFulltextSearchSvc {
super(); super();
} }
private void addTextSearch(QueryBuilder theQueryBuilder, BooleanJunction<?> theBoolean, List<List<? extends IQueryParameterType>> theTerms, String theFieldName, String theFieldNameEdgeNGram, String theFieldNameNGram) { private void addTextSearch(QueryBuilder theQueryBuilder, BooleanJunction<?> theBoolean, List<List<IQueryParameterType>> theTerms, String theFieldName, String theFieldNameEdgeNGram, String theFieldNameNGram) {
if (theTerms == null) { if (theTerms == null) {
return; return;
} }
@ -171,13 +171,13 @@ public class FulltextSearchSvcImpl implements IFulltextSearchSvc {
/* /*
* Handle _content parameter (resource body content) * Handle _content parameter (resource body content)
*/ */
List<List<? extends IQueryParameterType>> contentAndTerms = theParams.remove(Constants.PARAM_CONTENT); List<List<IQueryParameterType>> contentAndTerms = theParams.remove(Constants.PARAM_CONTENT);
addTextSearch(qb, bool, contentAndTerms, "myContentText", "myContentTextEdgeNGram", "myContentTextNGram"); addTextSearch(qb, bool, contentAndTerms, "myContentText", "myContentTextEdgeNGram", "myContentTextNGram");
/* /*
* Handle _text parameter (resource narrative content) * Handle _text parameter (resource narrative content)
*/ */
List<List<? extends IQueryParameterType>> textAndTerms = theParams.remove(Constants.PARAM_TEXT); List<List<IQueryParameterType>> textAndTerms = theParams.remove(Constants.PARAM_TEXT);
addTextSearch(qb, bool, textAndTerms, "myNarrativeText", "myNarrativeTextEdgeNGram", "myNarrativeTextNGram"); addTextSearch(qb, bool, textAndTerms, "myNarrativeText", "myNarrativeTextEdgeNGram", "myNarrativeTextNGram");
if (theReferencingPid != null) { if (theReferencingPid != null) {

View File

@ -9,9 +9,9 @@ package ca.uhn.fhir.jpa.dao;
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 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.MatchUrlService;
import ca.uhn.fhir.jpa.searchparam.ResourceMetaParams; import ca.uhn.fhir.jpa.searchparam.ResourceMetaParams;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; 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.searchparam.registry.ISearchParamRegistry;
import ca.uhn.fhir.jpa.term.IHapiTerminologySvc; import ca.uhn.fhir.jpa.term.IHapiTerminologySvc;
import ca.uhn.fhir.jpa.term.VersionIndependentConcept; import ca.uhn.fhir.jpa.term.VersionIndependentConcept;
@ -91,7 +90,6 @@ import java.math.BigDecimal;
import java.math.MathContext; import java.math.MathContext;
import java.util.*; import java.util.*;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.*; import static org.apache.commons.lang3.StringUtils.*;
@ -158,6 +156,7 @@ public class SearchBuilder implements ISearchBuilder {
private int myFetchSize; private int myFetchSize;
private Integer myMaxResultsToFetch; private Integer myMaxResultsToFetch;
private Set<Long> myPidSet; private Set<Long> myPidSet;
private boolean myHaveIndexJoins = false;
/** /**
* Constructor * Constructor
@ -213,7 +212,7 @@ public class SearchBuilder implements ISearchBuilder {
} }
private void addPredicateHas(List<List<? extends IQueryParameterType>> theHasParameters) { private void addPredicateHas(List<List<IQueryParameterType>> theHasParameters) {
for (List<? extends IQueryParameterType> nextOrList : theHasParameters) { for (List<? extends IQueryParameterType> nextOrList : theHasParameters) {
@ -274,7 +273,7 @@ public class SearchBuilder implements ISearchBuilder {
} }
} }
private void addPredicateLanguage(List<List<? extends IQueryParameterType>> theList) { private void addPredicateLanguage(List<List<IQueryParameterType>> theList) {
for (List<? extends IQueryParameterType> nextList : theList) { for (List<? extends IQueryParameterType> nextList : theList) {
Set<String> values = new HashSet<>(); Set<String> values = new HashSet<>();
@ -286,7 +285,7 @@ public class SearchBuilder implements ISearchBuilder {
} }
values.add(nextValue); values.add(nextValue);
} else { } 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<ResourceTable> subQfrom = subQ.from(ResourceTable.class); Root<ResourceTable> subQfrom = subQ.from(ResourceTable.class);
subQ.select(subQfrom.get("myId").as(Long.class)); subQ.select(subQfrom.get("myId").as(Long.class));
List<List<? extends IQueryParameterType>> andOrParams = new ArrayList<>(); List<List<IQueryParameterType>> andOrParams = new ArrayList<>();
andOrParams.add(theOrValues); andOrParams.add(theOrValues);
/* /*
@ -641,7 +640,7 @@ public class SearchBuilder implements ISearchBuilder {
return chainValue; return chainValue;
} }
private void addPredicateResourceId(List<List<? extends IQueryParameterType>> theValues) { private void addPredicateResourceId(List<List<IQueryParameterType>> theValues) {
for (List<? extends IQueryParameterType> nextValue : theValues) { for (List<? extends IQueryParameterType> nextValue : theValues) {
Set<Long> orPids = new HashSet<>(); Set<Long> orPids = new HashSet<>();
for (IQueryParameterType next : nextValue) { for (IQueryParameterType next : nextValue) {
@ -701,7 +700,7 @@ public class SearchBuilder implements ISearchBuilder {
} }
private void addPredicateTag(List<List<? extends IQueryParameterType>> theList, String theParamName) { private void addPredicateTag(List<List<IQueryParameterType>> theList, String theParamName) {
TagTypeEnum tagType; TagTypeEnum tagType;
if (Constants.PARAM_TAG.equals(theParamName)) { if (Constants.PARAM_TAG.equals(theParamName)) {
tagType = TagTypeEnum.TAG; tagType = TagTypeEnum.TAG;
@ -1013,13 +1012,7 @@ public class SearchBuilder implements ISearchBuilder {
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private <T> Join<ResourceTable, T> createOrReuseJoin(JoinEnum theType, String theSearchParameterName) { private <T> Join<ResourceTable, T> createJoin(JoinEnum theType, String theSearchParameterName) {
JoinKey key = new JoinKey(theSearchParameterName, theType);
return (Join<ResourceTable, T>) myIndexJoins.computeIfAbsent(key, k -> createJoin(theType, theSearchParameterName));
}
@SuppressWarnings("unchecked")
private <T> Join<ResourceTable, T> createJoin(JoinEnum theType, String theSearchParameterName) {
Join<ResourceTable, ResourceIndexedSearchParamDate> join = null; Join<ResourceTable, ResourceIndexedSearchParamDate> join = null;
switch (theType) { switch (theType) {
case DATE: case DATE:
@ -1044,6 +1037,11 @@ public class SearchBuilder implements ISearchBuilder {
join = myResourceTableRoot.join("myParamsToken", JoinType.LEFT); join = myResourceTableRoot.join("myParamsToken", JoinType.LEFT);
break; break;
} }
JoinKey key = new JoinKey(theSearchParameterName, theType);
myIndexJoins.put(key, join);
myHaveIndexJoins = true;
return (Join<ResourceTable, T>) join; return (Join<ResourceTable, T>) join;
} }
@ -1531,51 +1529,6 @@ public class SearchBuilder implements ISearchBuilder {
myBuilder = myEntityManager.getCriteriaBuilder(); myBuilder = myEntityManager.getCriteriaBuilder();
mySearchUuid = theSearchUuid; 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<String> paramNames = theParams.keySet();
if (paramNames.isEmpty() == false) {
List<JpaRuntimeSearchParam> searchParams = mySearchParamRegistry.getActiveUniqueSearchParams(myResourceName, paramNames);
if (searchParams.size() > 0) {
List<List<String>> params = new ArrayList<>();
for (Entry<String, List<List<? extends IQueryParameterType>>> nextParamNameToValues : theParams.entrySet()) {
String nextParamName = nextParamNameToValues.getKey();
nextParamName = UrlUtil.escapeUrlParam(nextParamName);
for (List<? extends IQueryParameterType> nextAnd : nextParamNameToValues.getValue()) {
ArrayList<String> nextValueList = new ArrayList<>();
params.add(nextValueList);
for (IQueryParameterType nextOr : nextAnd) {
String nextOrValue = nextOr.getValueAsQueryToken(myContext);
nextOrValue = UrlUtil.escapeUrlParam(nextOrValue);
nextValueList.add(nextParamName + "=" + nextOrValue);
}
}
}
Set<String> uniqueQueryStrings = ResourceIndexedSearchParams.extractCompositeStringUniquesValueChains(myResourceName, params);
if (ourTrackHandlersForUnitTest) {
ourLastHandlerParamsForUnitTest = theParams;
ourLastHandlerMechanismForUnitTest = HandlerTypeEnum.UNIQUE_INDEX;
ourLastHandlerThreadForUnitTest = Thread.currentThread().getName();
}
return new UniqueIndexIterator(uniqueQueryStrings);
}
}
}
}
}
}
}
if (ourTrackHandlersForUnitTest) { if (ourTrackHandlersForUnitTest) {
ourLastHandlerParamsForUnitTest = theParams; ourLastHandlerParamsForUnitTest = theParams;
ourLastHandlerMechanismForUnitTest = HandlerTypeEnum.STANDARD_QUERY; ourLastHandlerMechanismForUnitTest = HandlerTypeEnum.STANDARD_QUERY;
@ -1602,31 +1555,6 @@ public class SearchBuilder implements ISearchBuilder {
if (sort != null) { if (sort != null) {
assert !theCount; assert !theCount;
// outerQuery = myBuilder.createQuery(Long.class);
// Root<ResourceTable> outerQueryFrom = outerQuery.from(ResourceTable.class);
//
// List<Order> orders = Lists.newArrayList();
// List<Predicate> predicates = Lists.newArrayList();
//
// createSort(myBuilder, outerQueryFrom, sort, orders, predicates);
// if (orders.size() > 0) {
// outerQuery.orderBy(orders);
// }
//
// Subquery<Long> subQ = outerQuery.subquery(Long.class);
// Root<ResourceTable> subQfrom = subQ.from(ResourceTable.class);
//
// myResourceTableQuery = subQ;
// myResourceTableRoot = subQfrom;
//
// Expression<Long> 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); outerQuery = myBuilder.createQuery(Long.class);
myResourceTableQuery = outerQuery; myResourceTableQuery = outerQuery;
myResourceTableRoot = myResourceTableQuery.from(ResourceTable.class); myResourceTableRoot = myResourceTableQuery.from(ResourceTable.class);
@ -1644,7 +1572,6 @@ public class SearchBuilder implements ISearchBuilder {
outerQuery.orderBy(orders); outerQuery.orderBy(orders);
} }
} else { } else {
outerQuery = myBuilder.createQuery(Long.class); 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 * If we have any joins to index tables, we get this behaviour already guaranteed so we don't
* need an explicit predicate for it. * need an explicit predicate for it.
*/ */
if (myIndexJoins.isEmpty()) { if (!myHaveIndexJoins) {
if (myParams.getEverythingMode() == null) { if (myParams.getEverythingMode() == null) {
myPredicates.add(myBuilder.equal(myResourceTableRoot.get("myResourceType"), myResourceName)); myPredicates.add(myBuilder.equal(myResourceTableRoot.get("myResourceType"), myResourceName));
} }
@ -2186,17 +2113,111 @@ public class SearchBuilder implements ISearchBuilder {
private void searchForIdsWithAndOr(@Nonnull SearchParameterMap theParams) { private void searchForIdsWithAndOr(@Nonnull SearchParameterMap theParams) {
myParams = theParams; myParams = theParams;
// Remove any empty parameters
theParams.clean(); theParams.clean();
for (Entry<String, List<List<? extends IQueryParameterType>>> nextParamEntry : myParams.entrySet()) {
String nextParamName = nextParamEntry.getKey();
List<List<? extends IQueryParameterType>> 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<JpaRuntimeSearchParam> activeUniqueSearchParams = mySearchParamRegistry.getActiveUniqueSearchParams(myResourceName, theParams.keySet());
if (activeUniqueSearchParams.size() > 0) {
StringBuilder sb = new StringBuilder();
sb.append(myResourceName);
sb.append("?");
boolean first = true;
ArrayList<String> keys = new ArrayList<>(theParams.keySet());
Collections.sort(keys);
for (String nextParamName : keys) {
List<List<IQueryParameterType>> 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<? extends IQueryParameterType> 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<String, List<List<IQueryParameterType>>> nextParamEntry : myParams.entrySet()) {
String nextParamName = nextParamEntry.getKey();
List<List<IQueryParameterType>> andOrParams = nextParamEntry.getValue();
searchForIdsWithAndOr(myResourceName, nextParamName, andOrParams);
} }
} }
private void searchForIdsWithAndOr(String theResourceName, String theParamName, List<List<? extends IQueryParameterType>> theAndOrParams) {
private <T> void ensureSubListsAreWritable(List<List<T>> theListOfLists) {
for (int i = 0; i < theListOfLists.size(); i++) {
List<T> oldSubList = theListOfLists.get(i);
if (!(oldSubList instanceof ArrayList)) {
List<T> newSubList = new ArrayList<>(oldSubList);
theListOfLists.set(i, newSubList);
}
}
}
private void addPredicateCompositeStringUnique(@Nonnull SearchParameterMap theParams, String theIndexdString) {
myHaveIndexJoins = true;
Join<ResourceTable, ResourceIndexedCompositeStringUnique> 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<List<IQueryParameterType>> theAndOrParams) {
if (theAndOrParams.isEmpty()) { if (theAndOrParams.isEmpty()) {
return; return;
@ -2566,47 +2587,6 @@ public class SearchBuilder implements ISearchBuilder {
} }
private class UniqueIndexIterator implements IResultIterator {
private final Set<String> myUniqueQueryStrings;
private Iterator<Long> myWrap = null;
UniqueIndexIterator(Set<String> 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<Long> 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<Long> { private static class CountQueryIterator implements Iterator<Long> {
private final TypedQuery<Long> myQuery; private final TypedQuery<Long> myQuery;

View File

@ -23,7 +23,6 @@ package ca.uhn.fhir.jpa.dao.index;
import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.model.entity.*; import ca.uhn.fhir.jpa.model.entity.*;
import ca.uhn.fhir.jpa.searchparam.extractor.ResourceIndexedSearchParams; import ca.uhn.fhir.jpa.searchparam.extractor.ResourceIndexedSearchParams;
import org.apache.commons.lang3.Validate;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -44,17 +43,17 @@ public class DaoSearchParamSynchronizer {
public void synchronizeSearchParamsToDatabase(ResourceIndexedSearchParams theParams, ResourceTable theEntity, ResourceIndexedSearchParams existingParams) { public void synchronizeSearchParamsToDatabase(ResourceIndexedSearchParams theParams, ResourceTable theEntity, ResourceIndexedSearchParams existingParams) {
synchronize(theParams, theEntity, theParams.stringParams, existingParams.stringParams); synchronize(theParams, theEntity, theParams.myStringParams, existingParams.myStringParams);
synchronize(theParams, theEntity, theParams.tokenParams, existingParams.tokenParams); synchronize(theParams, theEntity, theParams.myTokenParams, existingParams.myTokenParams);
synchronize(theParams, theEntity, theParams.numberParams, existingParams.numberParams); synchronize(theParams, theEntity, theParams.myNumberParams, existingParams.myNumberParams);
synchronize(theParams, theEntity, theParams.quantityParams, existingParams.quantityParams); synchronize(theParams, theEntity, theParams.myQuantityParams, existingParams.myQuantityParams);
synchronize(theParams, theEntity, theParams.dateParams, existingParams.dateParams); synchronize(theParams, theEntity, theParams.myDateParams, existingParams.myDateParams);
synchronize(theParams, theEntity, theParams.uriParams, existingParams.uriParams); synchronize(theParams, theEntity, theParams.myUriParams, existingParams.myUriParams);
synchronize(theParams, theEntity, theParams.coordsParams, existingParams.coordsParams); synchronize(theParams, theEntity, theParams.myCoordsParams, existingParams.myCoordsParams);
synchronize(theParams, theEntity, theParams.links, existingParams.links); synchronize(theParams, theEntity, theParams.myLinks, existingParams.myLinks);
// make sure links are indexed // make sure links are indexed
theEntity.setResourceLinks(theParams.links); theEntity.setResourceLinks(theParams.myLinks);
} }
private <T extends BaseResourceIndex> void synchronize(ResourceIndexedSearchParams theParams, ResourceTable theEntity, Collection<T> theNewParms, Collection<T> theExistingParms) { private <T extends BaseResourceIndex> void synchronize(ResourceIndexedSearchParams theParams, ResourceTable theEntity, Collection<T> theNewParms, Collection<T> theExistingParms) {

View File

@ -106,9 +106,9 @@ public class SearchParamWithInlineReferencesExtractor {
*/ */
for (Iterator<ResourceLink> existingLinkIter = existingParams.getResourceLinks().iterator(); existingLinkIter.hasNext(); ) { for (Iterator<ResourceLink> existingLinkIter = existingParams.getResourceLinks().iterator(); existingLinkIter.hasNext(); ) {
ResourceLink nextExisting = existingLinkIter.next(); ResourceLink nextExisting = existingLinkIter.next();
if (theParams.links.remove(nextExisting)) { if (theParams.myLinks.remove(nextExisting)) {
existingLinkIter.remove(); existingLinkIter.remove();
theParams.links.add(nextExisting); theParams.myLinks.add(nextExisting);
} }
} }
@ -133,28 +133,28 @@ public class SearchParamWithInlineReferencesExtractor {
Collection<String> linksForCompositePartWantPaths = null; Collection<String> linksForCompositePartWantPaths = null;
switch (nextCompositeOf.getParamType()) { switch (nextCompositeOf.getParamType()) {
case NUMBER: case NUMBER:
paramsListForCompositePart = theParams.numberParams; paramsListForCompositePart = theParams.myNumberParams;
break; break;
case DATE: case DATE:
paramsListForCompositePart = theParams.dateParams; paramsListForCompositePart = theParams.myDateParams;
break; break;
case STRING: case STRING:
paramsListForCompositePart = theParams.stringParams; paramsListForCompositePart = theParams.myStringParams;
break; break;
case TOKEN: case TOKEN:
paramsListForCompositePart = theParams.tokenParams; paramsListForCompositePart = theParams.myTokenParams;
break; break;
case REFERENCE: case REFERENCE:
linksForCompositePart = theParams.links; linksForCompositePart = theParams.myLinks;
linksForCompositePartWantPaths = new HashSet<>(); linksForCompositePartWantPaths = new HashSet<>(nextCompositeOf.getPathsSplit());
linksForCompositePartWantPaths.addAll(nextCompositeOf.getPathsSplit());
break; break;
case QUANTITY: case QUANTITY:
paramsListForCompositePart = theParams.quantityParams; paramsListForCompositePart = theParams.myQuantityParams;
break; break;
case URI: case URI:
paramsListForCompositePart = theParams.uriParams; paramsListForCompositePart = theParams.myUriParams;
break; break;
case SPECIAL:
case COMPOSITE: case COMPOSITE:
case HAS: case HAS:
break; break;
@ -189,11 +189,13 @@ public class SearchParamWithInlineReferencesExtractor {
} }
} }
Set<String> queryStringsToPopulate = theParams.extractCompositeStringUniquesValueChains(resourceType, partsChoices); Set<String> queryStringsToPopulate = ResourceIndexedSearchParams.extractCompositeStringUniquesValueChains(resourceType, partsChoices);
for (String nextQueryString : queryStringsToPopulate) { for (String nextQueryString : queryStringsToPopulate) {
if (isNotBlank(nextQueryString)) { 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 * 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. * matching resource.
*/ */
public void extractInlineReferences(IBaseResource theResource) { public void extractInlineReferences(IBaseResource theResource) {
if (!myDaoConfig.isAllowInlineMatchUrlReferences()) { if (!myDaoConfig.isAllowInlineMatchUrlReferences()) {
return; return;
@ -258,12 +259,12 @@ public class SearchParamWithInlineReferencesExtractor {
// Store composite string uniques // Store composite string uniques
if (myDaoConfig.isUniqueIndexesEnabled()) { 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); ourLog.debug("Removing unique index: {}", next);
myEntityManager.remove(next); myEntityManager.remove(next);
theEntity.getParamsCompositeStringUnique().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()) { if (myDaoConfig.isUniqueIndexesCheckedBeforeSave()) {
ResourceIndexedCompositeStringUnique existing = myResourceIndexedCompositeStringUniqueDao.findByQueryString(next.getIndexString()); ResourceIndexedCompositeStringUnique existing = myResourceIndexedCompositeStringUniqueDao.findByQueryString(next.getIndexString());
if (existing != null) { if (existing != null) {

View File

@ -100,9 +100,16 @@ public abstract class BaseCaptureQueriesListener implements ProxyDataSourceBuild
if (theInlineParams) { if (theInlineParams) {
List<String> nextParams = new ArrayList<>(myParams); List<String> nextParams = new ArrayList<>(myParams);
while (retVal.contains("?") && nextParams.size() > 0) {
int idx = retVal.indexOf("?"); int idx = 0;
retVal = retVal.substring(0, idx) + "'" + nextParams.remove(0) + "'" + retVal.substring(idx + 1); 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();
} }
} }

View File

@ -104,6 +104,18 @@ public class CircularQueueCaptureQueriesListener extends BaseCaptureQueriesListe
ourLog.info("Select Queries:\n{}", String.join("\n", queries)); 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 * Log all captured INSERT queries
*/ */

View File

@ -32,10 +32,10 @@ public class ExpungeOptions {
@Override @Override
public String toString() { public String toString() {
return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE) return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
.append("myLimit", myLimit) .append("limit", myLimit)
.append("myExpungeOldVersions", myExpungeOldVersions) .append("oldVersions", myExpungeOldVersions)
.append("myExpungeDeletedResources", myExpungeDeletedResources) .append("deletedResources", myExpungeDeletedResources)
.append("myExpungeEverything", myExpungeEverything) .append("everything", myExpungeEverything)
.toString(); .toString();
} }

View File

@ -10,6 +10,8 @@ import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.DateParam; 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.param.TokenParam;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException; import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
@ -44,6 +46,8 @@ import static org.junit.Assert.*;
public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test { public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4UniqueSearchParamTest.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4UniqueSearchParamTest.class);
@Autowired
private ISearchParamRegistry mySearchParamRegistry;
@After @After
public void after() { public void after() {
@ -101,7 +105,6 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
SearchBuilder.resetLastHandlerMechanismForUnitTest(); SearchBuilder.resetLastHandlerMechanismForUnitTest();
} }
private void createUniqueIndexCoverageBeneficiary() { private void createUniqueIndexCoverageBeneficiary() {
SearchParameter sp = new SearchParameter(); SearchParameter sp = new SearchParameter();
sp.setId("SearchParameter/coverage-beneficiary"); sp.setId("SearchParameter/coverage-beneficiary");
@ -141,7 +144,6 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
mySearchParamRegistry.forceRefresh(); mySearchParamRegistry.forceRefresh();
} }
private void createUniqueIndexObservationSubject() { private void createUniqueIndexObservationSubject() {
SearchParameter sp = new SearchParameter(); SearchParameter sp = new SearchParameter();
@ -170,7 +172,6 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
mySearchParamRegistry.forceRefresh(); mySearchParamRegistry.forceRefresh();
} }
private void createUniqueIndexPatientIdentifier() { private void createUniqueIndexPatientIdentifier() {
SearchParameter sp = new SearchParameter(); SearchParameter sp = new SearchParameter();
@ -199,7 +200,6 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
mySearchParamRegistry.forceRefresh(); mySearchParamRegistry.forceRefresh();
} }
private void createUniqueIndexPatientIdentifierCount1() { private void createUniqueIndexPatientIdentifierCount1() {
SearchParameter sp = new SearchParameter(); SearchParameter sp = new SearchParameter();
@ -331,9 +331,209 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
assertEquals("gender", params.get(0).getCompositeOf().get(1).getName()); 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 @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(); createUniqueIndexPatientIdentifier();
Patient pt = new Patient(); Patient pt = new Patient();
@ -369,12 +569,10 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
.setIfNoneExist("/Patient?identifier=urn|111,urn|222"); .setIfNoneExist("/Patient?identifier=urn|111,urn|222");
mySystemDao.transaction(mySrd, input); mySystemDao.transaction(mySrd, input);
new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { // Make sure entries are saved
@Override runInTransaction(() -> {
protected void doInTransactionWithoutResult(@Nonnull TransactionStatus status) { List<ResourceIndexedCompositeStringUnique> all = myResourceIndexedCompositeStringUniqueDao.findAll();
List<ResourceIndexedCompositeStringUnique> all = myResourceIndexedCompositeStringUniqueDao.findAll(); assertEquals(2, all.size());
assertEquals(2, all.size());
}
}); });
} }
@ -424,10 +622,6 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
} }
@Autowired
private ISearchParamRegistry mySearchParamRegistry;
@Test @Test
public void testDuplicateUniqueValuesAreReIndexed() { public void testDuplicateUniqueValuesAreReIndexed() {
myDaoConfig.setSchedulingDisabled(true); myDaoConfig.setSchedulingDisabled(true);
@ -767,12 +961,14 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
pt2.setBirthDateElement(new DateType("2011-01-02")); pt2.setBirthDateElement(new DateType("2011-01-02"));
myPatientDao.create(pt2).getId().toUnqualifiedVersionless(); myPatientDao.create(pt2).getId().toUnqualifiedVersionless();
myCaptureQueriesListener.clear();
SearchBuilder.resetLastHandlerMechanismForUnitTest(); SearchBuilder.resetLastHandlerMechanismForUnitTest();
SearchParameterMap params = new SearchParameterMap(); SearchParameterMap params = new SearchParameterMap();
params.setLoadSynchronousUpTo(100); params.setLoadSynchronousUpTo(100);
params.add("gender", new TokenParam("http://hl7.org/fhir/administrative-gender", "male")); params.add("gender", new TokenParam("http://hl7.org/fhir/administrative-gender", "male"));
params.add("birthdate", new DateParam("2011-01-01")); params.add("birthdate", new DateParam("2011-01-01"));
IBundleProvider results = myPatientDao.search(params); IBundleProvider results = myPatientDao.search(params);
myCaptureQueriesListener.logFirstSelectQueryForCurrentThread();
assertThat(toUnqualifiedVersionlessIdValues(results), containsInAnyOrder(id1.getValue())); assertThat(toUnqualifiedVersionlessIdValues(results), containsInAnyOrder(id1.getValue()));
assertEquals(SearchBuilder.getLastHandlerParamsForUnitTest(), SearchBuilder.HandlerTypeEnum.UNIQUE_INDEX, SearchBuilder.getLastHandlerMechanismForUnitTest()); assertEquals(SearchBuilder.getLastHandlerParamsForUnitTest(), SearchBuilder.HandlerTypeEnum.UNIQUE_INDEX, SearchBuilder.getLastHandlerMechanismForUnitTest());
} }

View File

@ -49,7 +49,7 @@ public class LoggingRule implements TestRule {
try { try {
statement.evaluate(); statement.evaluate();
} catch (final Throwable e) { } 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; throw e;
} finally { } finally {
logger.info(MessageFormat.format("Finished test case [{0}]", description.getDisplayName())); logger.info(MessageFormat.format("Finished test case [{0}]", description.getDisplayName()));

View File

@ -45,7 +45,7 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class SearchParameterMap implements Serializable { public class SearchParameterMap implements Serializable {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchParameterMap.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchParameterMap.class);
private final HashMap<String, List<List<? extends IQueryParameterType>>> mySearchParameterMap = new LinkedHashMap<>(); private final HashMap<String, List<List<IQueryParameterType>>> mySearchParameterMap = new LinkedHashMap<>();
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@ -107,7 +107,7 @@ public class SearchParameterMap implements Serializable {
if (next == null) { if (next == null) {
continue; continue;
} }
get(theName).add(next.getValuesAsQueryTokens()); get(theName).add((List<IQueryParameterType>) next.getValuesAsQueryTokens());
} }
} }
@ -119,10 +119,10 @@ public class SearchParameterMap implements Serializable {
put(theName, new ArrayList<>()); put(theName, new ArrayList<>());
} }
get(theName).add(theOr.getValuesAsQueryTokens()); get(theName).add((List<IQueryParameterType>) theOr.getValuesAsQueryTokens());
} }
public Collection<List<List<? extends IQueryParameterType>>> values() { public Collection<List<List<IQueryParameterType>>> values() {
return mySearchParameterMap.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 * This will only return true if all parameters have no modifier of any kind
*/ */
public boolean isAllParametersHaveNoModifier() { public boolean isAllParametersHaveNoModifier() {
for (List<List<? extends IQueryParameterType>> nextParamName : values()) { for (List<List<IQueryParameterType>> nextParamName : values()) {
for (List<? extends IQueryParameterType> nextAnd : nextParamName) { for (List<IQueryParameterType> nextAnd : nextParamName) {
for (IQueryParameterType nextOr : nextAnd) { for (IQueryParameterType nextOr : nextAnd) {
if (isNotBlank(nextOr.getQueryParameterQualifier())) { if (isNotBlank(nextOr.getQueryParameterQualifier())) {
return false; return false;
@ -319,7 +319,7 @@ public class SearchParameterMap implements Serializable {
Collections.sort(keys); Collections.sort(keys);
for (String nextKey : keys) { for (String nextKey : keys) {
List<List<? extends IQueryParameterType>> nextValuesAndsIn = get(nextKey); List<List<IQueryParameterType>> nextValuesAndsIn = get(nextKey);
List<List<IQueryParameterType>> nextValuesAndsOut = new ArrayList<>(); List<List<IQueryParameterType>> nextValuesAndsOut = new ArrayList<>();
for (List<? extends IQueryParameterType> nextValuesAndIn : nextValuesAndsIn) { for (List<? extends IQueryParameterType> nextValuesAndIn : nextValuesAndsIn) {
@ -448,9 +448,9 @@ public class SearchParameterMap implements Serializable {
} }
public void clean() { public void clean() {
for (Map.Entry<String, List<List<? extends IQueryParameterType>>> nextParamEntry : this.entrySet()) { for (Map.Entry<String, List<List<IQueryParameterType>>> nextParamEntry : this.entrySet()) {
String nextParamName = nextParamEntry.getKey(); String nextParamName = nextParamEntry.getKey();
List<List<? extends IQueryParameterType>> andOrParams = nextParamEntry.getValue(); List<List<IQueryParameterType>> andOrParams = nextParamEntry.getValue();
clean(nextParamName, andOrParams); clean(nextParamName, andOrParams);
} }
} }
@ -458,7 +458,7 @@ public class SearchParameterMap implements Serializable {
/* /*
* Filter out * Filter out
*/ */
private void clean(String theParamName, List<List<? extends IQueryParameterType>> theAndOrParams) { private void clean(String theParamName, List<List<IQueryParameterType>> theAndOrParams) {
for (int andListIdx = 0; andListIdx < theAndOrParams.size(); andListIdx++) { for (int andListIdx = 0; andListIdx < theAndOrParams.size(); andListIdx++) {
List<? extends IQueryParameterType> nextOrList = theAndOrParams.get(andListIdx); List<? extends IQueryParameterType> nextOrList = theAndOrParams.get(andListIdx);
@ -603,11 +603,11 @@ public class SearchParameterMap implements Serializable {
// Wrapper methods // Wrapper methods
public List<List<? extends IQueryParameterType>> get(String theName) { public List<List<IQueryParameterType>> get(String theName) {
return mySearchParameterMap.get(theName); return mySearchParameterMap.get(theName);
} }
private void put(String theName, List<List<? extends IQueryParameterType>> theParams) { private void put(String theName, List<List<IQueryParameterType>> theParams) {
mySearchParameterMap.put(theName, theParams); mySearchParameterMap.put(theName, theParams);
} }
@ -623,11 +623,11 @@ public class SearchParameterMap implements Serializable {
return mySearchParameterMap.isEmpty(); return mySearchParameterMap.isEmpty();
} }
public Set<Map.Entry<String, List<List<? extends IQueryParameterType>>>> entrySet() { public Set<Map.Entry<String, List<List<IQueryParameterType>>>> entrySet() {
return mySearchParameterMap.entrySet(); return mySearchParameterMap.entrySet();
} }
public List<List<? extends IQueryParameterType>> remove(String theName) { public List<List<IQueryParameterType>> remove(String theName) {
return mySearchParameterMap.remove(theName); return mySearchParameterMap.remove(theName);
} }
} }

View File

@ -37,86 +37,86 @@ import static org.apache.commons.lang3.StringUtils.compare;
public final class ResourceIndexedSearchParams { public final class ResourceIndexedSearchParams {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResourceIndexedSearchParams.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResourceIndexedSearchParams.class);
final public Collection<ResourceIndexedSearchParamString> stringParams = new ArrayList<>(); final public Collection<ResourceIndexedSearchParamString> myStringParams = new ArrayList<>();
final public Collection<ResourceIndexedSearchParamToken> tokenParams = new HashSet<>(); final public Collection<ResourceIndexedSearchParamToken> myTokenParams = new HashSet<>();
final public Collection<ResourceIndexedSearchParamNumber> numberParams = new ArrayList<>(); final public Collection<ResourceIndexedSearchParamNumber> myNumberParams = new ArrayList<>();
final public Collection<ResourceIndexedSearchParamQuantity> quantityParams = new ArrayList<>(); final public Collection<ResourceIndexedSearchParamQuantity> myQuantityParams = new ArrayList<>();
final public Collection<ResourceIndexedSearchParamDate> dateParams = new ArrayList<>(); final public Collection<ResourceIndexedSearchParamDate> myDateParams = new ArrayList<>();
final public Collection<ResourceIndexedSearchParamUri> uriParams = new ArrayList<>(); final public Collection<ResourceIndexedSearchParamUri> myUriParams = new ArrayList<>();
final public Collection<ResourceIndexedSearchParamCoords> coordsParams = new ArrayList<>(); final public Collection<ResourceIndexedSearchParamCoords> myCoordsParams = new ArrayList<>();
final public Collection<ResourceIndexedCompositeStringUnique> compositeStringUniques = new HashSet<>(); final public Collection<ResourceIndexedCompositeStringUnique> myCompositeStringUniques = new HashSet<>();
final public Collection<ResourceLink> links = new HashSet<>(); final public Collection<ResourceLink> myLinks = new HashSet<>();
final public Set<String> populatedResourceLinkParameters = new HashSet<>(); final public Set<String> myPopulatedResourceLinkParameters = new HashSet<>();
public ResourceIndexedSearchParams() { public ResourceIndexedSearchParams() {
} }
public ResourceIndexedSearchParams(ResourceTable theEntity) { public ResourceIndexedSearchParams(ResourceTable theEntity) {
if (theEntity.isParamsStringPopulated()) { if (theEntity.isParamsStringPopulated()) {
stringParams.addAll(theEntity.getParamsString()); myStringParams.addAll(theEntity.getParamsString());
} }
if (theEntity.isParamsTokenPopulated()) { if (theEntity.isParamsTokenPopulated()) {
tokenParams.addAll(theEntity.getParamsToken()); myTokenParams.addAll(theEntity.getParamsToken());
} }
if (theEntity.isParamsNumberPopulated()) { if (theEntity.isParamsNumberPopulated()) {
numberParams.addAll(theEntity.getParamsNumber()); myNumberParams.addAll(theEntity.getParamsNumber());
} }
if (theEntity.isParamsQuantityPopulated()) { if (theEntity.isParamsQuantityPopulated()) {
quantityParams.addAll(theEntity.getParamsQuantity()); myQuantityParams.addAll(theEntity.getParamsQuantity());
} }
if (theEntity.isParamsDatePopulated()) { if (theEntity.isParamsDatePopulated()) {
dateParams.addAll(theEntity.getParamsDate()); myDateParams.addAll(theEntity.getParamsDate());
} }
if (theEntity.isParamsUriPopulated()) { if (theEntity.isParamsUriPopulated()) {
uriParams.addAll(theEntity.getParamsUri()); myUriParams.addAll(theEntity.getParamsUri());
} }
if (theEntity.isParamsCoordsPopulated()) { if (theEntity.isParamsCoordsPopulated()) {
coordsParams.addAll(theEntity.getParamsCoords()); myCoordsParams.addAll(theEntity.getParamsCoords());
} }
if (theEntity.isHasLinks()) { if (theEntity.isHasLinks()) {
links.addAll(theEntity.getResourceLinks()); myLinks.addAll(theEntity.getResourceLinks());
} }
if (theEntity.isParamsCompositeStringUniquePresent()) { if (theEntity.isParamsCompositeStringUniquePresent()) {
compositeStringUniques.addAll(theEntity.getParamsCompositeStringUnique()); myCompositeStringUniques.addAll(theEntity.getParamsCompositeStringUnique());
} }
} }
public Collection<ResourceLink> getResourceLinks() { public Collection<ResourceLink> getResourceLinks() {
return links; return myLinks;
} }
public void setParamsOn(ResourceTable theEntity) { public void setParamsOn(ResourceTable theEntity) {
theEntity.setParamsString(stringParams); theEntity.setParamsString(myStringParams);
theEntity.setParamsStringPopulated(stringParams.isEmpty() == false); theEntity.setParamsStringPopulated(myStringParams.isEmpty() == false);
theEntity.setParamsToken(tokenParams); theEntity.setParamsToken(myTokenParams);
theEntity.setParamsTokenPopulated(tokenParams.isEmpty() == false); theEntity.setParamsTokenPopulated(myTokenParams.isEmpty() == false);
theEntity.setParamsNumber(numberParams); theEntity.setParamsNumber(myNumberParams);
theEntity.setParamsNumberPopulated(numberParams.isEmpty() == false); theEntity.setParamsNumberPopulated(myNumberParams.isEmpty() == false);
theEntity.setParamsQuantity(quantityParams); theEntity.setParamsQuantity(myQuantityParams);
theEntity.setParamsQuantityPopulated(quantityParams.isEmpty() == false); theEntity.setParamsQuantityPopulated(myQuantityParams.isEmpty() == false);
theEntity.setParamsDate(dateParams); theEntity.setParamsDate(myDateParams);
theEntity.setParamsDatePopulated(dateParams.isEmpty() == false); theEntity.setParamsDatePopulated(myDateParams.isEmpty() == false);
theEntity.setParamsUri(uriParams); theEntity.setParamsUri(myUriParams);
theEntity.setParamsUriPopulated(uriParams.isEmpty() == false); theEntity.setParamsUriPopulated(myUriParams.isEmpty() == false);
theEntity.setParamsCoords(coordsParams); theEntity.setParamsCoords(myCoordsParams);
theEntity.setParamsCoordsPopulated(coordsParams.isEmpty() == false); theEntity.setParamsCoordsPopulated(myCoordsParams.isEmpty() == false);
theEntity.setParamsCompositeStringUniquePresent(compositeStringUniques.isEmpty() == false); theEntity.setParamsCompositeStringUniquePresent(myCompositeStringUniques.isEmpty() == false);
theEntity.setResourceLinks(links); theEntity.setResourceLinks(myLinks);
theEntity.setHasLinks(links.isEmpty() == false); theEntity.setHasLinks(myLinks.isEmpty() == false);
} }
public void setUpdatedTime(Date theUpdateTime) { public void setUpdatedTime(Date theUpdateTime) {
setUpdatedTime(stringParams, theUpdateTime); setUpdatedTime(myStringParams, theUpdateTime);
setUpdatedTime(numberParams, theUpdateTime); setUpdatedTime(myNumberParams, theUpdateTime);
setUpdatedTime(quantityParams, theUpdateTime); setUpdatedTime(myQuantityParams, theUpdateTime);
setUpdatedTime(dateParams, theUpdateTime); setUpdatedTime(myDateParams, theUpdateTime);
setUpdatedTime(uriParams, theUpdateTime); setUpdatedTime(myUriParams, theUpdateTime);
setUpdatedTime(coordsParams, theUpdateTime); setUpdatedTime(myCoordsParams, theUpdateTime);
setUpdatedTime(tokenParams, theUpdateTime); setUpdatedTime(myTokenParams, theUpdateTime);
} }
private void setUpdatedTime(Collection<? extends BaseResourceIndexedSearchParam> theParams, Date theUpdateTime) { private void setUpdatedTime(Collection<? extends BaseResourceIndexedSearchParam> theParams, Date theUpdateTime) {
@ -215,7 +215,7 @@ public final class ResourceIndexedSearchParams {
} }
public Set<String> getPopulatedResourceLinkParameters() { public Set<String> getPopulatedResourceLinkParameters() {
return populatedResourceLinkParameters; return myPopulatedResourceLinkParameters;
} }
public boolean matchParam(String theResourceName, String theParamName, RuntimeSearchParam theParamDef, IQueryParameterType theParam) { public boolean matchParam(String theResourceName, String theParamName, RuntimeSearchParam theParamDef, IQueryParameterType theParam) {
@ -225,22 +225,22 @@ public final class ResourceIndexedSearchParams {
Collection<? extends BaseResourceIndexedSearchParam> resourceParams; Collection<? extends BaseResourceIndexedSearchParam> resourceParams;
switch (theParamDef.getParamType()) { switch (theParamDef.getParamType()) {
case TOKEN: case TOKEN:
resourceParams = tokenParams; resourceParams = myTokenParams;
break; break;
case QUANTITY: case QUANTITY:
resourceParams = quantityParams; resourceParams = myQuantityParams;
break; break;
case STRING: case STRING:
resourceParams = stringParams; resourceParams = myStringParams;
break; break;
case NUMBER: case NUMBER:
resourceParams = numberParams; resourceParams = myNumberParams;
break; break;
case URI: case URI:
resourceParams = uriParams; resourceParams = myUriParams;
break; break;
case DATE: case DATE:
resourceParams = dateParams; resourceParams = myDateParams;
break; break;
case REFERENCE: case REFERENCE:
return matchResourceLinks(theResourceName, theParamName, theParam, theParamDef.getPath()); return matchResourceLinks(theResourceName, theParamName, theParam, theParamDef.getPath());
@ -267,7 +267,7 @@ public final class ResourceIndexedSearchParams {
resourceLinkMatches(theResourceName, resourceLink, theParamName, theParamPath) resourceLinkMatches(theResourceName, resourceLink, theParamName, theParamPath)
&& resourceIdMatches(resourceLink, reference); && resourceIdMatches(resourceLink, reference);
return links.stream().anyMatch(namedParamPredicate); return myLinks.stream().anyMatch(namedParamPredicate);
} }
private boolean resourceIdMatches(ResourceLink theResourceLink, ReferenceParam theReference) { private boolean resourceIdMatches(ResourceLink theResourceLink, ReferenceParam theReference) {
@ -293,25 +293,25 @@ public final class ResourceIndexedSearchParams {
@Override @Override
public String toString() { public String toString() {
return "ResourceIndexedSearchParams{" + return "ResourceIndexedSearchParams{" +
"stringParams=" + stringParams + "stringParams=" + myStringParams +
", tokenParams=" + tokenParams + ", tokenParams=" + myTokenParams +
", numberParams=" + numberParams + ", numberParams=" + myNumberParams +
", quantityParams=" + quantityParams + ", quantityParams=" + myQuantityParams +
", dateParams=" + dateParams + ", dateParams=" + myDateParams +
", uriParams=" + uriParams + ", uriParams=" + myUriParams +
", coordsParams=" + coordsParams + ", coordsParams=" + myCoordsParams +
", compositeStringUniques=" + compositeStringUniques + ", compositeStringUniques=" + myCompositeStringUniques +
", links=" + links + ", links=" + myLinks +
'}'; '}';
} }
public void findMissingSearchParams(ModelConfig theModelConfig, ResourceTable theEntity, Set<Entry<String, RuntimeSearchParam>> theActiveSearchParams) { public void findMissingSearchParams(ModelConfig theModelConfig, ResourceTable theEntity, Set<Entry<String, RuntimeSearchParam>> theActiveSearchParams) {
findMissingSearchParams(theModelConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.STRING, stringParams); findMissingSearchParams(theModelConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.STRING, myStringParams);
findMissingSearchParams(theModelConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.NUMBER, numberParams); findMissingSearchParams(theModelConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.NUMBER, myNumberParams);
findMissingSearchParams(theModelConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.QUANTITY, quantityParams); findMissingSearchParams(theModelConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.QUANTITY, myQuantityParams);
findMissingSearchParams(theModelConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.DATE, dateParams); findMissingSearchParams(theModelConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.DATE, myDateParams);
findMissingSearchParams(theModelConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.URI, uriParams); findMissingSearchParams(theModelConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.URI, myUriParams);
findMissingSearchParams(theModelConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.TOKEN, tokenParams); findMissingSearchParams(theModelConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.TOKEN, myTokenParams);
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")

View File

@ -74,7 +74,7 @@ public class ResourceLinkExtractor {
extractResourceLinks(theParams, theEntity, theResource, theUpdateTime, theResourceLinkResolver, resourceType, nextSpDef, theFailOnInvalidReference); 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) { 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)) { if (LogicalReferenceHelper.isLogicalReference(myModelConfig, nextId)) {
ResourceLink resourceLink = new ResourceLink(nextPathAndRef.getPath(), theEntity, nextId, theUpdateTime); 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); ourLog.debug("Indexing remote resource reference URL: {}", nextId);
} }
return; return;
@ -195,7 +195,7 @@ public class ResourceLinkExtractor {
throw new InvalidRequestException(msg); throw new InvalidRequestException(msg);
} else { } else {
ResourceLink resourceLink = new ResourceLink(nextPathAndRef.getPath(), theEntity, nextId, theUpdateTime); 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); ourLog.debug("Indexing remote resource reference URL: {}", nextId);
} }
return; return;
@ -217,7 +217,7 @@ public class ResourceLinkExtractor {
theResourceLinkResolver.validateTypeOrThrowException(type); theResourceLinkResolver.validateTypeOrThrowException(type);
ResourceLink resourceLink = createResourceLink(theEntity, theUpdateTime, theResourceLinkResolver, nextSpDef, theNextPathsUnsplit, nextPathAndRef, nextId, typeString, type, id); ResourceLink resourceLink = createResourceLink(theEntity, theUpdateTime, theResourceLinkResolver, nextSpDef, theNextPathsUnsplit, nextPathAndRef, nextId, typeString, type, id);
if (resourceLink == null) return; 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<? extends IBaseResource> theType, String theId) { private ResourceLink createResourceLink(ResourceTable theEntity, Date theUpdateTime, IResourceLinkResolver theResourceLinkResolver, RuntimeSearchParam nextSpDef, String theNextPathsUnsplit, PathAndRef nextPathAndRef, IIdType theNextId, String theTypeString, Class<? extends IBaseResource> theType, String theId) {

View File

@ -37,20 +37,20 @@ public class SearchParamExtractorService {
private ISearchParamExtractor mySearchParamExtractor; private ISearchParamExtractor mySearchParamExtractor;
public void extractFromResource(ResourceIndexedSearchParams theParams, ResourceTable theEntity, IBaseResource theResource) { public void extractFromResource(ResourceIndexedSearchParams theParams, ResourceTable theEntity, IBaseResource theResource) {
theParams.stringParams.addAll(extractSearchParamStrings(theEntity, theResource)); theParams.myStringParams.addAll(extractSearchParamStrings(theEntity, theResource));
theParams.numberParams.addAll(extractSearchParamNumber(theEntity, theResource)); theParams.myNumberParams.addAll(extractSearchParamNumber(theEntity, theResource));
theParams.quantityParams.addAll(extractSearchParamQuantity(theEntity, theResource)); theParams.myQuantityParams.addAll(extractSearchParamQuantity(theEntity, theResource));
theParams.dateParams.addAll(extractSearchParamDates(theEntity, theResource)); theParams.myDateParams.addAll(extractSearchParamDates(theEntity, theResource));
theParams.uriParams.addAll(extractSearchParamUri(theEntity, theResource)); theParams.myUriParams.addAll(extractSearchParamUri(theEntity, theResource));
theParams.coordsParams.addAll(extractSearchParamCoords(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)) { for (BaseResourceIndexedSearchParam next : extractSearchParamTokens(theEntity, theResource)) {
if (next instanceof ResourceIndexedSearchParamToken) { if (next instanceof ResourceIndexedSearchParamToken) {
theParams.tokenParams.add((ResourceIndexedSearchParamToken) next); theParams.myTokenParams.add((ResourceIndexedSearchParamToken) next);
} else { } else {
theParams.stringParams.add((ResourceIndexedSearchParamString) next); theParams.myStringParams.add((ResourceIndexedSearchParamString) next);
} }
} }
} }

View File

@ -169,12 +169,7 @@ public abstract class BaseSearchParamRegistry<SP extends IBaseResource> implemen
} }
if (next.getCompositeOf() != null) { if (next.getCompositeOf() != null) {
next.getCompositeOf().sort(new Comparator<RuntimeSearchParam>() { next.getCompositeOf().sort((theO1, theO2) -> StringUtils.compare(theO1.getName(), theO2.getName()));
@Override
public int compare(RuntimeSearchParam theO1, RuntimeSearchParam theO2) {
return StringUtils.compare(theO1.getName(), theO2.getName());
}
});
for (String nextBase : next.getBase()) { for (String nextBase : next.getBase()) {
if (!activeParamNamesToUniqueSearchParams.containsKey(nextBase)) { if (!activeParamNamesToUniqueSearchParams.containsKey(nextBase)) {
activeParamNamesToUniqueSearchParams.put(nextBase, new HashMap<>()); activeParamNamesToUniqueSearchParams.put(nextBase, new HashMap<>());

View File

@ -47,7 +47,6 @@ import java.util.function.Predicate;
@Service @Service
public class CriteriaResourceMatcher { public class CriteriaResourceMatcher {
private static final String CRITERIA = "CRITERIA";
@Autowired @Autowired
private MatchUrlService myMatchUrlService; private MatchUrlService myMatchUrlService;
@Autowired @Autowired
@ -83,9 +82,9 @@ public class CriteriaResourceMatcher {
return SubscriptionMatchResult.unsupportedFromParameterAndReason(Constants.PARAM_LASTUPDATED, SubscriptionMatchResult.STANDARD_PARAMETER); return SubscriptionMatchResult.unsupportedFromParameterAndReason(Constants.PARAM_LASTUPDATED, SubscriptionMatchResult.STANDARD_PARAMETER);
} }
for (Map.Entry<String, List<List<? extends IQueryParameterType>>> entry : searchParameterMap.entrySet()) { for (Map.Entry<String, List<List<IQueryParameterType>>> entry : searchParameterMap.entrySet()) {
String theParamName = entry.getKey(); String theParamName = entry.getKey();
List<List<? extends IQueryParameterType>> theAndOrParams = entry.getValue(); List<List<IQueryParameterType>> theAndOrParams = entry.getValue();
SubscriptionMatchResult result = matchIdsWithAndOr(theParamName, theAndOrParams, resourceDefinition, theResource, theSearchParams); SubscriptionMatchResult result = matchIdsWithAndOr(theParamName, theAndOrParams, resourceDefinition, theResource, theSearchParams);
if (!result.matched()){ if (!result.matched()){
return result; return result;
@ -95,7 +94,7 @@ public class CriteriaResourceMatcher {
} }
// This method is modelled from SearchBuilder.searchForIdsWithAndOr() // This method is modelled from SearchBuilder.searchForIdsWithAndOr()
private SubscriptionMatchResult matchIdsWithAndOr(String theParamName, List<List<? extends IQueryParameterType>> theAndOrParams, RuntimeResourceDefinition theResourceDefinition, IBaseResource theResource, ResourceIndexedSearchParams theSearchParams) { private SubscriptionMatchResult matchIdsWithAndOr(String theParamName, List<List<IQueryParameterType>> theAndOrParams, RuntimeResourceDefinition theResourceDefinition, IBaseResource theResource, ResourceIndexedSearchParams theSearchParams) {
if (theAndOrParams.isEmpty()) { if (theAndOrParams.isEmpty()) {
return SubscriptionMatchResult.successfulMatch(); return SubscriptionMatchResult.successfulMatch();
} }
@ -132,13 +131,13 @@ public class CriteriaResourceMatcher {
} }
} }
private boolean matchIdsAndOr(List<List<? extends IQueryParameterType>> theAndOrParams, IBaseResource theResource) { private boolean matchIdsAndOr(List<List<IQueryParameterType>> theAndOrParams, IBaseResource theResource) {
if (theResource == null) { if (theResource == null) {
return true; return true;
} }
return theAndOrParams.stream().allMatch(nextAnd -> matchIdsOr(nextAnd, theResource)); return theAndOrParams.stream().allMatch(nextAnd -> matchIdsOr(nextAnd, theResource));
} }
private boolean matchIdsOr(List<? extends IQueryParameterType> theOrParams, IBaseResource theResource) { private boolean matchIdsOr(List<IQueryParameterType> theOrParams, IBaseResource theResource) {
if (theResource == null) { if (theResource == null) {
return true; return true;
} }
@ -149,7 +148,7 @@ public class CriteriaResourceMatcher {
return theValue.equals(theId.getValue()) || theValue.equals(theId.getIdPart()); return theValue.equals(theId.getValue()) || theValue.equals(theId.getIdPart());
} }
private SubscriptionMatchResult matchResourceParam(String theParamName, List<List<? extends IQueryParameterType>> theAndOrParams, ResourceIndexedSearchParams theSearchParams, String theResourceName, RuntimeSearchParam theParamDef) { private SubscriptionMatchResult matchResourceParam(String theParamName, List<List<IQueryParameterType>> theAndOrParams, ResourceIndexedSearchParams theSearchParams, String theResourceName, RuntimeSearchParam theParamDef) {
if (theParamDef != null) { if (theParamDef != null) {
switch (theParamDef.getParamType()) { switch (theParamDef.getParamType()) {
case QUANTITY: case QUANTITY:
@ -183,15 +182,15 @@ public class CriteriaResourceMatcher {
return theNextAnd.stream().anyMatch(token -> theSearchParams.matchParam(theResourceName, theParamName, paramDef, token)); return theNextAnd.stream().anyMatch(token -> theSearchParams.matchParam(theResourceName, theParamName, paramDef, token));
} }
private boolean hasChain(List<List<? extends IQueryParameterType>> theAndOrParams) { private boolean hasChain(List<List<IQueryParameterType>> theAndOrParams) {
return theAndOrParams.stream().flatMap(List::stream).anyMatch(param -> param instanceof ReferenceParam && ((ReferenceParam)param).getChain() != null); return theAndOrParams.stream().flatMap(List::stream).anyMatch(param -> param instanceof ReferenceParam && ((ReferenceParam)param).getChain() != null);
} }
private boolean hasQualifiers(List<List<? extends IQueryParameterType>> theAndOrParams) { private boolean hasQualifiers(List<List<IQueryParameterType>> theAndOrParams) {
return theAndOrParams.stream().flatMap(List::stream).anyMatch(param -> param.getQueryParameterQualifier() != null); return theAndOrParams.stream().flatMap(List::stream).anyMatch(param -> param.getQueryParameterQualifier() != null);
} }
private boolean hasPrefixes(List<List<? extends IQueryParameterType>> theAndOrParams) { private boolean hasPrefixes(List<List<IQueryParameterType>> theAndOrParams) {
Predicate<IQueryParameterType> hasPrefixPredicate = param -> param instanceof BaseParamWithPrefix && Predicate<IQueryParameterType> hasPrefixPredicate = param -> param instanceof BaseParamWithPrefix &&
((BaseParamWithPrefix) param).getPrefix() != null; ((BaseParamWithPrefix) param).getPrefix() != null;
return theAndOrParams.stream().flatMap(List::stream).anyMatch(hasPrefixPredicate); return theAndOrParams.stream().flatMap(List::stream).anyMatch(hasPrefixPredicate);

View File

@ -90,6 +90,16 @@
HapiLocalizer can now handle message patterns with braces that aren't a part of a HapiLocalizer can now handle message patterns with braces that aren't a part of a
message format expression. E.g. "Here is an {example}". message format expression. E.g. "Here is an {example}".
</action> </action>
<action type="add">
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.
</action>
<action type="fix">
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.
</action>
</release> </release>
<release version="3.7.0" date="2019-02-06" description="Gale"> <release version="3.7.0" date="2019-02-06" description="Gale">
<action type="add"> <action type="add">