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 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.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<DateParam> implements /*IQueryParameterType , */IQueryParameterOr<DateParam> {
private static final long serialVersionUID = 1L;
private final DateParamDateTimeHolder myValue = new DateParamDateTimeHolder();
/**
@ -119,9 +116,7 @@ public class DateParam extends BaseParamWithPrefix<DateParam> 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<DateParam> 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<DateParam> implements /*IQuer
/**
* Constructor
*/
public DateParamDateTimeHolder() {
DateParamDateTimeHolder() {
super();
}

View File

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

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) {
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 (IQueryParameterType next : nextOrs) {
if (next.getMissing() != null) {

View File

@ -81,7 +81,7 @@ public class FulltextSearchSvcImpl implements IFulltextSearchSvc {
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) {
return;
}
@ -171,13 +171,13 @@ public class FulltextSearchSvcImpl implements IFulltextSearchSvc {
/*
* 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");
/*
* 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");
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");
* 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<Long> myPidSet;
private boolean myHaveIndexJoins = false;
/**
* 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) {
@ -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) {
Set<String> 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<ResourceTable> subQfrom = subQ.from(ResourceTable.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);
/*
@ -641,7 +640,7 @@ public class SearchBuilder implements ISearchBuilder {
return chainValue;
}
private void addPredicateResourceId(List<List<? extends IQueryParameterType>> theValues) {
private void addPredicateResourceId(List<List<IQueryParameterType>> theValues) {
for (List<? extends IQueryParameterType> nextValue : theValues) {
Set<Long> orPids = new HashSet<>();
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;
if (Constants.PARAM_TAG.equals(theParamName)) {
tagType = TagTypeEnum.TAG;
@ -1013,13 +1012,7 @@ public class SearchBuilder implements ISearchBuilder {
}
@SuppressWarnings("unchecked")
private <T> Join<ResourceTable, T> createOrReuseJoin(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) {
private <T> Join<ResourceTable, T> createJoin(JoinEnum theType, String theSearchParameterName) {
Join<ResourceTable, ResourceIndexedSearchParamDate> 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<ResourceTable, T>) 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<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) {
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<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);
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<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()) {
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 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.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 <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(); ) {
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<String> 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<String> queryStringsToPopulate = theParams.extractCompositeStringUniquesValueChains(resourceType, partsChoices);
Set<String> 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) {

View File

@ -100,9 +100,16 @@ public abstract class BaseCaptureQueriesListener implements ProxyDataSourceBuild
if (theInlineParams) {
List<String> 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();
}
}

View File

@ -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
*/

View File

@ -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();
}

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.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<ResourceIndexedCompositeStringUnique> all = myResourceIndexedCompositeStringUniqueDao.findAll();
assertEquals(2, all.size());
}
// Make sure entries are saved
runInTransaction(() -> {
List<ResourceIndexedCompositeStringUnique> 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());
}

View File

@ -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()));

View File

@ -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<String, List<List<? extends IQueryParameterType>>> mySearchParameterMap = new LinkedHashMap<>();
private final HashMap<String, List<List<IQueryParameterType>>> 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<IQueryParameterType>) next.getValuesAsQueryTokens());
}
}
@ -119,10 +119,10 @@ public class SearchParameterMap implements Serializable {
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();
}
@ -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<List<? extends IQueryParameterType>> nextParamName : values()) {
for (List<? extends IQueryParameterType> nextAnd : nextParamName) {
for (List<List<IQueryParameterType>> nextParamName : values()) {
for (List<IQueryParameterType> 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<List<? extends IQueryParameterType>> nextValuesAndsIn = get(nextKey);
List<List<IQueryParameterType>> nextValuesAndsIn = get(nextKey);
List<List<IQueryParameterType>> nextValuesAndsOut = new ArrayList<>();
for (List<? extends IQueryParameterType> nextValuesAndIn : nextValuesAndsIn) {
@ -448,9 +448,9 @@ public class SearchParameterMap implements Serializable {
}
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();
List<List<? extends IQueryParameterType>> andOrParams = nextParamEntry.getValue();
List<List<IQueryParameterType>> andOrParams = nextParamEntry.getValue();
clean(nextParamName, andOrParams);
}
}
@ -458,7 +458,7 @@ public class SearchParameterMap implements Serializable {
/*
* 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++) {
List<? extends IQueryParameterType> nextOrList = theAndOrParams.get(andListIdx);
@ -603,11 +603,11 @@ public class SearchParameterMap implements Serializable {
// Wrapper methods
public List<List<? extends IQueryParameterType>> get(String theName) {
public List<List<IQueryParameterType>> get(String 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);
}
@ -623,11 +623,11 @@ public class SearchParameterMap implements Serializable {
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();
}
public List<List<? extends IQueryParameterType>> remove(String theName) {
public List<List<IQueryParameterType>> remove(String theName) {
return mySearchParameterMap.remove(theName);
}
}

View File

@ -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<ResourceIndexedSearchParamString> stringParams = new ArrayList<>();
final public Collection<ResourceIndexedSearchParamToken> tokenParams = new HashSet<>();
final public Collection<ResourceIndexedSearchParamNumber> numberParams = new ArrayList<>();
final public Collection<ResourceIndexedSearchParamQuantity> quantityParams = new ArrayList<>();
final public Collection<ResourceIndexedSearchParamDate> dateParams = new ArrayList<>();
final public Collection<ResourceIndexedSearchParamUri> uriParams = new ArrayList<>();
final public Collection<ResourceIndexedSearchParamCoords> coordsParams = new ArrayList<>();
final public Collection<ResourceIndexedSearchParamString> myStringParams = new ArrayList<>();
final public Collection<ResourceIndexedSearchParamToken> myTokenParams = new HashSet<>();
final public Collection<ResourceIndexedSearchParamNumber> myNumberParams = new ArrayList<>();
final public Collection<ResourceIndexedSearchParamQuantity> myQuantityParams = new ArrayList<>();
final public Collection<ResourceIndexedSearchParamDate> myDateParams = new ArrayList<>();
final public Collection<ResourceIndexedSearchParamUri> myUriParams = new ArrayList<>();
final public Collection<ResourceIndexedSearchParamCoords> myCoordsParams = new ArrayList<>();
final public Collection<ResourceIndexedCompositeStringUnique> compositeStringUniques = new HashSet<>();
final public Collection<ResourceLink> links = new HashSet<>();
final public Set<String> populatedResourceLinkParameters = new HashSet<>();
final public Collection<ResourceIndexedCompositeStringUnique> myCompositeStringUniques = new HashSet<>();
final public Collection<ResourceLink> myLinks = new HashSet<>();
final public Set<String> 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<ResourceLink> 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<? extends BaseResourceIndexedSearchParam> theParams, Date theUpdateTime) {
@ -215,7 +215,7 @@ public final class ResourceIndexedSearchParams {
}
public Set<String> 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<? extends BaseResourceIndexedSearchParam> 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<Entry<String, RuntimeSearchParam>> 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")

View File

@ -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<? extends IBaseResource> theType, String theId) {

View File

@ -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);
}
}
}

View File

@ -169,12 +169,7 @@ public abstract class BaseSearchParamRegistry<SP extends IBaseResource> implemen
}
if (next.getCompositeOf() != null) {
next.getCompositeOf().sort(new Comparator<RuntimeSearchParam>() {
@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<>());

View File

@ -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<String, List<List<? extends IQueryParameterType>>> entry : searchParameterMap.entrySet()) {
for (Map.Entry<String, List<List<IQueryParameterType>>> entry : searchParameterMap.entrySet()) {
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);
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<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()) {
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) {
return true;
}
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) {
return true;
}
@ -149,7 +148,7 @@ public class CriteriaResourceMatcher {
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) {
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<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);
}
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);
}
private boolean hasPrefixes(List<List<? extends IQueryParameterType>> theAndOrParams) {
private boolean hasPrefixes(List<List<IQueryParameterType>> theAndOrParams) {
Predicate<IQueryParameterType> hasPrefixPredicate = param -> param instanceof BaseParamWithPrefix &&
((BaseParamWithPrefix) param).getPrefix() != null;
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
message format expression. E.g. "Here is an {example}".
</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 version="3.7.0" date="2019-02-06" description="Gale">
<action type="add">