Merge branch 'master' into windows-fixes-2
This commit is contained in:
commit
6b601708dd
|
@ -255,7 +255,15 @@ public class DateParam extends BaseParamWithPrefix<DateParam> implements /*IQuer
|
||||||
return b.build();
|
return b.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public class DateParamDateTimeHolder extends BaseDateTimeDt {
|
public static class DateParamDateTimeHolder extends BaseDateTimeDt {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*/
|
||||||
|
public DateParamDateTimeHolder() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected TemporalPrecisionEnum getDefaultPrecisionForDatatype() {
|
protected TemporalPrecisionEnum getDefaultPrecisionForDatatype() {
|
||||||
return TemporalPrecisionEnum.SECOND;
|
return TemporalPrecisionEnum.SECOND;
|
||||||
|
|
|
@ -80,7 +80,6 @@ import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.context.annotation.Scope;
|
import org.springframework.context.annotation.Scope;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.thymeleaf.util.ListUtils;
|
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
@ -385,7 +384,8 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
|
|
||||||
List<Predicate> codePredicates = new ArrayList<>();
|
List<Predicate> codePredicates = new ArrayList<>();
|
||||||
|
|
||||||
for (IQueryParameterType nextOr : theList) {
|
for (int orIdx = 0; orIdx < theList.size(); orIdx++) {
|
||||||
|
IQueryParameterType nextOr = theList.get(orIdx);
|
||||||
|
|
||||||
if (nextOr instanceof ReferenceParam) {
|
if (nextOr instanceof ReferenceParam) {
|
||||||
ReferenceParam ref = (ReferenceParam) nextOr;
|
ReferenceParam ref = (ReferenceParam) nextOr;
|
||||||
|
@ -496,15 +496,16 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
|
|
||||||
boolean foundChainMatch = false;
|
boolean foundChainMatch = false;
|
||||||
|
|
||||||
String chain = ref.getChain();
|
|
||||||
String remainingChain = null;
|
|
||||||
int chainDotIndex = chain.indexOf('.');
|
|
||||||
if (chainDotIndex != -1) {
|
|
||||||
remainingChain = chain.substring(chainDotIndex + 1);
|
|
||||||
chain = chain.substring(0, chainDotIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Class<? extends IBaseResource> nextType : resourceTypes) {
|
for (Class<? extends IBaseResource> nextType : resourceTypes) {
|
||||||
|
|
||||||
|
String chain = ref.getChain();
|
||||||
|
String remainingChain = null;
|
||||||
|
int chainDotIndex = chain.indexOf('.');
|
||||||
|
if (chainDotIndex != -1) {
|
||||||
|
remainingChain = chain.substring(chainDotIndex + 1);
|
||||||
|
chain = chain.substring(0, chainDotIndex);
|
||||||
|
}
|
||||||
|
|
||||||
RuntimeResourceDefinition typeDef = myContext.getResourceDefinition(nextType);
|
RuntimeResourceDefinition typeDef = myContext.getResourceDefinition(nextType);
|
||||||
String subResourceName = typeDef.getName();
|
String subResourceName = typeDef.getName();
|
||||||
|
|
||||||
|
@ -531,37 +532,29 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
IQueryParameterType chainValue;
|
ArrayList<IQueryParameterType> orValues = Lists.newArrayList();
|
||||||
if (remainingChain != null) {
|
|
||||||
if (param == null || param.getParamType() != RestSearchParameterTypeEnum.REFERENCE) {
|
for (IQueryParameterType next : theList) {
|
||||||
ourLog.debug("Type {} parameter {} is not a reference, can not chain {}", nextType.getSimpleName(), chain, remainingChain);
|
String nextValue = next.getValueAsQueryToken(myContext);
|
||||||
|
IQueryParameterType chainValue = mapReferenceChainToRawParamType(remainingChain, param, theParamName, qualifier, nextType, chain, isMeta, nextValue);
|
||||||
|
if (chainValue == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
foundChainMatch = true;
|
||||||
chainValue = new ReferenceParam();
|
orValues.add(chainValue);
|
||||||
chainValue.setValueAsQueryToken(myContext, theParamName, qualifier, resourceId);
|
|
||||||
((ReferenceParam) chainValue).setChain(remainingChain);
|
|
||||||
} else if (isMeta) {
|
|
||||||
IQueryParameterType type = myMatchUrlService.newInstanceType(chain);
|
|
||||||
type.setValueAsQueryToken(myContext, theParamName, qualifier, resourceId);
|
|
||||||
chainValue = type;
|
|
||||||
} else {
|
|
||||||
chainValue = toParameterType(param, qualifier, resourceId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
foundChainMatch = true;
|
|
||||||
|
|
||||||
Subquery<Long> subQ = myResourceTableQuery.subquery(Long.class);
|
Subquery<Long> subQ = myResourceTableQuery.subquery(Long.class);
|
||||||
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<? extends IQueryParameterType>> andOrParams = new ArrayList<>();
|
||||||
andOrParams.add(Collections.singletonList(chainValue));
|
andOrParams.add(orValues);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* We're doing a chain call, so push the current query root
|
* We're doing a chain call, so push the current query root
|
||||||
* and predicate list down and put new ones at the top of the
|
* and predicate list down and put new ones at the top of the
|
||||||
* stack and run a subuery
|
* stack and run a subquery
|
||||||
*/
|
*/
|
||||||
Root<ResourceTable> stackRoot = myResourceTableRoot;
|
Root<ResourceTable> stackRoot = myResourceTableRoot;
|
||||||
ArrayList<Predicate> stackPredicates = myPredicates;
|
ArrayList<Predicate> stackPredicates = myPredicates;
|
||||||
|
@ -573,9 +566,11 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
// Create the subquery predicates
|
// Create the subquery predicates
|
||||||
myPredicates.add(myBuilder.equal(myResourceTableRoot.get("myResourceType"), subResourceName));
|
myPredicates.add(myBuilder.equal(myResourceTableRoot.get("myResourceType"), subResourceName));
|
||||||
myPredicates.add(myBuilder.isNull(myResourceTableRoot.get("myDeleted")));
|
myPredicates.add(myBuilder.isNull(myResourceTableRoot.get("myDeleted")));
|
||||||
searchForIdsWithAndOr(subResourceName, chain, andOrParams);
|
|
||||||
|
|
||||||
subQ.where(toArray(myPredicates));
|
if (foundChainMatch) {
|
||||||
|
searchForIdsWithAndOr(subResourceName, chain, andOrParams);
|
||||||
|
subQ.where(toArray(myPredicates));
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Pop the old query root and predicate list back
|
* Pop the old query root and predicate list back
|
||||||
|
@ -593,6 +588,10 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
if (!foundChainMatch) {
|
if (!foundChainMatch) {
|
||||||
throw new InvalidRequestException(myContext.getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "invalidParameterChain", theParamName + '.' + ref.getChain()));
|
throw new InvalidRequestException(myContext.getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "invalidParameterChain", theParamName + '.' + ref.getChain()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
myPredicates.add(myBuilder.or(toArray(codePredicates)));
|
||||||
|
return;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
@ -604,6 +603,28 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
myPredicates.add(myBuilder.or(toArray(codePredicates)));
|
myPredicates.add(myBuilder.or(toArray(codePredicates)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IQueryParameterType mapReferenceChainToRawParamType(String remainingChain, RuntimeSearchParam param, String theParamName, String qualifier, Class<? extends IBaseResource> nextType, String chain, boolean isMeta, String resourceId) {
|
||||||
|
IQueryParameterType chainValue;
|
||||||
|
if (remainingChain != null) {
|
||||||
|
if (param == null || param.getParamType() != RestSearchParameterTypeEnum.REFERENCE) {
|
||||||
|
ourLog.debug("Type {} parameter {} is not a reference, can not chain {}", nextType.getSimpleName(), chain, remainingChain);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
chainValue = new ReferenceParam();
|
||||||
|
chainValue.setValueAsQueryToken(myContext, theParamName, qualifier, resourceId);
|
||||||
|
((ReferenceParam) chainValue).setChain(remainingChain);
|
||||||
|
} else if (isMeta) {
|
||||||
|
IQueryParameterType type = myMatchUrlService.newInstanceType(chain);
|
||||||
|
type.setValueAsQueryToken(myContext, theParamName, qualifier, resourceId);
|
||||||
|
chainValue = type;
|
||||||
|
} else {
|
||||||
|
chainValue = toParameterType(param, qualifier, resourceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return chainValue;
|
||||||
|
}
|
||||||
|
|
||||||
private void addPredicateResourceId(List<List<? extends IQueryParameterType>> theValues) {
|
private void addPredicateResourceId(List<List<? extends IQueryParameterType>> theValues) {
|
||||||
for (List<? extends IQueryParameterType> nextValue : theValues) {
|
for (List<? extends IQueryParameterType> nextValue : theValues) {
|
||||||
Set<Long> orPids = new HashSet<>();
|
Set<Long> orPids = new HashSet<>();
|
||||||
|
@ -794,24 +815,27 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
|
|
||||||
private void addPredicateToken(String theResourceName, String theParamName, List<? extends IQueryParameterType> theList) {
|
private void addPredicateToken(String theResourceName, String theParamName, List<? extends IQueryParameterType> theList) {
|
||||||
|
|
||||||
Join<ResourceTable, ResourceIndexedSearchParamToken> join = createOrReuseJoin(JoinEnum.TOKEN, theParamName);
|
|
||||||
|
|
||||||
if (theList.get(0).getMissing() != null) {
|
if (theList.get(0).getMissing() != null) {
|
||||||
|
Join<ResourceTable, ResourceIndexedSearchParamToken> join = createOrReuseJoin(JoinEnum.TOKEN, theParamName);
|
||||||
addPredicateParamMissing(theResourceName, theParamName, theList.get(0).getMissing(), join);
|
addPredicateParamMissing(theResourceName, theParamName, theList.get(0).getMissing(), join);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Predicate> codePredicates = new ArrayList<>();
|
List<Predicate> codePredicates = new ArrayList<>();
|
||||||
|
Join<ResourceTable, ResourceIndexedSearchParamToken> join = null;
|
||||||
for (IQueryParameterType nextOr : theList) {
|
for (IQueryParameterType nextOr : theList) {
|
||||||
|
|
||||||
if (nextOr instanceof TokenParam) {
|
if (nextOr instanceof TokenParam) {
|
||||||
TokenParam id = (TokenParam) nextOr;
|
TokenParam id = (TokenParam) nextOr;
|
||||||
if (id.isText()) {
|
if (id.isText()) {
|
||||||
addPredicateString(theResourceName, theParamName, theList);
|
addPredicateString(theResourceName, theParamName, theList);
|
||||||
continue;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (join == null) {
|
||||||
|
join = createOrReuseJoin(JoinEnum.TOKEN, theParamName);
|
||||||
|
}
|
||||||
Predicate singleCode = createPredicateToken(nextOr, theResourceName, theParamName, myBuilder, join);
|
Predicate singleCode = createPredicateToken(nextOr, theResourceName, theParamName, myBuilder, join);
|
||||||
codePredicates.add(singleCode);
|
codePredicates.add(singleCode);
|
||||||
}
|
}
|
||||||
|
@ -972,38 +996,34 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private <T> Join<ResourceTable, T> createOrReuseJoin(JoinEnum theType, String theSearchParameterName) {
|
private <T> Join<ResourceTable, T> createOrReuseJoin(JoinEnum theType, String theSearchParameterName) {
|
||||||
Join<ResourceTable, ResourceIndexedSearchParamDate> join = null;
|
|
||||||
|
|
||||||
switch (theType) {
|
|
||||||
case DATE:
|
|
||||||
join = myResourceTableRoot.join("myParamsDate", JoinType.LEFT);
|
|
||||||
break;
|
|
||||||
case NUMBER:
|
|
||||||
join = myResourceTableRoot.join("myParamsNumber", JoinType.LEFT);
|
|
||||||
break;
|
|
||||||
case QUANTITY:
|
|
||||||
join = myResourceTableRoot.join("myParamsQuantity", JoinType.LEFT);
|
|
||||||
break;
|
|
||||||
case REFERENCE:
|
|
||||||
join = myResourceTableRoot.join("myResourceLinks", JoinType.LEFT);
|
|
||||||
break;
|
|
||||||
case STRING:
|
|
||||||
join = myResourceTableRoot.join("myParamsString", JoinType.LEFT);
|
|
||||||
break;
|
|
||||||
case URI:
|
|
||||||
join = myResourceTableRoot.join("myParamsUri", JoinType.LEFT);
|
|
||||||
break;
|
|
||||||
case TOKEN:
|
|
||||||
join = myResourceTableRoot.join("myParamsToken", JoinType.LEFT);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
JoinKey key = new JoinKey(theSearchParameterName, theType);
|
JoinKey key = new JoinKey(theSearchParameterName, theType);
|
||||||
if (!myIndexJoins.containsKey(key)) {
|
return (Join<ResourceTable, T>) myIndexJoins.computeIfAbsent(key, k -> {
|
||||||
myIndexJoins.put(key, join);
|
Join<ResourceTable, ResourceIndexedSearchParamDate> join = null;
|
||||||
}
|
switch (theType) {
|
||||||
|
case DATE:
|
||||||
return (Join<ResourceTable, T>) join;
|
join = myResourceTableRoot.join("myParamsDate", JoinType.LEFT);
|
||||||
|
break;
|
||||||
|
case NUMBER:
|
||||||
|
join = myResourceTableRoot.join("myParamsNumber", JoinType.LEFT);
|
||||||
|
break;
|
||||||
|
case QUANTITY:
|
||||||
|
join = myResourceTableRoot.join("myParamsQuantity", JoinType.LEFT);
|
||||||
|
break;
|
||||||
|
case REFERENCE:
|
||||||
|
join = myResourceTableRoot.join("myResourceLinks", JoinType.LEFT);
|
||||||
|
break;
|
||||||
|
case STRING:
|
||||||
|
join = myResourceTableRoot.join("myParamsString", JoinType.LEFT);
|
||||||
|
break;
|
||||||
|
case URI:
|
||||||
|
join = myResourceTableRoot.join("myParamsUri", JoinType.LEFT);
|
||||||
|
break;
|
||||||
|
case TOKEN:
|
||||||
|
join = myResourceTableRoot.join("myParamsToken", JoinType.LEFT);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return join;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private Predicate createPredicateDate(IQueryParameterType theParam, String theResourceName, String theParamName, CriteriaBuilder theBuilder, From<?, ResourceIndexedSearchParamDate> theFrom) {
|
private Predicate createPredicateDate(IQueryParameterType theParam, String theResourceName, String theParamName, CriteriaBuilder theBuilder, From<?, ResourceIndexedSearchParamDate> theFrom) {
|
||||||
|
|
|
@ -0,0 +1,92 @@
|
||||||
|
package ca.uhn.fhir.jpa.config;
|
||||||
|
|
||||||
|
import net.ttddyy.dsproxy.ExecutionInfo;
|
||||||
|
import net.ttddyy.dsproxy.QueryInfo;
|
||||||
|
import net.ttddyy.dsproxy.proxy.ParameterSetOperation;
|
||||||
|
import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder;
|
||||||
|
import org.hibernate.engine.jdbc.internal.BasicFormatterImpl;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class CaptureQueriesListener implements ProxyDataSourceBuilder.SingleQueryExecution {
|
||||||
|
|
||||||
|
private static final LinkedList<Query> LAST_N_QUERIES = new LinkedList<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(ExecutionInfo execInfo, List<QueryInfo> queryInfoList) {
|
||||||
|
synchronized (LAST_N_QUERIES) {
|
||||||
|
for (QueryInfo next : queryInfoList) {
|
||||||
|
String sql = next.getQuery();
|
||||||
|
List<String> params;
|
||||||
|
if (next.getParametersList().size() > 0 && next.getParametersList().get(0).size() > 0) {
|
||||||
|
List<ParameterSetOperation> values = next
|
||||||
|
.getParametersList()
|
||||||
|
.get(0);
|
||||||
|
params = values.stream()
|
||||||
|
.map(t -> t.getArgs()[1])
|
||||||
|
.map(t -> t != null ? t.toString() : "NULL")
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
} else {
|
||||||
|
params = new ArrayList<>();
|
||||||
|
}
|
||||||
|
LAST_N_QUERIES.add(0, new Query(sql, params));
|
||||||
|
}
|
||||||
|
while (LAST_N_QUERIES.size() > 100) {
|
||||||
|
LAST_N_QUERIES.removeLast();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Query {
|
||||||
|
private final String myThreadName = Thread.currentThread().getName();
|
||||||
|
private final String mySql;
|
||||||
|
private final List<String> myParams;
|
||||||
|
|
||||||
|
Query(String theSql, List<String> theParams) {
|
||||||
|
mySql = theSql;
|
||||||
|
myParams = Collections.unmodifiableList(theParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getThreadName() {
|
||||||
|
return myThreadName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSql(boolean theInlineParams, boolean theFormat) {
|
||||||
|
String retVal = mySql;
|
||||||
|
if (theFormat) {
|
||||||
|
retVal = new BasicFormatterImpl().format(retVal);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void clear() {
|
||||||
|
synchronized (LAST_N_QUERIES) {
|
||||||
|
LAST_N_QUERIES.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Index 0 is newest!
|
||||||
|
*/
|
||||||
|
public static ArrayList<Query> getLastNQueries() {
|
||||||
|
synchronized (LAST_N_QUERIES) {
|
||||||
|
return new ArrayList<>(LAST_N_QUERIES);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -100,6 +100,7 @@ public class TestR4Config extends BaseJavaConfigR4 {
|
||||||
// .logSlowQueryBySlf4j(10, TimeUnit.SECONDS)
|
// .logSlowQueryBySlf4j(10, TimeUnit.SECONDS)
|
||||||
// .countQuery(new ThreadQueryCountHolder())
|
// .countQuery(new ThreadQueryCountHolder())
|
||||||
.beforeQuery(new BlockLargeNumbersOfParamsListener())
|
.beforeQuery(new BlockLargeNumbersOfParamsListener())
|
||||||
|
.afterQuery(new CaptureQueriesListener())
|
||||||
.countQuery(singleQueryCountHolder())
|
.countQuery(singleQueryCountHolder())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package ca.uhn.fhir.jpa.dao;
|
package ca.uhn.fhir.jpa.dao;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import ca.uhn.fhir.jpa.config.CaptureQueriesListener;
|
||||||
import ca.uhn.fhir.jpa.entity.TermConcept;
|
import ca.uhn.fhir.jpa.entity.TermConcept;
|
||||||
import ca.uhn.fhir.jpa.model.interceptor.api.IInterceptorRegistry;
|
import ca.uhn.fhir.jpa.model.interceptor.api.IInterceptorRegistry;
|
||||||
import ca.uhn.fhir.jpa.model.interceptor.api.Pointcut;
|
import ca.uhn.fhir.jpa.model.interceptor.api.Pointcut;
|
||||||
|
@ -94,6 +95,7 @@ public abstract class BaseJpaTest {
|
||||||
@After
|
@After
|
||||||
public void afterPerformCleanup() {
|
public void afterPerformCleanup() {
|
||||||
BaseHapiFhirResourceDao.setDisableIncrementOnUpdateForUnitTest(false);
|
BaseHapiFhirResourceDao.setDisableIncrementOnUpdateForUnitTest(false);
|
||||||
|
CaptureQueriesListener.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
package ca.uhn.fhir.jpa.dao.r4;
|
package ca.uhn.fhir.jpa.dao.r4;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.jpa.config.CaptureQueriesListener;
|
||||||
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.searchparam.SearchParameterMap;
|
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap.EverythingModeEnum;
|
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap.EverythingModeEnum;
|
||||||
import ca.uhn.fhir.jpa.model.entity.*;
|
|
||||||
import ca.uhn.fhir.jpa.util.TestUtil;
|
import ca.uhn.fhir.jpa.util.TestUtil;
|
||||||
import ca.uhn.fhir.model.api.Include;
|
import ca.uhn.fhir.model.api.Include;
|
||||||
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
|
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
|
||||||
|
@ -13,6 +14,7 @@ import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||||
import ca.uhn.fhir.rest.param.*;
|
import ca.uhn.fhir.rest.param.*;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException;
|
import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.hl7.fhir.instance.model.api.IAnyResource;
|
import org.hl7.fhir.instance.model.api.IAnyResource;
|
||||||
|
@ -39,6 +41,7 @@ import java.io.IOException;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.*;
|
import static org.hamcrest.Matchers.*;
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
@ -53,6 +56,7 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
|
||||||
myDaoConfig.setReuseCachedSearchResultsForMillis(new DaoConfig().getReuseCachedSearchResultsForMillis());
|
myDaoConfig.setReuseCachedSearchResultsForMillis(new DaoConfig().getReuseCachedSearchResultsForMillis());
|
||||||
myDaoConfig.setFetchSizeDefaultMaximum(new DaoConfig().getFetchSizeDefaultMaximum());
|
myDaoConfig.setFetchSizeDefaultMaximum(new DaoConfig().getFetchSizeDefaultMaximum());
|
||||||
myDaoConfig.setAllowContainsSearches(new DaoConfig().isAllowContainsSearches());
|
myDaoConfig.setAllowContainsSearches(new DaoConfig().isAllowContainsSearches());
|
||||||
|
myDaoConfig.setSearchPreFetchThresholds(new DaoConfig().getSearchPreFetchThresholds());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
|
@ -614,7 +618,7 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
|
||||||
expect1.setResource(resource);
|
expect1.setResource(resource);
|
||||||
expect1.calculateHashes();
|
expect1.calculateHashes();
|
||||||
|
|
||||||
assertThat("Got: \"" + results.toString()+"\"", results, containsInAnyOrder(expect0, expect1));
|
assertThat("Got: \"" + results.toString() + "\"", results, containsInAnyOrder(expect0, expect1));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1060,7 +1064,7 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
|
||||||
QuantityParam v1 = new QuantityParam(ParamPrefixEnum.GREATERTHAN_OR_EQUALS, 150, "http://bar", "code1");
|
QuantityParam v1 = new QuantityParam(ParamPrefixEnum.GREATERTHAN_OR_EQUALS, 150, "http://bar", "code1");
|
||||||
SearchParameterMap map = new SearchParameterMap().setLoadSynchronous(true).add(param, v1);
|
SearchParameterMap map = new SearchParameterMap().setLoadSynchronous(true).add(param, v1);
|
||||||
IBundleProvider result = myObservationDao.search(map);
|
IBundleProvider result = myObservationDao.search(map);
|
||||||
assertThat("Got: "+ toUnqualifiedVersionlessIdValues(result), toUnqualifiedVersionlessIdValues(result), containsInAnyOrder(id1.getValue()));
|
assertThat("Got: " + toUnqualifiedVersionlessIdValues(result), toUnqualifiedVersionlessIdValues(result), containsInAnyOrder(id1.getValue()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1092,7 +1096,7 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
|
||||||
CompositeParam<TokenParam, QuantityParam> val = new CompositeParam<>(v0, v1);
|
CompositeParam<TokenParam, QuantityParam> val = new CompositeParam<>(v0, v1);
|
||||||
SearchParameterMap map = new SearchParameterMap().setLoadSynchronous(true).add(param, val);
|
SearchParameterMap map = new SearchParameterMap().setLoadSynchronous(true).add(param, val);
|
||||||
IBundleProvider result = myObservationDao.search(map);
|
IBundleProvider result = myObservationDao.search(map);
|
||||||
assertThat("Got: "+ toUnqualifiedVersionlessIdValues(result), toUnqualifiedVersionlessIdValues(result), containsInAnyOrder(id2.getValue()));
|
assertThat("Got: " + toUnqualifiedVersionlessIdValues(result), toUnqualifiedVersionlessIdValues(result), containsInAnyOrder(id2.getValue()));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
TokenParam v0 = new TokenParam("http://foo", "code1");
|
TokenParam v0 = new TokenParam("http://foo", "code1");
|
||||||
|
@ -1140,6 +1144,40 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See #1174
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testSearchDateInSavedSearch() {
|
||||||
|
for (int i = 1; i <= 9; i++) {
|
||||||
|
Patient p1 = new Patient();
|
||||||
|
p1.getBirthDateElement().setValueAsString("1980-01-0" + i);
|
||||||
|
String id1 = myPatientDao.create(p1).getId().toUnqualifiedVersionless().getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
myDaoConfig.setSearchPreFetchThresholds(Lists.newArrayList(3, 6, 10));
|
||||||
|
|
||||||
|
{
|
||||||
|
// Don't load synchronous
|
||||||
|
SearchParameterMap map = new SearchParameterMap();
|
||||||
|
map.setLastUpdated(new DateRangeParam().setUpperBound(new DateParam(ParamPrefixEnum.LESSTHAN, "2022-01-01")));
|
||||||
|
IBundleProvider found = myPatientDao.search(map);
|
||||||
|
Set<String> dates = new HashSet<>();
|
||||||
|
for (int i = 0; i < 9; i++) {
|
||||||
|
Patient nextResource = (Patient) found.getResources(i, i + 1).get(0);
|
||||||
|
dates.add(nextResource.getBirthDateElement().getValueAsString());
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThat(dates, hasItems(
|
||||||
|
"1980-01-01",
|
||||||
|
"1980-01-09"
|
||||||
|
));
|
||||||
|
|
||||||
|
assertFalse(map.isLoadSynchronous());
|
||||||
|
assertNull(map.getLoadSynchronousUpTo());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* #222
|
* #222
|
||||||
*/
|
*/
|
||||||
|
@ -2160,6 +2198,51 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSearchLinkToken() {
|
||||||
|
// /fhirapi/MedicationRequest?category=community&identifier=urn:oid:2.16.840.1.113883.3.7418.12.3%7C&intent=order&medication.code:text=calcitriol,hectorol,Zemplar,rocaltrol,vectical,vitamin%20D,doxercalciferol,paricalcitol&status=active,completed
|
||||||
|
|
||||||
|
Medication m = new Medication();
|
||||||
|
m.getCode().setText("valueb");
|
||||||
|
myMedicationDao.create(m);
|
||||||
|
|
||||||
|
MedicationRequest mr = new MedicationRequest();
|
||||||
|
mr.addCategory().addCoding().setCode("community");
|
||||||
|
mr.addIdentifier().setSystem("urn:oid:2.16.840.1.113883.3.7418.12.3").setValue("1");
|
||||||
|
mr.setIntent(MedicationRequest.MedicationRequestIntent.ORDER);
|
||||||
|
mr.setMedication(new Reference(m.getId()));
|
||||||
|
myMedicationRequestDao.create(mr);
|
||||||
|
|
||||||
|
SearchParameterMap sp = new SearchParameterMap();
|
||||||
|
sp.setLoadSynchronous(true);
|
||||||
|
sp.add("category", new TokenParam("community"));
|
||||||
|
sp.add("identifier", new TokenParam("urn:oid:2.16.840.1.113883.3.7418.12.3", "1"));
|
||||||
|
sp.add("intent", new TokenParam("order"));
|
||||||
|
ReferenceParam param1 = new ReferenceParam("valuea").setChain("code:text");
|
||||||
|
ReferenceParam param2 = new ReferenceParam("valueb").setChain("code:text");
|
||||||
|
ReferenceParam param3 = new ReferenceParam("valuec").setChain("code:text");
|
||||||
|
sp.add("medication", new ReferenceOrListParam().addOr(param1).addOr(param2).addOr(param3));
|
||||||
|
|
||||||
|
IBundleProvider retrieved = myMedicationRequestDao.search(sp);
|
||||||
|
assertEquals(1, retrieved.size().intValue());
|
||||||
|
|
||||||
|
List<String> queries = CaptureQueriesListener
|
||||||
|
.getLastNQueries()
|
||||||
|
.stream()
|
||||||
|
.filter(t -> t.getThreadName().equals("main"))
|
||||||
|
.filter(t -> t.getSql(false, false).toLowerCase().contains("select"))
|
||||||
|
.filter(t -> t.getSql(false, false).toLowerCase().contains("token"))
|
||||||
|
.map(t -> t.getSql(true, true))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
ourLog.info("Queries:\n {}", queries.stream().findFirst());
|
||||||
|
|
||||||
|
String searchQuery = queries.get(0);
|
||||||
|
assertEquals(searchQuery, 3, StringUtils.countMatches(searchQuery.toUpperCase(), "HFJ_SPIDX_TOKEN"));
|
||||||
|
assertEquals(searchQuery, 5, StringUtils.countMatches(searchQuery.toUpperCase(), "LEFT OUTER JOIN"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSearchTokenParam() {
|
public void testSearchTokenParam() {
|
||||||
Patient patient = new Patient();
|
Patient patient = new Patient();
|
||||||
|
@ -3314,7 +3397,7 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
|
||||||
"Observation/YES21",
|
"Observation/YES21",
|
||||||
"Observation/YES22",
|
"Observation/YES22",
|
||||||
"Observation/YES23"
|
"Observation/YES23"
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createObservationWithEffective(String theId, String theEffective) {
|
private void createObservationWithEffective(String theId, String theEffective) {
|
||||||
|
|
2
pom.xml
2
pom.xml
|
@ -534,7 +534,7 @@
|
||||||
<jetty_version>9.4.14.v20181114</jetty_version>
|
<jetty_version>9.4.14.v20181114</jetty_version>
|
||||||
<jsr305_version>3.0.2</jsr305_version>
|
<jsr305_version>3.0.2</jsr305_version>
|
||||||
<!--<hibernate_version>5.2.10.Final</hibernate_version>-->
|
<!--<hibernate_version>5.2.10.Final</hibernate_version>-->
|
||||||
<hibernate_version>5.4.0.Final</hibernate_version>
|
<hibernate_version>5.4.1.Final</hibernate_version>
|
||||||
<!-- Update lucene version when you update hibernate-search version -->
|
<!-- Update lucene version when you update hibernate-search version -->
|
||||||
<hibernate_search_version>5.11.0.Final</hibernate_search_version>
|
<hibernate_search_version>5.11.0.Final</hibernate_search_version>
|
||||||
<lucene_version>5.5.5</lucene_version>
|
<lucene_version>5.5.5</lucene_version>
|
||||||
|
|
|
@ -333,6 +333,17 @@
|
||||||
whether a call out to the database may be required. I say "may" because subscription matches fail fast
|
whether a call out to the database may be required. I say "may" because subscription matches fail fast
|
||||||
so a negative match may be performed in-memory, but a positive match will require a database call.
|
so a negative match may be performed in-memory, but a positive match will require a database call.
|
||||||
</action>
|
</action>
|
||||||
|
<action type="fix">
|
||||||
|
When performing a JPA search with a chained :text modifier
|
||||||
|
(e.g. MedicationStatement?medication.code:text=aspirin,tylenol) a series
|
||||||
|
of unneccesary joins were introduced to the generated SQL query, harming
|
||||||
|
performance. This has been fixed.
|
||||||
|
</action>
|
||||||
|
<action type="fix">
|
||||||
|
A serialization error when performing some searches in the JPA server
|
||||||
|
using data parameters has been fixed. Thanks to GitHub user
|
||||||
|
@PickOneFish for reporting!
|
||||||
|
</action>
|
||||||
</release>
|
</release>
|
||||||
<release version="3.6.0" date="2018-11-12" description="Food">
|
<release version="3.6.0" date="2018-11-12" description="Food">
|
||||||
<action type="add">
|
<action type="add">
|
||||||
|
|
Loading…
Reference in New Issue