Improve performance of chained queries into contained resources (#3312)
* base restructuring of query * fix unit tests * suppress unnecessary resource type parameter * pass the resource type used to fetch the search param as part of the chain, so later we do not need to guess what it was * add query structure tests * changelog * fix test failures * got one of the branches wrong in the 3-reference case
This commit is contained in:
parent
283ff19375
commit
48caff30d9
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
type: perf
|
||||||
|
issue: 3312
|
||||||
|
title: "Improves the performance of the query for searching by chained search parameter
|
||||||
|
when the `Index Contained Resources` feature is enabled."
|
|
@ -81,6 +81,8 @@ import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
|
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
|
||||||
import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
|
import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
|
import com.google.common.collect.Sets;
|
||||||
import com.healthmarketscience.sqlbuilder.BinaryCondition;
|
import com.healthmarketscience.sqlbuilder.BinaryCondition;
|
||||||
import com.healthmarketscience.sqlbuilder.ComboCondition;
|
import com.healthmarketscience.sqlbuilder.ComboCondition;
|
||||||
import com.healthmarketscience.sqlbuilder.Condition;
|
import com.healthmarketscience.sqlbuilder.Condition;
|
||||||
|
@ -88,7 +90,9 @@ import com.healthmarketscience.sqlbuilder.Expression;
|
||||||
import com.healthmarketscience.sqlbuilder.InCondition;
|
import com.healthmarketscience.sqlbuilder.InCondition;
|
||||||
import com.healthmarketscience.sqlbuilder.OrderObject;
|
import com.healthmarketscience.sqlbuilder.OrderObject;
|
||||||
import com.healthmarketscience.sqlbuilder.SelectQuery;
|
import com.healthmarketscience.sqlbuilder.SelectQuery;
|
||||||
|
import com.healthmarketscience.sqlbuilder.SetOperationQuery;
|
||||||
import com.healthmarketscience.sqlbuilder.Subquery;
|
import com.healthmarketscience.sqlbuilder.Subquery;
|
||||||
|
import com.healthmarketscience.sqlbuilder.UnionQuery;
|
||||||
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbColumn;
|
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbColumn;
|
||||||
import org.apache.commons.collections4.BidiMap;
|
import org.apache.commons.collections4.BidiMap;
|
||||||
import org.apache.commons.collections4.bidimap.DualHashBidiMap;
|
import org.apache.commons.collections4.bidimap.DualHashBidiMap;
|
||||||
|
@ -112,12 +116,15 @@ import java.util.EnumSet;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
|
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
|
||||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||||
|
import static org.apache.commons.lang3.StringUtils.split;
|
||||||
|
|
||||||
public class QueryStack {
|
public class QueryStack {
|
||||||
|
|
||||||
|
@ -287,6 +294,10 @@ public class QueryStack {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Condition createPredicateComposite(@Nullable DbColumn theSourceJoinColumn, String theResourceName, String theSpnamePrefix, RuntimeSearchParam theParamDef, List<? extends IQueryParameterType> theNextAnd, RequestPartitionId theRequestPartitionId) {
|
private Condition createPredicateComposite(@Nullable DbColumn theSourceJoinColumn, String theResourceName, String theSpnamePrefix, RuntimeSearchParam theParamDef, List<? extends IQueryParameterType> theNextAnd, RequestPartitionId theRequestPartitionId) {
|
||||||
|
return createPredicateComposite(theSourceJoinColumn, theResourceName, theSpnamePrefix, theParamDef, theNextAnd, theRequestPartitionId, mySqlBuilder);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Condition createPredicateComposite(@Nullable DbColumn theSourceJoinColumn, String theResourceName, String theSpnamePrefix, RuntimeSearchParam theParamDef, List<? extends IQueryParameterType> theNextAnd, RequestPartitionId theRequestPartitionId, SearchQueryBuilder theSqlBuilder) {
|
||||||
|
|
||||||
Condition orCondidtion = null;
|
Condition orCondidtion = null;
|
||||||
for (IQueryParameterType next : theNextAnd) {
|
for (IQueryParameterType next : theNextAnd) {
|
||||||
|
@ -299,11 +310,11 @@ public class QueryStack {
|
||||||
List<RuntimeSearchParam> componentParams = JpaParamUtil.resolveComponentParameters(mySearchParamRegistry, theParamDef);
|
List<RuntimeSearchParam> componentParams = JpaParamUtil.resolveComponentParameters(mySearchParamRegistry, theParamDef);
|
||||||
RuntimeSearchParam left = componentParams.get(0);
|
RuntimeSearchParam left = componentParams.get(0);
|
||||||
IQueryParameterType leftValue = cp.getLeftValue();
|
IQueryParameterType leftValue = cp.getLeftValue();
|
||||||
Condition leftPredicate = createPredicateCompositePart(theSourceJoinColumn, theResourceName, theSpnamePrefix, left, leftValue, theRequestPartitionId);
|
Condition leftPredicate = createPredicateCompositePart(theSourceJoinColumn, theResourceName, theSpnamePrefix, left, leftValue, theRequestPartitionId, theSqlBuilder);
|
||||||
|
|
||||||
RuntimeSearchParam right = componentParams.get(1);
|
RuntimeSearchParam right = componentParams.get(1);
|
||||||
IQueryParameterType rightValue = cp.getRightValue();
|
IQueryParameterType rightValue = cp.getRightValue();
|
||||||
Condition rightPredicate = createPredicateCompositePart(theSourceJoinColumn, theResourceName, theSpnamePrefix, right, rightValue, theRequestPartitionId);
|
Condition rightPredicate = createPredicateCompositePart(theSourceJoinColumn, theResourceName, theSpnamePrefix, right, rightValue, theRequestPartitionId, theSqlBuilder);
|
||||||
|
|
||||||
Condition andCondition = toAndPredicate(leftPredicate, rightPredicate);
|
Condition andCondition = toAndPredicate(leftPredicate, rightPredicate);
|
||||||
|
|
||||||
|
@ -318,19 +329,23 @@ public class QueryStack {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Condition createPredicateCompositePart(@Nullable DbColumn theSourceJoinColumn, String theResourceName, String theSpnamePrefix, RuntimeSearchParam theParam, IQueryParameterType theParamValue, RequestPartitionId theRequestPartitionId) {
|
private Condition createPredicateCompositePart(@Nullable DbColumn theSourceJoinColumn, String theResourceName, String theSpnamePrefix, RuntimeSearchParam theParam, IQueryParameterType theParamValue, RequestPartitionId theRequestPartitionId) {
|
||||||
|
return createPredicateCompositePart(theSourceJoinColumn, theResourceName, theSpnamePrefix, theParam, theParamValue, theRequestPartitionId, mySqlBuilder);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Condition createPredicateCompositePart(@Nullable DbColumn theSourceJoinColumn, String theResourceName, String theSpnamePrefix, RuntimeSearchParam theParam, IQueryParameterType theParamValue, RequestPartitionId theRequestPartitionId, SearchQueryBuilder theSqlBuilder) {
|
||||||
|
|
||||||
switch (theParam.getParamType()) {
|
switch (theParam.getParamType()) {
|
||||||
case STRING: {
|
case STRING: {
|
||||||
return createPredicateString(theSourceJoinColumn, theResourceName, theSpnamePrefix, theParam, Collections.singletonList(theParamValue), null, theRequestPartitionId);
|
return createPredicateString(theSourceJoinColumn, theResourceName, theSpnamePrefix, theParam, Collections.singletonList(theParamValue), null, theRequestPartitionId, theSqlBuilder);
|
||||||
}
|
}
|
||||||
case TOKEN: {
|
case TOKEN: {
|
||||||
return createPredicateToken(theSourceJoinColumn, theResourceName, theSpnamePrefix, theParam, Collections.singletonList(theParamValue), null, theRequestPartitionId);
|
return createPredicateToken(theSourceJoinColumn, theResourceName, theSpnamePrefix, theParam, Collections.singletonList(theParamValue), null, theRequestPartitionId, theSqlBuilder);
|
||||||
}
|
}
|
||||||
case DATE: {
|
case DATE: {
|
||||||
return createPredicateDate(theSourceJoinColumn, theResourceName, theSpnamePrefix, theParam, Collections.singletonList(theParamValue), toOperation(((DateParam) theParamValue).getPrefix()), theRequestPartitionId);
|
return createPredicateDate(theSourceJoinColumn, theResourceName, theSpnamePrefix, theParam, Collections.singletonList(theParamValue), toOperation(((DateParam) theParamValue).getPrefix()), theRequestPartitionId, theSqlBuilder);
|
||||||
}
|
}
|
||||||
case QUANTITY: {
|
case QUANTITY: {
|
||||||
return createPredicateQuantity(theSourceJoinColumn, theResourceName, theSpnamePrefix, theParam, Collections.singletonList(theParamValue), null, theRequestPartitionId);
|
return createPredicateQuantity(theSourceJoinColumn, theResourceName, theSpnamePrefix, theParam, Collections.singletonList(theParamValue), null, theRequestPartitionId, theSqlBuilder);
|
||||||
}
|
}
|
||||||
case NUMBER:
|
case NUMBER:
|
||||||
case REFERENCE:
|
case REFERENCE:
|
||||||
|
@ -368,10 +383,15 @@ public class QueryStack {
|
||||||
public Condition createPredicateDate(@Nullable DbColumn theSourceJoinColumn, String theResourceName,
|
public Condition createPredicateDate(@Nullable DbColumn theSourceJoinColumn, String theResourceName,
|
||||||
String theSpnamePrefix, RuntimeSearchParam theSearchParam, List<? extends IQueryParameterType> theList,
|
String theSpnamePrefix, RuntimeSearchParam theSearchParam, List<? extends IQueryParameterType> theList,
|
||||||
SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) {
|
SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) {
|
||||||
|
return createPredicateDate(theSourceJoinColumn, theResourceName, theSpnamePrefix, theSearchParam, theList, theOperation, theRequestPartitionId, mySqlBuilder);
|
||||||
|
}
|
||||||
|
public Condition createPredicateDate(@Nullable DbColumn theSourceJoinColumn, String theResourceName,
|
||||||
|
String theSpnamePrefix, RuntimeSearchParam theSearchParam, List<? extends IQueryParameterType> theList,
|
||||||
|
SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId, SearchQueryBuilder theSqlBuilder) {
|
||||||
|
|
||||||
String paramName = getParamNameWithPrefix(theSpnamePrefix, theSearchParam.getName());
|
String paramName = getParamNameWithPrefix(theSpnamePrefix, theSearchParam.getName());
|
||||||
|
|
||||||
PredicateBuilderCacheLookupResult<DatePredicateBuilder> predicateBuilderLookupResult = createOrReusePredicateBuilder(PredicateBuilderTypeEnum.DATE, theSourceJoinColumn, paramName, () -> mySqlBuilder.addDatePredicateBuilder(theSourceJoinColumn));
|
PredicateBuilderCacheLookupResult<DatePredicateBuilder> predicateBuilderLookupResult = createOrReusePredicateBuilder(PredicateBuilderTypeEnum.DATE, theSourceJoinColumn, paramName, () -> theSqlBuilder.addDatePredicateBuilder(theSourceJoinColumn));
|
||||||
DatePredicateBuilder predicateBuilder = predicateBuilderLookupResult.getResult();
|
DatePredicateBuilder predicateBuilder = predicateBuilderLookupResult.getResult();
|
||||||
boolean cacheHit = predicateBuilderLookupResult.isCacheHit();
|
boolean cacheHit = predicateBuilderLookupResult.isCacheHit();
|
||||||
|
|
||||||
|
@ -577,10 +597,16 @@ public class QueryStack {
|
||||||
public Condition createPredicateNumber(@Nullable DbColumn theSourceJoinColumn, String theResourceName,
|
public Condition createPredicateNumber(@Nullable DbColumn theSourceJoinColumn, String theResourceName,
|
||||||
String theSpnamePrefix, RuntimeSearchParam theSearchParam, List<? extends IQueryParameterType> theList,
|
String theSpnamePrefix, RuntimeSearchParam theSearchParam, List<? extends IQueryParameterType> theList,
|
||||||
SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) {
|
SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) {
|
||||||
|
return createPredicateNumber(theSourceJoinColumn, theResourceName, theSpnamePrefix, theSearchParam, theList, theOperation, theRequestPartitionId, mySqlBuilder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Condition createPredicateNumber(@Nullable DbColumn theSourceJoinColumn, String theResourceName,
|
||||||
|
String theSpnamePrefix, RuntimeSearchParam theSearchParam, List<? extends IQueryParameterType> theList,
|
||||||
|
SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId, SearchQueryBuilder theSqlBuilder) {
|
||||||
|
|
||||||
String paramName = getParamNameWithPrefix(theSpnamePrefix, theSearchParam.getName());
|
String paramName = getParamNameWithPrefix(theSpnamePrefix, theSearchParam.getName());
|
||||||
|
|
||||||
NumberPredicateBuilder join = createOrReusePredicateBuilder(PredicateBuilderTypeEnum.NUMBER, theSourceJoinColumn, paramName, () -> mySqlBuilder.addNumberPredicateBuilder(theSourceJoinColumn)).getResult();
|
NumberPredicateBuilder join = createOrReusePredicateBuilder(PredicateBuilderTypeEnum.NUMBER, theSourceJoinColumn, paramName, () -> theSqlBuilder.addNumberPredicateBuilder(theSourceJoinColumn)).getResult();
|
||||||
|
|
||||||
if (theList.get(0).getMissing() != null) {
|
if (theList.get(0).getMissing() != null) {
|
||||||
return join.createPredicateParamMissingForNonReference(theResourceName, paramName, theList.get(0).getMissing(), theRequestPartitionId);
|
return join.createPredicateParamMissingForNonReference(theResourceName, paramName, theList.get(0).getMissing(), theRequestPartitionId);
|
||||||
|
@ -618,11 +644,17 @@ public class QueryStack {
|
||||||
public Condition createPredicateQuantity(@Nullable DbColumn theSourceJoinColumn, String theResourceName,
|
public Condition createPredicateQuantity(@Nullable DbColumn theSourceJoinColumn, String theResourceName,
|
||||||
String theSpnamePrefix, RuntimeSearchParam theSearchParam, List<? extends IQueryParameterType> theList,
|
String theSpnamePrefix, RuntimeSearchParam theSearchParam, List<? extends IQueryParameterType> theList,
|
||||||
SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) {
|
SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) {
|
||||||
|
return createPredicateQuantity(theSourceJoinColumn, theResourceName, theSpnamePrefix, theSearchParam, theList, theOperation, theRequestPartitionId, mySqlBuilder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Condition createPredicateQuantity(@Nullable DbColumn theSourceJoinColumn, String theResourceName,
|
||||||
|
String theSpnamePrefix, RuntimeSearchParam theSearchParam, List<? extends IQueryParameterType> theList,
|
||||||
|
SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId, SearchQueryBuilder theSqlBuilder) {
|
||||||
|
|
||||||
String paramName = getParamNameWithPrefix(theSpnamePrefix, theSearchParam.getName());
|
String paramName = getParamNameWithPrefix(theSpnamePrefix, theSearchParam.getName());
|
||||||
|
|
||||||
if (theList.get(0).getMissing() != null) {
|
if (theList.get(0).getMissing() != null) {
|
||||||
QuantityBasePredicateBuilder join = createOrReusePredicateBuilder(PredicateBuilderTypeEnum.QUANTITY, theSourceJoinColumn, theSearchParam.getName(), () -> mySqlBuilder.addQuantityPredicateBuilder(theSourceJoinColumn)).getResult();
|
QuantityBasePredicateBuilder join = createOrReusePredicateBuilder(PredicateBuilderTypeEnum.QUANTITY, theSourceJoinColumn, theSearchParam.getName(), () -> theSqlBuilder.addQuantityPredicateBuilder(theSourceJoinColumn)).getResult();
|
||||||
return join.createPredicateParamMissingForNonReference(theResourceName, paramName, theList.get(0).getMissing(), theRequestPartitionId);
|
return join.createPredicateParamMissingForNonReference(theResourceName, paramName, theList.get(0).getMissing(), theRequestPartitionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -641,13 +673,13 @@ public class QueryStack {
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
if (normalizedQuantityParams.size() == quantityParams.size()) {
|
if (normalizedQuantityParams.size() == quantityParams.size()) {
|
||||||
join = createOrReusePredicateBuilder(PredicateBuilderTypeEnum.QUANTITY, theSourceJoinColumn, paramName, () -> mySqlBuilder.addQuantityNormalizedPredicateBuilder(theSourceJoinColumn)).getResult();
|
join = createOrReusePredicateBuilder(PredicateBuilderTypeEnum.QUANTITY, theSourceJoinColumn, paramName, () -> theSqlBuilder.addQuantityNormalizedPredicateBuilder(theSourceJoinColumn)).getResult();
|
||||||
quantityParams = normalizedQuantityParams;
|
quantityParams = normalizedQuantityParams;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (join == null) {
|
if (join == null) {
|
||||||
join = createOrReusePredicateBuilder(PredicateBuilderTypeEnum.QUANTITY, theSourceJoinColumn, paramName, () -> mySqlBuilder.addQuantityPredicateBuilder(theSourceJoinColumn)).getResult();
|
join = createOrReusePredicateBuilder(PredicateBuilderTypeEnum.QUANTITY, theSourceJoinColumn, paramName, () -> theSqlBuilder.addQuantityPredicateBuilder(theSourceJoinColumn)).getResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Condition> codePredicates = new ArrayList<>();
|
List<Condition> codePredicates = new ArrayList<>();
|
||||||
|
@ -667,6 +699,17 @@ public class QueryStack {
|
||||||
SearchFilterParser.CompareOperation theOperation,
|
SearchFilterParser.CompareOperation theOperation,
|
||||||
RequestDetails theRequest,
|
RequestDetails theRequest,
|
||||||
RequestPartitionId theRequestPartitionId) {
|
RequestPartitionId theRequestPartitionId) {
|
||||||
|
return createPredicateReference(theSourceJoinColumn, theResourceName, theParamName, theQualifiers, theList, theOperation, theRequest, theRequestPartitionId, mySqlBuilder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Condition createPredicateReference(@Nullable DbColumn theSourceJoinColumn,
|
||||||
|
String theResourceName,
|
||||||
|
String theParamName,
|
||||||
|
List<String> theQualifiers,
|
||||||
|
List<? extends IQueryParameterType> theList,
|
||||||
|
SearchFilterParser.CompareOperation theOperation,
|
||||||
|
RequestDetails theRequest,
|
||||||
|
RequestPartitionId theRequestPartitionId, SearchQueryBuilder theSqlBuilder) {
|
||||||
|
|
||||||
if ((theOperation != null) &&
|
if ((theOperation != null) &&
|
||||||
(theOperation != SearchFilterParser.CompareOperation.eq) &&
|
(theOperation != SearchFilterParser.CompareOperation.eq) &&
|
||||||
|
@ -675,140 +718,407 @@ public class QueryStack {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (theList.get(0).getMissing() != null) {
|
if (theList.get(0).getMissing() != null) {
|
||||||
SearchParamPresentPredicateBuilder join = mySqlBuilder.addSearchParamPresentPredicateBuilder(theSourceJoinColumn);
|
SearchParamPresentPredicateBuilder join = theSqlBuilder.addSearchParamPresentPredicateBuilder(theSourceJoinColumn);
|
||||||
return join.createPredicateParamMissingForReference(theResourceName, theParamName, theList.get(0).getMissing(), theRequestPartitionId);
|
return join.createPredicateParamMissingForReference(theResourceName, theParamName, theList.get(0).getMissing(), theRequestPartitionId);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ResourceLinkPredicateBuilder predicateBuilder = createOrReusePredicateBuilder(PredicateBuilderTypeEnum.REFERENCE, theSourceJoinColumn, theParamName, () -> mySqlBuilder.addReferencePredicateBuilder(this, theSourceJoinColumn)).getResult();
|
ResourceLinkPredicateBuilder predicateBuilder = createOrReusePredicateBuilder(PredicateBuilderTypeEnum.REFERENCE, theSourceJoinColumn, theParamName, () -> theSqlBuilder.addReferencePredicateBuilder(this, theSourceJoinColumn)).getResult();
|
||||||
return predicateBuilder.createPredicate(theRequest, theResourceName, theParamName, theQualifiers, theList, theOperation, theRequestPartitionId);
|
return predicateBuilder.createPredicate(theRequest, theResourceName, theParamName, theQualifiers, theList, theOperation, theRequestPartitionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class ChainElement {
|
||||||
|
private final String myResourceType;
|
||||||
|
private final RuntimeSearchParam mySearchParam;
|
||||||
|
|
||||||
|
public ChainElement(String theResourceType, RuntimeSearchParam theSearchParam) {
|
||||||
|
this.myResourceType = theResourceType;
|
||||||
|
this.mySearchParam = theSearchParam;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getResourceType() {
|
||||||
|
return myResourceType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RuntimeSearchParam getSearchParam() {
|
||||||
|
return mySearchParam;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
ChainElement that = (ChainElement) o;
|
||||||
|
return myResourceType.equals(that.myResourceType) && mySearchParam.equals(that.mySearchParam);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(myResourceType, mySearchParam);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ReferenceChainExtractor {
|
||||||
|
private final Map<List<ChainElement>,Set<LeafNodeDefinition>> myChains = Maps.newHashMap();
|
||||||
|
|
||||||
|
public Map<List<ChainElement>,Set<LeafNodeDefinition>> getChains() { return myChains; }
|
||||||
|
|
||||||
|
private boolean isReferenceParamValid(ReferenceParam theReferenceParam) {
|
||||||
|
return split(theReferenceParam.getChain(), '.').length <= 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deriveChains(String theResourceType, RuntimeSearchParam theSearchParam, List<? extends IQueryParameterType> theList) {
|
||||||
|
List<ChainElement> searchParams = Lists.newArrayList();
|
||||||
|
searchParams.add(new ChainElement(theResourceType, theSearchParam));
|
||||||
|
for (IQueryParameterType nextOr : theList) {
|
||||||
|
String targetValue = nextOr.getValueAsQueryToken(myFhirContext);
|
||||||
|
if (nextOr instanceof ReferenceParam) {
|
||||||
|
ReferenceParam referenceParam = (ReferenceParam) nextOr;
|
||||||
|
|
||||||
|
if (!isReferenceParamValid(referenceParam)) {
|
||||||
|
throw new InvalidRequestException(
|
||||||
|
"The search chain " + theSearchParam.getName() + "." + referenceParam.getChain() +
|
||||||
|
" is too long. Only chains up to three references are supported.");
|
||||||
|
}
|
||||||
|
|
||||||
|
String targetChain = referenceParam.getChain();
|
||||||
|
List<String> qualifiers = Lists.newArrayList(referenceParam.getResourceType());
|
||||||
|
|
||||||
|
processNextLinkInChain(searchParams, theSearchParam, targetChain, targetValue, qualifiers, referenceParam.getResourceType());
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processNextLinkInChain(List<ChainElement> theSearchParams, RuntimeSearchParam thePreviousSearchParam, String theChain, String theTargetValue, List<String> theQualifiers, String theResourceType) {
|
||||||
|
|
||||||
|
String nextParamName = theChain;
|
||||||
|
String nextChain = null;
|
||||||
|
String nextQualifier = null;
|
||||||
|
int linkIndex = theChain.indexOf('.');
|
||||||
|
if (linkIndex != -1) {
|
||||||
|
nextParamName = theChain.substring(0, linkIndex);
|
||||||
|
nextChain = theChain.substring(linkIndex+1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int qualifierIndex = nextParamName.indexOf(':');
|
||||||
|
if (qualifierIndex != -1) {
|
||||||
|
nextParamName = nextParamName.substring(0, qualifierIndex);
|
||||||
|
nextQualifier = nextParamName.substring(qualifierIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> qualifiersBranch = Lists.newArrayList();
|
||||||
|
qualifiersBranch.addAll(theQualifiers);
|
||||||
|
qualifiersBranch.add(nextQualifier);
|
||||||
|
|
||||||
|
boolean searchParamFound = false;
|
||||||
|
for (String nextTarget : thePreviousSearchParam.getTargets()) {
|
||||||
|
RuntimeSearchParam nextSearchParam = null;
|
||||||
|
if (StringUtils.isBlank(theResourceType) || theResourceType.equals(nextTarget)) {
|
||||||
|
nextSearchParam = mySearchParamRegistry.getActiveSearchParam(nextTarget, nextParamName);
|
||||||
|
}
|
||||||
|
if (nextSearchParam != null) {
|
||||||
|
searchParamFound = true;
|
||||||
|
// If we find a search param on this resource type for this parameter name, keep iterating
|
||||||
|
// Otherwise, abandon this branch and carry on to the next one
|
||||||
|
List<ChainElement> searchParamBranch = Lists.newArrayList();
|
||||||
|
searchParamBranch.addAll(theSearchParams);
|
||||||
|
|
||||||
|
if (StringUtils.isEmpty(nextChain)) {
|
||||||
|
// We've reached the end of the chain
|
||||||
|
ArrayList<IQueryParameterType> orValues = Lists.newArrayList();
|
||||||
|
|
||||||
|
if (RestSearchParameterTypeEnum.REFERENCE.equals(nextSearchParam.getParamType())) {
|
||||||
|
orValues.add(new ReferenceParam(nextQualifier, "", theTargetValue));
|
||||||
|
} else {
|
||||||
|
IQueryParameterType qp = toParameterType(nextSearchParam);
|
||||||
|
qp.setValueAsQueryToken(myFhirContext, nextSearchParam.getName(), null, theTargetValue);
|
||||||
|
orValues.add(qp);
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<LeafNodeDefinition> leafNodes = myChains.get(searchParamBranch);
|
||||||
|
if (leafNodes == null) {
|
||||||
|
leafNodes = Sets.newHashSet();
|
||||||
|
myChains.put(searchParamBranch, leafNodes);
|
||||||
|
}
|
||||||
|
leafNodes.add(new LeafNodeDefinition(nextSearchParam, orValues, nextTarget, nextParamName, "", qualifiersBranch));
|
||||||
|
} else {
|
||||||
|
|
||||||
|
searchParamBranch.add(new ChainElement(nextTarget, nextSearchParam));
|
||||||
|
processNextLinkInChain(searchParamBranch, nextSearchParam, nextChain, theTargetValue, qualifiersBranch, nextQualifier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!searchParamFound) {
|
||||||
|
throw new InvalidRequestException(myFhirContext.getLocalizer().getMessage(BaseStorageDao.class, "invalidParameterChain", thePreviousSearchParam.getName() + '.' + theChain));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class LeafNodeDefinition {
|
||||||
|
private final RuntimeSearchParam myParamDefinition;
|
||||||
|
private final ArrayList<IQueryParameterType> myOrValues;
|
||||||
|
private final String myLeafTarget;
|
||||||
|
private final String myLeafParamName;
|
||||||
|
private final String myLeafPathPrefix;
|
||||||
|
private final List<String> myQualifiers;
|
||||||
|
|
||||||
|
public LeafNodeDefinition(RuntimeSearchParam theParamDefinition, ArrayList<IQueryParameterType> theOrValues, String theLeafTarget, String theLeafParamName, String theLeafPathPrefix, List<String> theQualifiers) {
|
||||||
|
myParamDefinition = theParamDefinition;
|
||||||
|
myOrValues = theOrValues;
|
||||||
|
myLeafTarget = theLeafTarget;
|
||||||
|
myLeafParamName = theLeafParamName;
|
||||||
|
myLeafPathPrefix = theLeafPathPrefix;
|
||||||
|
myQualifiers = theQualifiers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RuntimeSearchParam getParamDefinition() {
|
||||||
|
return myParamDefinition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArrayList<IQueryParameterType> getOrValues() {
|
||||||
|
return myOrValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLeafTarget() {
|
||||||
|
return myLeafTarget;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLeafParamName() {
|
||||||
|
return myLeafParamName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLeafPathPrefix() {
|
||||||
|
return myLeafPathPrefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getQualifiers() {
|
||||||
|
return myQualifiers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LeafNodeDefinition withPathPrefix(String theResourceType, String theName) {
|
||||||
|
return new LeafNodeDefinition(myParamDefinition, myOrValues, theResourceType, myLeafParamName, theName, myQualifiers);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
LeafNodeDefinition that = (LeafNodeDefinition) o;
|
||||||
|
return Objects.equals(myParamDefinition, that.myParamDefinition) && Objects.equals(myOrValues, that.myOrValues) && Objects.equals(myLeafTarget, that.myLeafTarget) && Objects.equals(myLeafParamName, that.myLeafParamName) && Objects.equals(myLeafPathPrefix, that.myLeafPathPrefix) && Objects.equals(myQualifiers, that.myQualifiers);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(myParamDefinition, myOrValues, myLeafTarget, myLeafParamName, myLeafPathPrefix, myQualifiers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public Condition createPredicateReferenceForContainedResource(@Nullable DbColumn theSourceJoinColumn,
|
public Condition createPredicateReferenceForContainedResource(@Nullable DbColumn theSourceJoinColumn,
|
||||||
String theResourceName, String theParamName, List<String> theQualifiers, RuntimeSearchParam theSearchParam,
|
String theResourceName, String theParamName, List<String> theQualifiers, RuntimeSearchParam theSearchParam,
|
||||||
List<? extends IQueryParameterType> theList, SearchFilterParser.CompareOperation theOperation,
|
List<? extends IQueryParameterType> theList, SearchFilterParser.CompareOperation theOperation,
|
||||||
RequestDetails theRequest, RequestPartitionId theRequestPartitionId) {
|
RequestDetails theRequest, RequestPartitionId theRequestPartitionId) {
|
||||||
|
// A bit of a hack, but we need to turn off cache reuse while in this method so that we don't try to reuse builders across different subselects
|
||||||
|
EnumSet<PredicateBuilderTypeEnum> cachedReusePredicateBuilderTypes = EnumSet.copyOf(myReusePredicateBuilderTypes);
|
||||||
|
myReusePredicateBuilderTypes.clear();
|
||||||
|
|
||||||
String spnamePrefix = theParamName;
|
UnionQuery union = new UnionQuery(SetOperationQuery.Type.UNION);
|
||||||
|
|
||||||
String targetChain = null;
|
ReferenceChainExtractor chainExtractor = new ReferenceChainExtractor();
|
||||||
String targetParamName = null;
|
chainExtractor.deriveChains(theResourceName, theSearchParam, theList);
|
||||||
String headQualifier = null;
|
Map<List<ChainElement>,Set<LeafNodeDefinition>> chains = chainExtractor.getChains();
|
||||||
String targetQualifier = null;
|
|
||||||
String targetValue = null;
|
|
||||||
|
|
||||||
RuntimeSearchParam targetParamDefinition = null;
|
Map<List<String>,Set<LeafNodeDefinition>> referenceLinks = Maps.newHashMap();
|
||||||
|
for (List<ChainElement> nextChain : chains.keySet()) {
|
||||||
|
Set<LeafNodeDefinition> leafNodes = chains.get(nextChain);
|
||||||
|
|
||||||
ArrayList<IQueryParameterType> orValues = Lists.newArrayList();
|
collateChainedSearchOptions(referenceLinks, nextChain, leafNodes);
|
||||||
List<IQueryParameterType> trimmedParameters = Lists.newArrayList();
|
}
|
||||||
IQueryParameterType qp = null;
|
|
||||||
|
|
||||||
for (int orIdx = 0; orIdx < theList.size(); orIdx++) {
|
for (List<String> nextReferenceLink: referenceLinks.keySet()) {
|
||||||
|
for (LeafNodeDefinition leafNodeDefinition : referenceLinks.get(nextReferenceLink)) {
|
||||||
|
SearchQueryBuilder builder = mySqlBuilder.newChildSqlBuilder();
|
||||||
|
DbColumn previousJoinColumn = null;
|
||||||
|
|
||||||
IQueryParameterType nextOr = theList.get(orIdx);
|
// Create a reference link predicate to the subselect for every link but the last one
|
||||||
|
for (String nextLink : nextReferenceLink) {
|
||||||
if (nextOr instanceof ReferenceParam) {
|
// We don't want to call createPredicateReference() here, because the whole point is to avoid the recursion.
|
||||||
|
// TODO: Are we missing any important business logic from that method? All tests are passing.
|
||||||
ReferenceParam referenceParam = (ReferenceParam) nextOr;
|
ResourceLinkPredicateBuilder resourceLinkPredicateBuilder = builder.addReferencePredicateBuilder(this, previousJoinColumn);
|
||||||
|
builder.addPredicate(resourceLinkPredicateBuilder.createPredicateSourcePaths(Lists.newArrayList(nextLink)));
|
||||||
// 1. Find out the parameter, qualifier and the value
|
previousJoinColumn = resourceLinkPredicateBuilder.getColumnTargetResourceId();
|
||||||
targetChain = referenceParam.getChain();
|
|
||||||
targetParamName = targetChain;
|
|
||||||
targetValue = nextOr.getValueAsQueryToken(myFhirContext);
|
|
||||||
headQualifier = referenceParam.getResourceType();
|
|
||||||
|
|
||||||
String targetNextChain = null;
|
|
||||||
int linkIndex = targetChain.indexOf('.');
|
|
||||||
if (linkIndex != -1) {
|
|
||||||
targetParamName = targetChain.substring(0, linkIndex);
|
|
||||||
targetNextChain = targetChain.substring(linkIndex+1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int qualifierIndex = targetParamName.indexOf(':');
|
Condition containedCondition = createIndexPredicate(
|
||||||
if (qualifierIndex != -1) {
|
previousJoinColumn,
|
||||||
targetParamName = targetParamName.substring(0, qualifierIndex);
|
leafNodeDefinition.getLeafTarget(),
|
||||||
targetQualifier = targetParamName.substring(qualifierIndex);
|
leafNodeDefinition.getLeafPathPrefix(),
|
||||||
}
|
leafNodeDefinition.getLeafParamName(),
|
||||||
trimmedParameters.add(new ReferenceParam(targetQualifier, targetNextChain, referenceParam.getValue()));
|
leafNodeDefinition.getParamDefinition(),
|
||||||
|
leafNodeDefinition.getOrValues(),
|
||||||
|
theOperation,
|
||||||
|
leafNodeDefinition.getQualifiers(),
|
||||||
|
theRequest,
|
||||||
|
theRequestPartitionId,
|
||||||
|
builder);
|
||||||
|
|
||||||
// 2. find out the data type
|
builder.addPredicate(containedCondition);
|
||||||
if (targetParamDefinition == null) {
|
|
||||||
for (String nextTarget : theSearchParam.getTargets()) {
|
|
||||||
if (!referenceParam.hasResourceType() || referenceParam.getResourceType().equals(nextTarget)) {
|
|
||||||
targetParamDefinition = mySearchParamRegistry.getActiveSearchParam(nextTarget, targetParamName);
|
|
||||||
}
|
|
||||||
if (targetParamDefinition != null)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (targetParamDefinition == null) {
|
union.addQueries(builder.getSelect());
|
||||||
throw new InvalidRequestException("Unknown search parameter name: " + theSearchParam.getName() + '.' + targetParamName + ".");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (RestSearchParameterTypeEnum.REFERENCE.equals(targetParamDefinition.getParamType())) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
qp = toParameterType(targetParamDefinition);
|
|
||||||
qp.setValueAsQueryToken(myFhirContext, targetParamName, targetQualifier, targetValue);
|
|
||||||
orValues.add(qp);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (targetParamDefinition == null) {
|
InCondition inCondition;
|
||||||
throw new InvalidRequestException("Unknown search parameter name: " + theSearchParam.getName() + ".");
|
if (theSourceJoinColumn == null) {
|
||||||
|
inCondition = new InCondition(mySqlBuilder.getOrCreateFirstPredicateBuilder(false).getResourceIdColumn(), union);
|
||||||
|
} else {
|
||||||
|
//-- for the resource link, need join with target_resource_id
|
||||||
|
inCondition = new InCondition(theSourceJoinColumn, union);
|
||||||
}
|
}
|
||||||
|
|
||||||
theQualifiers.add(headQualifier);
|
// restore the state of this collection to turn caching back on before we exit
|
||||||
|
myReusePredicateBuilderTypes.addAll(cachedReusePredicateBuilderTypes);
|
||||||
|
return inCondition;
|
||||||
|
}
|
||||||
|
|
||||||
// 3. create the query
|
private void collateChainedSearchOptions(Map<List<String>, Set<LeafNodeDefinition>> referenceLinks, List<ChainElement> nextChain, Set<LeafNodeDefinition> leafNodes) {
|
||||||
|
// Manually collapse the chain using all possible variants of contained resource patterns.
|
||||||
|
// This is a bit excruciating to extend beyond three references. Do we want to find a way to automate this someday?
|
||||||
|
// Note: the first element in each chain is assumed to be discrete. This may need to change when we add proper support for `_contained`
|
||||||
|
if (nextChain.size() == 1) {
|
||||||
|
// discrete -> discrete
|
||||||
|
updateMapOfReferenceLinks(referenceLinks, Lists.newArrayList(nextChain.get(0).getSearchParam().getPath()), leafNodes);
|
||||||
|
// discrete -> contained
|
||||||
|
updateMapOfReferenceLinks(referenceLinks, Lists.newArrayList(),
|
||||||
|
leafNodes
|
||||||
|
.stream()
|
||||||
|
.map(t -> t.withPathPrefix(nextChain.get(0).getResourceType(), nextChain.get(0).getSearchParam().getName()))
|
||||||
|
.collect(Collectors.toSet()));
|
||||||
|
} else if (nextChain.size() == 2) {
|
||||||
|
// discrete -> discrete -> discrete
|
||||||
|
updateMapOfReferenceLinks(referenceLinks, Lists.newArrayList(nextChain.get(0).getSearchParam().getPath(), nextChain.get(1).getSearchParam().getPath()), leafNodes);
|
||||||
|
// discrete -> discrete -> contained
|
||||||
|
updateMapOfReferenceLinks(referenceLinks, Lists.newArrayList(nextChain.get(0).getSearchParam().getPath()),
|
||||||
|
leafNodes
|
||||||
|
.stream()
|
||||||
|
.map(t -> t.withPathPrefix(nextChain.get(1).getResourceType(), nextChain.get(1).getSearchParam().getName()))
|
||||||
|
.collect(Collectors.toSet()));
|
||||||
|
// discrete -> contained -> discrete
|
||||||
|
updateMapOfReferenceLinks(referenceLinks, Lists.newArrayList(mergePaths(nextChain.get(0).getSearchParam().getPath(), nextChain.get(1).getSearchParam().getPath())), leafNodes);
|
||||||
|
if (myModelConfig.isIndexOnContainedResourcesRecursively()) {
|
||||||
|
// discrete -> contained -> contained
|
||||||
|
updateMapOfReferenceLinks(referenceLinks, Lists.newArrayList(),
|
||||||
|
leafNodes
|
||||||
|
.stream()
|
||||||
|
.map(t -> t.withPathPrefix(nextChain.get(0).getResourceType(), nextChain.get(0).getSearchParam().getName() + "." + nextChain.get(1).getSearchParam().getName()))
|
||||||
|
.collect(Collectors.toSet()));
|
||||||
|
}
|
||||||
|
} else if (nextChain.size() == 3) {
|
||||||
|
// discrete -> discrete -> discrete -> discrete
|
||||||
|
updateMapOfReferenceLinks(referenceLinks, Lists.newArrayList(nextChain.get(0).getSearchParam().getPath(), nextChain.get(1).getSearchParam().getPath(), nextChain.get(2).getSearchParam().getPath()), leafNodes);
|
||||||
|
// discrete -> discrete -> discrete -> contained
|
||||||
|
updateMapOfReferenceLinks(referenceLinks, Lists.newArrayList(nextChain.get(0).getSearchParam().getPath(), nextChain.get(1).getSearchParam().getPath()),
|
||||||
|
leafNodes
|
||||||
|
.stream()
|
||||||
|
.map(t -> t.withPathPrefix(nextChain.get(2).getResourceType(), nextChain.get(2).getSearchParam().getName()))
|
||||||
|
.collect(Collectors.toSet()));
|
||||||
|
// discrete -> discrete -> contained -> discrete
|
||||||
|
updateMapOfReferenceLinks(referenceLinks, Lists.newArrayList(nextChain.get(0).getSearchParam().getPath(), mergePaths(nextChain.get(1).getSearchParam().getPath(), nextChain.get(2).getSearchParam().getPath())), leafNodes);
|
||||||
|
// discrete -> contained -> discrete -> discrete
|
||||||
|
updateMapOfReferenceLinks(referenceLinks, Lists.newArrayList(mergePaths(nextChain.get(0).getSearchParam().getPath(), nextChain.get(1).getSearchParam().getPath()), nextChain.get(2).getSearchParam().getPath()), leafNodes);
|
||||||
|
// discrete -> contained -> discrete -> contained
|
||||||
|
updateMapOfReferenceLinks(referenceLinks, Lists.newArrayList(mergePaths(nextChain.get(0).getSearchParam().getPath(), nextChain.get(1).getSearchParam().getPath())),
|
||||||
|
leafNodes
|
||||||
|
.stream()
|
||||||
|
.map(t -> t.withPathPrefix(nextChain.get(2).getResourceType(), nextChain.get(2).getSearchParam().getName()))
|
||||||
|
.collect(Collectors.toSet()));
|
||||||
|
if (myModelConfig.isIndexOnContainedResourcesRecursively()) {
|
||||||
|
// discrete -> contained -> contained -> discrete
|
||||||
|
updateMapOfReferenceLinks(referenceLinks, Lists.newArrayList(mergePaths(nextChain.get(0).getSearchParam().getPath(), nextChain.get(1).getSearchParam().getPath(), nextChain.get(2).getSearchParam().getPath())), leafNodes);
|
||||||
|
// discrete -> discrete -> contained -> contained
|
||||||
|
updateMapOfReferenceLinks(referenceLinks, Lists.newArrayList(nextChain.get(0).getSearchParam().getPath()),
|
||||||
|
leafNodes
|
||||||
|
.stream()
|
||||||
|
.map(t -> t.withPathPrefix(nextChain.get(1).getResourceType(), nextChain.get(1).getSearchParam().getName() + "." + nextChain.get(2).getSearchParam().getName()))
|
||||||
|
.collect(Collectors.toSet()));
|
||||||
|
// discrete -> contained -> contained -> contained
|
||||||
|
updateMapOfReferenceLinks(referenceLinks, Lists.newArrayList(),
|
||||||
|
leafNodes
|
||||||
|
.stream()
|
||||||
|
.map(t -> t.withPathPrefix(nextChain.get(0).getResourceType(), nextChain.get(0).getSearchParam().getName() + "." + nextChain.get(1).getSearchParam().getName() + "." + nextChain.get(2).getSearchParam().getName()))
|
||||||
|
.collect(Collectors.toSet()));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// TODO: the chain is too long, it isn't practical to hard-code all the possible patterns. If anyone ever needs this, we should revisit the approach
|
||||||
|
throw new InvalidRequestException(
|
||||||
|
"The search chain is too long. Only chains of up to three references are supported.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateMapOfReferenceLinks(Map<List<String>, Set<LeafNodeDefinition>> theReferenceLinksMap, ArrayList<String> thePath, Set<LeafNodeDefinition> theLeafNodesToAdd) {
|
||||||
|
Set<LeafNodeDefinition> leafNodes = theReferenceLinksMap.get(thePath);
|
||||||
|
if (leafNodes == null) {
|
||||||
|
leafNodes = Sets.newHashSet();
|
||||||
|
theReferenceLinksMap.put(thePath, leafNodes);
|
||||||
|
}
|
||||||
|
leafNodes.addAll(theLeafNodesToAdd);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String mergePaths(String... paths) {
|
||||||
|
String result = "";
|
||||||
|
for (String nextPath : paths) {
|
||||||
|
int separatorIndex = nextPath.indexOf('.');
|
||||||
|
if (StringUtils.isEmpty(result)) {
|
||||||
|
result = nextPath;
|
||||||
|
} else {
|
||||||
|
result = result + nextPath.substring(separatorIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Condition createIndexPredicate(DbColumn theSourceJoinColumn, String theResourceName, String theSpnamePrefix, String theParamName, RuntimeSearchParam theParamDefinition, ArrayList<IQueryParameterType> theOrValues, SearchFilterParser.CompareOperation theOperation, List<String> theQualifiers, RequestDetails theRequest, RequestPartitionId theRequestPartitionId, SearchQueryBuilder theSqlBuilder) {
|
||||||
Condition containedCondition = null;
|
Condition containedCondition = null;
|
||||||
|
|
||||||
switch (targetParamDefinition.getParamType()) {
|
switch (theParamDefinition.getParamType()) {
|
||||||
case DATE:
|
case DATE:
|
||||||
containedCondition = createPredicateDate(theSourceJoinColumn, theResourceName, spnamePrefix, targetParamDefinition,
|
containedCondition = createPredicateDate(theSourceJoinColumn, theResourceName, theSpnamePrefix, theParamDefinition,
|
||||||
orValues, theOperation, theRequestPartitionId);
|
theOrValues, theOperation, theRequestPartitionId, theSqlBuilder);
|
||||||
break;
|
break;
|
||||||
case NUMBER:
|
case NUMBER:
|
||||||
containedCondition = createPredicateNumber(theSourceJoinColumn, theResourceName, spnamePrefix, targetParamDefinition,
|
containedCondition = createPredicateNumber(theSourceJoinColumn, theResourceName, theSpnamePrefix, theParamDefinition,
|
||||||
orValues, theOperation, theRequestPartitionId);
|
theOrValues, theOperation, theRequestPartitionId, theSqlBuilder);
|
||||||
break;
|
break;
|
||||||
case QUANTITY:
|
case QUANTITY:
|
||||||
containedCondition = createPredicateQuantity(theSourceJoinColumn, theResourceName, spnamePrefix, targetParamDefinition,
|
containedCondition = createPredicateQuantity(theSourceJoinColumn, theResourceName, theSpnamePrefix, theParamDefinition,
|
||||||
orValues, theOperation, theRequestPartitionId);
|
theOrValues, theOperation, theRequestPartitionId, theSqlBuilder);
|
||||||
break;
|
break;
|
||||||
case STRING:
|
case STRING:
|
||||||
containedCondition = createPredicateString(theSourceJoinColumn, theResourceName, spnamePrefix, targetParamDefinition,
|
containedCondition = createPredicateString(theSourceJoinColumn, theResourceName, theSpnamePrefix, theParamDefinition,
|
||||||
orValues, theOperation, theRequestPartitionId);
|
theOrValues, theOperation, theRequestPartitionId, theSqlBuilder);
|
||||||
break;
|
break;
|
||||||
case TOKEN:
|
case TOKEN:
|
||||||
containedCondition = createPredicateToken(theSourceJoinColumn, theResourceName, spnamePrefix, targetParamDefinition,
|
containedCondition = createPredicateToken(theSourceJoinColumn, theResourceName, theSpnamePrefix, theParamDefinition,
|
||||||
orValues, theOperation, theRequestPartitionId);
|
theOrValues, theOperation, theRequestPartitionId, theSqlBuilder);
|
||||||
break;
|
break;
|
||||||
case COMPOSITE:
|
case COMPOSITE:
|
||||||
containedCondition = createPredicateComposite(theSourceJoinColumn, theResourceName, spnamePrefix, targetParamDefinition,
|
containedCondition = createPredicateComposite(theSourceJoinColumn, theResourceName, theSpnamePrefix, theParamDefinition,
|
||||||
orValues, theRequestPartitionId);
|
theOrValues, theRequestPartitionId, theSqlBuilder);
|
||||||
break;
|
break;
|
||||||
case URI:
|
case URI:
|
||||||
containedCondition = createPredicateUri(theSourceJoinColumn, theResourceName, spnamePrefix, targetParamDefinition,
|
containedCondition = createPredicateUri(theSourceJoinColumn, theResourceName, theSpnamePrefix, theParamDefinition,
|
||||||
orValues, theOperation, theRequest, theRequestPartitionId);
|
theOrValues, theOperation, theRequest, theRequestPartitionId, theSqlBuilder);
|
||||||
break;
|
break;
|
||||||
case REFERENCE:
|
case REFERENCE:
|
||||||
String chainedParamName = theParamName + "." + targetParamName;
|
containedCondition = createPredicateReference(theSourceJoinColumn, theResourceName, StringUtils.isBlank(theSpnamePrefix) ? theParamName : theSpnamePrefix + "." + theParamName, theQualifiers,
|
||||||
containedCondition = createPredicateReference(theSourceJoinColumn, theResourceName, chainedParamName, theQualifiers, trimmedParameters, theOperation, theRequest, theRequestPartitionId);
|
theOrValues, theOperation, theRequest, theRequestPartitionId, theSqlBuilder);
|
||||||
if (myModelConfig.isIndexOnContainedResourcesRecursively()) {
|
|
||||||
containedCondition = toOrPredicate(containedCondition,
|
|
||||||
createPredicateReferenceForContainedResource(theSourceJoinColumn, theResourceName, chainedParamName, theQualifiers, theSearchParam, trimmedParameters, theOperation, theRequest, theRequestPartitionId));
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case HAS:
|
case HAS:
|
||||||
case SPECIAL:
|
case SPECIAL:
|
||||||
default:
|
default:
|
||||||
throw new InvalidRequestException(
|
throw new InvalidRequestException(
|
||||||
"The search type:" + targetParamDefinition.getParamType() + " is not supported.");
|
"The search type:" + theParamDefinition.getParamType() + " is not supported.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return containedCondition;
|
return containedCondition;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -857,10 +1167,17 @@ public class QueryStack {
|
||||||
public Condition createPredicateString(@Nullable DbColumn theSourceJoinColumn, String theResourceName,
|
public Condition createPredicateString(@Nullable DbColumn theSourceJoinColumn, String theResourceName,
|
||||||
String theSpnamePrefix, RuntimeSearchParam theSearchParam, List<? extends IQueryParameterType> theList,
|
String theSpnamePrefix, RuntimeSearchParam theSearchParam, List<? extends IQueryParameterType> theList,
|
||||||
SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) {
|
SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) {
|
||||||
|
return createPredicateString(theSourceJoinColumn, theResourceName, theSpnamePrefix, theSearchParam, theList, theOperation, theRequestPartitionId, mySqlBuilder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Condition createPredicateString(@Nullable DbColumn theSourceJoinColumn, String theResourceName,
|
||||||
|
String theSpnamePrefix, RuntimeSearchParam theSearchParam, List<? extends IQueryParameterType> theList,
|
||||||
|
SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId,
|
||||||
|
SearchQueryBuilder theSqlBuilder) {
|
||||||
|
|
||||||
String paramName = getParamNameWithPrefix(theSpnamePrefix, theSearchParam.getName());
|
String paramName = getParamNameWithPrefix(theSpnamePrefix, theSearchParam.getName());
|
||||||
|
|
||||||
StringPredicateBuilder join = createOrReusePredicateBuilder(PredicateBuilderTypeEnum.STRING, theSourceJoinColumn, paramName, () -> mySqlBuilder.addStringPredicateBuilder(theSourceJoinColumn)).getResult();
|
StringPredicateBuilder join = createOrReusePredicateBuilder(PredicateBuilderTypeEnum.STRING, theSourceJoinColumn, paramName, () -> theSqlBuilder.addStringPredicateBuilder(theSourceJoinColumn)).getResult();
|
||||||
|
|
||||||
if (theList.get(0).getMissing() != null) {
|
if (theList.get(0).getMissing() != null) {
|
||||||
return join.createPredicateParamMissingForNonReference(theResourceName, paramName, theList.get(0).getMissing(), theRequestPartitionId);
|
return join.createPredicateParamMissingForNonReference(theResourceName, paramName, theList.get(0).getMissing(), theRequestPartitionId);
|
||||||
|
@ -967,6 +1284,12 @@ public class QueryStack {
|
||||||
public Condition createPredicateToken(@Nullable DbColumn theSourceJoinColumn, String theResourceName,
|
public Condition createPredicateToken(@Nullable DbColumn theSourceJoinColumn, String theResourceName,
|
||||||
String theSpnamePrefix, RuntimeSearchParam theSearchParam, List<? extends IQueryParameterType> theList,
|
String theSpnamePrefix, RuntimeSearchParam theSearchParam, List<? extends IQueryParameterType> theList,
|
||||||
SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) {
|
SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) {
|
||||||
|
return createPredicateToken(theSourceJoinColumn, theResourceName, theSpnamePrefix, theSearchParam, theList, theOperation, theRequestPartitionId, mySqlBuilder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Condition createPredicateToken(@Nullable DbColumn theSourceJoinColumn, String theResourceName,
|
||||||
|
String theSpnamePrefix, RuntimeSearchParam theSearchParam, List<? extends IQueryParameterType> theList,
|
||||||
|
SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId, SearchQueryBuilder theSqlBuilder) {
|
||||||
|
|
||||||
List<IQueryParameterType> tokens = new ArrayList<>();
|
List<IQueryParameterType> tokens = new ArrayList<>();
|
||||||
|
|
||||||
|
@ -991,7 +1314,7 @@ public class QueryStack {
|
||||||
throw new MethodNotAllowedException(msg);
|
throw new MethodNotAllowedException(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
return createPredicateString(theSourceJoinColumn, theResourceName, theSpnamePrefix, theSearchParam, theList, null, theRequestPartitionId);
|
return createPredicateString(theSourceJoinColumn, theResourceName, theSpnamePrefix, theSearchParam, theList, null, theRequestPartitionId, theSqlBuilder);
|
||||||
}
|
}
|
||||||
|
|
||||||
modifier = id.getModifier();
|
modifier = id.getModifier();
|
||||||
|
@ -1017,13 +1340,13 @@ public class QueryStack {
|
||||||
BaseJoiningPredicateBuilder join;
|
BaseJoiningPredicateBuilder join;
|
||||||
|
|
||||||
if (paramInverted) {
|
if (paramInverted) {
|
||||||
SearchQueryBuilder sqlBuilder = mySqlBuilder.newChildSqlBuilder();
|
SearchQueryBuilder sqlBuilder = theSqlBuilder.newChildSqlBuilder();
|
||||||
TokenPredicateBuilder tokenSelector = sqlBuilder.addTokenPredicateBuilder(null);
|
TokenPredicateBuilder tokenSelector = sqlBuilder.addTokenPredicateBuilder(null);
|
||||||
sqlBuilder.addPredicate(tokenSelector.createPredicateToken(tokens, theResourceName, theSpnamePrefix, theSearchParam, theRequestPartitionId));
|
sqlBuilder.addPredicate(tokenSelector.createPredicateToken(tokens, theResourceName, theSpnamePrefix, theSearchParam, theRequestPartitionId));
|
||||||
SelectQuery sql = sqlBuilder.getSelect();
|
SelectQuery sql = sqlBuilder.getSelect();
|
||||||
Expression subSelect = new Subquery(sql);
|
Expression subSelect = new Subquery(sql);
|
||||||
|
|
||||||
join = mySqlBuilder.getOrCreateFirstPredicateBuilder();
|
join = theSqlBuilder.getOrCreateFirstPredicateBuilder();
|
||||||
|
|
||||||
if (theSourceJoinColumn == null) {
|
if (theSourceJoinColumn == null) {
|
||||||
predicate = new InCondition(join.getResourceIdColumn(), subSelect).setNegate(true);
|
predicate = new InCondition(join.getResourceIdColumn(), subSelect).setNegate(true);
|
||||||
|
@ -1034,7 +1357,7 @@ public class QueryStack {
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
TokenPredicateBuilder tokenJoin = createOrReusePredicateBuilder(PredicateBuilderTypeEnum.TOKEN, theSourceJoinColumn, paramName, () -> mySqlBuilder.addTokenPredicateBuilder(theSourceJoinColumn)).getResult();
|
TokenPredicateBuilder tokenJoin = createOrReusePredicateBuilder(PredicateBuilderTypeEnum.TOKEN, theSourceJoinColumn, paramName, () -> theSqlBuilder.addTokenPredicateBuilder(theSourceJoinColumn)).getResult();
|
||||||
|
|
||||||
if (theList.get(0).getMissing() != null) {
|
if (theList.get(0).getMissing() != null) {
|
||||||
return tokenJoin.createPredicateParamMissingForNonReference(theResourceName, paramName, theList.get(0).getMissing(), theRequestPartitionId);
|
return tokenJoin.createPredicateParamMissingForNonReference(theResourceName, paramName, theList.get(0).getMissing(), theRequestPartitionId);
|
||||||
|
@ -1051,10 +1374,17 @@ public class QueryStack {
|
||||||
String theSpnamePrefix, RuntimeSearchParam theSearchParam, List<? extends IQueryParameterType> theList,
|
String theSpnamePrefix, RuntimeSearchParam theSearchParam, List<? extends IQueryParameterType> theList,
|
||||||
SearchFilterParser.CompareOperation theOperation, RequestDetails theRequestDetails,
|
SearchFilterParser.CompareOperation theOperation, RequestDetails theRequestDetails,
|
||||||
RequestPartitionId theRequestPartitionId) {
|
RequestPartitionId theRequestPartitionId) {
|
||||||
|
return createPredicateUri(theSourceJoinColumn, theResourceName, theSpnamePrefix, theSearchParam, theList, theOperation, theRequestDetails, theRequestPartitionId, mySqlBuilder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Condition createPredicateUri(@Nullable DbColumn theSourceJoinColumn, String theResourceName,
|
||||||
|
String theSpnamePrefix, RuntimeSearchParam theSearchParam, List<? extends IQueryParameterType> theList,
|
||||||
|
SearchFilterParser.CompareOperation theOperation, RequestDetails theRequestDetails,
|
||||||
|
RequestPartitionId theRequestPartitionId, SearchQueryBuilder theSqlBuilder) {
|
||||||
|
|
||||||
String paramName = getParamNameWithPrefix(theSpnamePrefix, theSearchParam.getName());
|
String paramName = getParamNameWithPrefix(theSpnamePrefix, theSearchParam.getName());
|
||||||
|
|
||||||
UriPredicateBuilder join = mySqlBuilder.addUriPredicateBuilder(theSourceJoinColumn);
|
UriPredicateBuilder join = theSqlBuilder.addUriPredicateBuilder(theSourceJoinColumn);
|
||||||
|
|
||||||
if (theList.get(0).getMissing() != null) {
|
if (theList.get(0).getMissing() != null) {
|
||||||
return join.createPredicateParamMissingForNonReference(theResourceName, paramName, theList.get(0).getMissing(), theRequestPartitionId);
|
return join.createPredicateParamMissingForNonReference(theResourceName, paramName, theList.get(0).getMissing(), theRequestPartitionId);
|
||||||
|
@ -1140,10 +1470,7 @@ public class QueryStack {
|
||||||
case REFERENCE:
|
case REFERENCE:
|
||||||
for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) {
|
for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) {
|
||||||
if (isEligibleForContainedResourceSearch(nextAnd)) {
|
if (isEligibleForContainedResourceSearch(nextAnd)) {
|
||||||
andPredicates.add(toOrPredicate(
|
andPredicates.add(createPredicateReferenceForContainedResource(theSourceJoinColumn, theResourceName, theParamName, new ArrayList<>(), nextParamDef, nextAnd, null, theRequest, theRequestPartitionId));
|
||||||
createPredicateReference(theSourceJoinColumn, theResourceName, theParamName, new ArrayList<>(), nextAnd, null, theRequest, theRequestPartitionId),
|
|
||||||
createPredicateReferenceForContainedResource(theSourceJoinColumn, theResourceName, theParamName, new ArrayList<>(), nextParamDef, nextAnd, null, theRequest, theRequestPartitionId)
|
|
||||||
));
|
|
||||||
} else {
|
} else {
|
||||||
andPredicates.add(createPredicateReference(theSourceJoinColumn, theResourceName, theParamName, new ArrayList<>(), nextAnd, null, theRequest, theRequestPartitionId));
|
andPredicates.add(createPredicateReference(theSourceJoinColumn, theResourceName, theParamName, new ArrayList<>(), nextAnd, null, theRequest, theRequestPartitionId));
|
||||||
}
|
}
|
||||||
|
|
|
@ -265,7 +265,7 @@ public class ResourceLinkPredicateBuilder extends BaseJoiningPredicateBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
private Condition createPredicateSourcePaths(List<String> thePathsToMatch) {
|
public Condition createPredicateSourcePaths(List<String> thePathsToMatch) {
|
||||||
return toEqualToOrInPredicate(myColumnSrcPath, generatePlaceholders(thePathsToMatch));
|
return toEqualToOrInPredicate(myColumnSrcPath, generatePlaceholders(thePathsToMatch));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -140,6 +140,7 @@ import org.hl7.fhir.r4.model.NamingSystem;
|
||||||
import org.hl7.fhir.r4.model.Observation;
|
import org.hl7.fhir.r4.model.Observation;
|
||||||
import org.hl7.fhir.r4.model.OperationDefinition;
|
import org.hl7.fhir.r4.model.OperationDefinition;
|
||||||
import org.hl7.fhir.r4.model.Organization;
|
import org.hl7.fhir.r4.model.Organization;
|
||||||
|
import org.hl7.fhir.r4.model.OrganizationAffiliation;
|
||||||
import org.hl7.fhir.r4.model.Patient;
|
import org.hl7.fhir.r4.model.Patient;
|
||||||
import org.hl7.fhir.r4.model.Practitioner;
|
import org.hl7.fhir.r4.model.Practitioner;
|
||||||
import org.hl7.fhir.r4.model.PractitionerRole;
|
import org.hl7.fhir.r4.model.PractitionerRole;
|
||||||
|
@ -370,6 +371,9 @@ public abstract class BaseJpaR4Test extends BaseJpaTest implements ITestDataBuil
|
||||||
@Qualifier("myOrganizationDaoR4")
|
@Qualifier("myOrganizationDaoR4")
|
||||||
protected IFhirResourceDao<Organization> myOrganizationDao;
|
protected IFhirResourceDao<Organization> myOrganizationDao;
|
||||||
@Autowired
|
@Autowired
|
||||||
|
@Qualifier("myOrganizationAffiliationDaoR4")
|
||||||
|
protected IFhirResourceDao<OrganizationAffiliation> myOrganizationAffiliationDao;
|
||||||
|
@Autowired
|
||||||
protected DatabaseBackedPagingProvider myPagingProvider;
|
protected DatabaseBackedPagingProvider myPagingProvider;
|
||||||
@Autowired
|
@Autowired
|
||||||
@Qualifier("myBinaryDaoR4")
|
@Qualifier("myBinaryDaoR4")
|
||||||
|
|
|
@ -5,8 +5,10 @@ import ca.uhn.fhir.jpa.model.entity.ModelConfig;
|
||||||
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
|
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
|
||||||
import ca.uhn.fhir.jpa.searchparam.ResourceSearch;
|
import ca.uhn.fhir.jpa.searchparam.ResourceSearch;
|
||||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||||
|
import ca.uhn.fhir.jpa.util.SqlQuery;
|
||||||
import ca.uhn.fhir.parser.StrictErrorHandler;
|
import ca.uhn.fhir.parser.StrictErrorHandler;
|
||||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||||
|
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
import org.hl7.fhir.r4.model.Device;
|
import org.hl7.fhir.r4.model.Device;
|
||||||
import org.hl7.fhir.r4.model.IdType;
|
import org.hl7.fhir.r4.model.IdType;
|
||||||
|
@ -25,9 +27,11 @@ import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.apache.commons.lang3.StringUtils.countMatches;
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
import static org.hamcrest.Matchers.contains;
|
import static org.hamcrest.Matchers.contains;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
|
||||||
|
|
||||||
public class ChainingR4SearchTest extends BaseJpaR4Test {
|
public class ChainingR4SearchTest extends BaseJpaR4Test {
|
||||||
|
@ -46,7 +50,6 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
|
||||||
myDaoConfig.setAllowContainsSearches(new DaoConfig().isAllowContainsSearches());
|
myDaoConfig.setAllowContainsSearches(new DaoConfig().isAllowContainsSearches());
|
||||||
myDaoConfig.setIndexMissingFields(new DaoConfig().getIndexMissingFields());
|
myDaoConfig.setIndexMissingFields(new DaoConfig().getIndexMissingFields());
|
||||||
|
|
||||||
myModelConfig.setIndexOnContainedResources(false);
|
|
||||||
myModelConfig.setIndexOnContainedResources(new ModelConfig().isIndexOnContainedResources());
|
myModelConfig.setIndexOnContainedResources(new ModelConfig().isIndexOnContainedResources());
|
||||||
myModelConfig.setIndexOnContainedResourcesRecursively(new ModelConfig().isIndexOnContainedResourcesRecursively());
|
myModelConfig.setIndexOnContainedResourcesRecursively(new ModelConfig().isIndexOnContainedResourcesRecursively());
|
||||||
}
|
}
|
||||||
|
@ -57,12 +60,11 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
|
||||||
|
|
||||||
myDaoConfig.setAllowMultipleDelete(true);
|
myDaoConfig.setAllowMultipleDelete(true);
|
||||||
myDaoConfig.setSearchPreFetchThresholds(new DaoConfig().getSearchPreFetchThresholds());
|
myDaoConfig.setSearchPreFetchThresholds(new DaoConfig().getSearchPreFetchThresholds());
|
||||||
myModelConfig.setIndexOnContainedResources(true);
|
|
||||||
myDaoConfig.setReuseCachedSearchResultsForMillis(null);
|
myDaoConfig.setReuseCachedSearchResultsForMillis(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testShouldResolveATwoLinkChainWithStandAloneResources() throws Exception {
|
public void testShouldResolveATwoLinkChainWithStandAloneResourcesWithoutContainedResourceIndexing() throws Exception {
|
||||||
|
|
||||||
// setup
|
// setup
|
||||||
IIdType oid1;
|
IIdType oid1;
|
||||||
|
@ -78,6 +80,43 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
|
||||||
obs.getSubject().setReference(p.getId());
|
obs.getSubject().setReference(p.getId());
|
||||||
|
|
||||||
oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||||
|
|
||||||
|
// Create a dummy record so that an unconstrained query doesn't pass the test due to returning the only record
|
||||||
|
myObservationDao.create(new Observation(), mySrd);
|
||||||
|
}
|
||||||
|
|
||||||
|
String url = "/Observation?subject.name=Smith";
|
||||||
|
|
||||||
|
// execute
|
||||||
|
List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(url);
|
||||||
|
|
||||||
|
// validate
|
||||||
|
assertEquals(1L, oids.size());
|
||||||
|
assertThat(oids, contains(oid1.getIdPart()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testShouldResolveATwoLinkChainWithStandAloneResources() throws Exception {
|
||||||
|
|
||||||
|
// setup
|
||||||
|
myModelConfig.setIndexOnContainedResources(true);
|
||||||
|
|
||||||
|
IIdType oid1;
|
||||||
|
|
||||||
|
{
|
||||||
|
Patient p = new Patient();
|
||||||
|
p.setId(IdType.newRandomUuid());
|
||||||
|
p.addName().setFamily("Smith").addGiven("John");
|
||||||
|
myPatientDao.create(p, mySrd);
|
||||||
|
|
||||||
|
Observation obs = new Observation();
|
||||||
|
obs.getCode().setText("Observation 1");
|
||||||
|
obs.getSubject().setReference(p.getId());
|
||||||
|
|
||||||
|
oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||||
|
|
||||||
|
// Create a dummy record so that an unconstrained query doesn't pass the test due to returning the only record
|
||||||
|
myObservationDao.create(new Observation(), mySrd);
|
||||||
}
|
}
|
||||||
|
|
||||||
String url = "/Observation?subject.name=Smith";
|
String url = "/Observation?subject.name=Smith";
|
||||||
|
@ -93,6 +132,8 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
|
||||||
@Test
|
@Test
|
||||||
public void testShouldResolveATwoLinkChainWithAContainedResource() throws Exception {
|
public void testShouldResolveATwoLinkChainWithAContainedResource() throws Exception {
|
||||||
// setup
|
// setup
|
||||||
|
myModelConfig.setIndexOnContainedResources(true);
|
||||||
|
|
||||||
IIdType oid1;
|
IIdType oid1;
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -107,6 +148,11 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
|
||||||
obs.getSubject().setReference("#pat");
|
obs.getSubject().setReference("#pat");
|
||||||
|
|
||||||
oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||||
|
|
||||||
|
// Create a dummy record so that an unconstrained query doesn't pass the test due to returning the only record
|
||||||
|
myCaptureQueriesListener.clear();
|
||||||
|
myObservationDao.create(new Observation(), mySrd);
|
||||||
|
myCaptureQueriesListener.logInsertQueries();
|
||||||
}
|
}
|
||||||
|
|
||||||
String url = "/Observation?subject.name=Smith";
|
String url = "/Observation?subject.name=Smith";
|
||||||
|
@ -119,12 +165,45 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
|
||||||
assertThat(oids, contains(oid1.getIdPart()));
|
assertThat(oids, contains(oid1.getIdPart()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testShouldNotResolveATwoLinkChainWithAContainedResourceWhenContainedResourceIndexingIsTurnedOff() throws Exception {
|
||||||
|
// setup
|
||||||
|
IIdType oid1;
|
||||||
|
|
||||||
|
{
|
||||||
|
Patient p = new Patient();
|
||||||
|
p.setId("pat");
|
||||||
|
p.addName().setFamily("Smith").addGiven("John");
|
||||||
|
|
||||||
|
Observation obs = new Observation();
|
||||||
|
obs.getContained().add(p);
|
||||||
|
obs.getCode().setText("Observation 1");
|
||||||
|
obs.setValue(new StringType("Test"));
|
||||||
|
obs.getSubject().setReference("#pat");
|
||||||
|
|
||||||
|
oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||||
|
|
||||||
|
// Create a dummy record so that an unconstrained query doesn't pass the test due to returning the only record
|
||||||
|
myObservationDao.create(new Observation(), mySrd);
|
||||||
|
}
|
||||||
|
|
||||||
|
String url = "/Observation?subject.name=Smith";
|
||||||
|
|
||||||
|
// execute
|
||||||
|
List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(url);
|
||||||
|
|
||||||
|
// validate
|
||||||
|
assertEquals(0L, oids.size());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Disabled
|
@Disabled
|
||||||
public void testShouldResolveATwoLinkChainWithQualifiersWithAContainedResource() throws Exception {
|
public void testShouldResolveATwoLinkChainWithQualifiersWithAContainedResource() throws Exception {
|
||||||
// TODO: This test fails because of a known limitation in qualified searches over contained resources.
|
// TODO: This test fails because of a known limitation in qualified searches over contained resources.
|
||||||
// Type information for intermediate resources in the chain is not being retained in the indexes.
|
// Type information for intermediate resources in the chain is not being retained in the indexes.
|
||||||
// setup
|
// setup
|
||||||
|
myModelConfig.setIndexOnContainedResources(true);
|
||||||
|
|
||||||
IIdType oid1;
|
IIdType oid1;
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -151,6 +230,9 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
|
||||||
obs2.getSubject().setReference("#loc");
|
obs2.getSubject().setReference("#loc");
|
||||||
|
|
||||||
myObservationDao.create(obs2, mySrd);
|
myObservationDao.create(obs2, mySrd);
|
||||||
|
|
||||||
|
// Create a dummy record so that an unconstrained query doesn't pass the test due to returning the only record
|
||||||
|
myObservationDao.create(new Observation(), mySrd);
|
||||||
}
|
}
|
||||||
|
|
||||||
String url = "/Observation?subject:Patient.name=Smith";
|
String url = "/Observation?subject:Patient.name=Smith";
|
||||||
|
@ -168,6 +250,8 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
|
||||||
// Adding support for this case in SMILE-3151
|
// Adding support for this case in SMILE-3151
|
||||||
|
|
||||||
// setup
|
// setup
|
||||||
|
myModelConfig.setIndexOnContainedResources(true);
|
||||||
|
|
||||||
IIdType oid1;
|
IIdType oid1;
|
||||||
IIdType orgId;
|
IIdType orgId;
|
||||||
|
|
||||||
|
@ -188,6 +272,9 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
|
||||||
obs.getSubject().setReference("#pat");
|
obs.getSubject().setReference("#pat");
|
||||||
|
|
||||||
oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||||
|
|
||||||
|
// Create a dummy record so that an unconstrained query doesn't pass the test due to returning the only record
|
||||||
|
myObservationDao.create(new Observation(), mySrd);
|
||||||
}
|
}
|
||||||
|
|
||||||
String url = "/Observation?subject.organization=" + orgId.getValueAsString();
|
String url = "/Observation?subject.organization=" + orgId.getValueAsString();
|
||||||
|
@ -201,7 +288,49 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testShouldResolveAThreeLinkChainWhereAllResourcesStandAlone() throws Exception {
|
public void testShouldResolveATwoLinkChainToAStandAloneReference() throws Exception {
|
||||||
|
// Adding support for this case in SMILE-3151
|
||||||
|
|
||||||
|
// setup
|
||||||
|
myModelConfig.setIndexOnContainedResources(true);
|
||||||
|
|
||||||
|
IIdType oid1;
|
||||||
|
IIdType orgId;
|
||||||
|
|
||||||
|
{
|
||||||
|
Organization org = new Organization();
|
||||||
|
org.setId(IdType.newRandomUuid());
|
||||||
|
org.setName("HealthCo");
|
||||||
|
orgId = myOrganizationDao.create(org, mySrd).getId();
|
||||||
|
|
||||||
|
Patient p = new Patient();
|
||||||
|
p.addName().setFamily("Smith").addGiven("John");
|
||||||
|
p.getManagingOrganization().setReference(org.getId());
|
||||||
|
myPatientDao.create(p, mySrd);
|
||||||
|
|
||||||
|
Observation obs = new Observation();
|
||||||
|
obs.getContained().add(p);
|
||||||
|
obs.getCode().setText("Observation 1");
|
||||||
|
obs.getSubject().setReference(p.getId());
|
||||||
|
|
||||||
|
oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||||
|
|
||||||
|
// Create a dummy record so that an unconstrained query doesn't pass the test due to returning the only record
|
||||||
|
myObservationDao.create(new Observation(), mySrd);
|
||||||
|
}
|
||||||
|
|
||||||
|
String url = "/Observation?subject.organization=" + orgId.getValueAsString();
|
||||||
|
|
||||||
|
// execute
|
||||||
|
List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(url);
|
||||||
|
|
||||||
|
// validate
|
||||||
|
assertEquals(1L, oids.size());
|
||||||
|
assertThat(oids, contains(oid1.getIdPart()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testShouldResolveAThreeLinkChainWhereAllResourcesStandAloneWithoutContainedResourceIndexing() throws Exception {
|
||||||
|
|
||||||
// setup
|
// setup
|
||||||
IIdType oid1;
|
IIdType oid1;
|
||||||
|
@ -223,6 +352,77 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
|
||||||
obs.getSubject().setReference(p.getId());
|
obs.getSubject().setReference(p.getId());
|
||||||
|
|
||||||
oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||||
|
|
||||||
|
// Create a dummy record so that an unconstrained query doesn't pass the test due to returning the only record
|
||||||
|
Organization dummyOrg = new Organization();
|
||||||
|
dummyOrg.setId(IdType.newRandomUuid());
|
||||||
|
dummyOrg.setName("Dummy");
|
||||||
|
myOrganizationDao.create(dummyOrg, mySrd);
|
||||||
|
|
||||||
|
Patient dummyPatient = new Patient();
|
||||||
|
dummyPatient.setId(IdType.newRandomUuid());
|
||||||
|
dummyPatient.addName().setFamily("Jones").addGiven("Jane");
|
||||||
|
dummyPatient.getManagingOrganization().setReference(dummyOrg.getId());
|
||||||
|
myPatientDao.create(dummyPatient, mySrd);
|
||||||
|
|
||||||
|
Observation dummyObs = new Observation();
|
||||||
|
dummyObs.getCode().setText("Observation 2");
|
||||||
|
dummyObs.getSubject().setReference(dummyPatient.getId());
|
||||||
|
myObservationDao.create(dummyObs, mySrd);
|
||||||
|
}
|
||||||
|
|
||||||
|
String url = "/Observation?subject.organization.name=HealthCo";
|
||||||
|
|
||||||
|
// execute
|
||||||
|
List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(url);
|
||||||
|
|
||||||
|
// validate
|
||||||
|
assertEquals(1L, oids.size());
|
||||||
|
assertThat(oids, contains(oid1.getIdPart()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testShouldResolveAThreeLinkChainWhereAllResourcesStandAlone() throws Exception {
|
||||||
|
|
||||||
|
// setup
|
||||||
|
myModelConfig.setIndexOnContainedResources(true);
|
||||||
|
|
||||||
|
IIdType oid1;
|
||||||
|
|
||||||
|
{
|
||||||
|
Organization org = new Organization();
|
||||||
|
org.setId(IdType.newRandomUuid());
|
||||||
|
org.setName("HealthCo");
|
||||||
|
myOrganizationDao.create(org, mySrd);
|
||||||
|
|
||||||
|
Patient p = new Patient();
|
||||||
|
p.setId(IdType.newRandomUuid());
|
||||||
|
p.addName().setFamily("Smith").addGiven("John");
|
||||||
|
p.getManagingOrganization().setReference(org.getId());
|
||||||
|
myPatientDao.create(p, mySrd);
|
||||||
|
|
||||||
|
Observation obs = new Observation();
|
||||||
|
obs.getCode().setText("Observation 1");
|
||||||
|
obs.getSubject().setReference(p.getId());
|
||||||
|
|
||||||
|
oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||||
|
|
||||||
|
// Create a dummy record so that an unconstrained query doesn't pass the test due to returning the only record
|
||||||
|
Organization dummyOrg = new Organization();
|
||||||
|
dummyOrg.setId(IdType.newRandomUuid());
|
||||||
|
dummyOrg.setName("Dummy");
|
||||||
|
myOrganizationDao.create(dummyOrg, mySrd);
|
||||||
|
|
||||||
|
Patient dummyPatient = new Patient();
|
||||||
|
dummyPatient.setId(IdType.newRandomUuid());
|
||||||
|
dummyPatient.addName().setFamily("Jones").addGiven("Jane");
|
||||||
|
dummyPatient.getManagingOrganization().setReference(dummyOrg.getId());
|
||||||
|
myPatientDao.create(dummyPatient, mySrd);
|
||||||
|
|
||||||
|
Observation dummyObs = new Observation();
|
||||||
|
dummyObs.getCode().setText("Observation 2");
|
||||||
|
dummyObs.getSubject().setReference(dummyPatient.getId());
|
||||||
|
myObservationDao.create(dummyObs, mySrd);
|
||||||
}
|
}
|
||||||
|
|
||||||
String url = "/Observation?subject.organization.name=HealthCo";
|
String url = "/Observation?subject.organization.name=HealthCo";
|
||||||
|
@ -240,6 +440,8 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
|
||||||
// This is the case that is most relevant to SMILE-2899
|
// This is the case that is most relevant to SMILE-2899
|
||||||
|
|
||||||
// setup
|
// setup
|
||||||
|
myModelConfig.setIndexOnContainedResources(true);
|
||||||
|
|
||||||
IIdType oid1;
|
IIdType oid1;
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -259,6 +461,9 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
|
||||||
obs.getSubject().setReference(p.getId());
|
obs.getSubject().setReference(p.getId());
|
||||||
|
|
||||||
oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||||
|
|
||||||
|
// Create a dummy record so that an unconstrained query doesn't pass the test due to returning the only record
|
||||||
|
myObservationDao.create(new Observation(), mySrd);
|
||||||
}
|
}
|
||||||
|
|
||||||
String url = "/Observation?subject.organization.name=HealthCo";
|
String url = "/Observation?subject.organization.name=HealthCo";
|
||||||
|
@ -276,6 +481,8 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
|
||||||
// Adding support for this case in SMILE-3151
|
// Adding support for this case in SMILE-3151
|
||||||
|
|
||||||
// setup
|
// setup
|
||||||
|
myModelConfig.setIndexOnContainedResources(true);
|
||||||
|
|
||||||
IIdType oid1;
|
IIdType oid1;
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -295,6 +502,9 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
|
||||||
obs.getSubject().setReference("#pat");
|
obs.getSubject().setReference("#pat");
|
||||||
|
|
||||||
oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||||
|
|
||||||
|
// Create a dummy record so that an unconstrained query doesn't pass the test due to returning the only record
|
||||||
|
myObservationDao.create(new Observation(), mySrd);
|
||||||
}
|
}
|
||||||
|
|
||||||
String url = "/Observation?subject.organization.name=HealthCo";
|
String url = "/Observation?subject.organization.name=HealthCo";
|
||||||
|
@ -307,10 +517,50 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
|
||||||
assertThat(oids, contains(oid1.getIdPart()));
|
assertThat(oids, contains(oid1.getIdPart()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testShouldNotResolveAThreeLinkChainWithAllContainedResourcesWhenRecursiveContainedIndexesAreDisabled() throws Exception {
|
||||||
|
|
||||||
|
// setup
|
||||||
|
myModelConfig.setIndexOnContainedResources(true);
|
||||||
|
|
||||||
|
IIdType oid1;
|
||||||
|
|
||||||
|
{
|
||||||
|
Organization org = new Organization();
|
||||||
|
org.setId("org");
|
||||||
|
org.setName("HealthCo");
|
||||||
|
|
||||||
|
Patient p = new Patient();
|
||||||
|
p.setId("pat");
|
||||||
|
p.addName().setFamily("Smith").addGiven("John");
|
||||||
|
p.getManagingOrganization().setReference("#org");
|
||||||
|
|
||||||
|
Observation obs = new Observation();
|
||||||
|
obs.getContained().add(p);
|
||||||
|
obs.getContained().add(org);
|
||||||
|
obs.getCode().setText("Observation 1");
|
||||||
|
obs.getSubject().setReference("#pat");
|
||||||
|
|
||||||
|
oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||||
|
|
||||||
|
// Create a dummy record so that an unconstrained query doesn't pass the test due to returning the only record
|
||||||
|
myObservationDao.create(new Observation(), mySrd);
|
||||||
|
}
|
||||||
|
|
||||||
|
String url = "/Observation?subject.organization.name=HealthCo";
|
||||||
|
|
||||||
|
// execute
|
||||||
|
List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(url);
|
||||||
|
|
||||||
|
// validate
|
||||||
|
assertEquals(0L, oids.size());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testShouldResolveAThreeLinkChainWithAllContainedResources() throws Exception {
|
public void testShouldResolveAThreeLinkChainWithAllContainedResources() throws Exception {
|
||||||
|
|
||||||
// setup
|
// setup
|
||||||
|
myModelConfig.setIndexOnContainedResources(true);
|
||||||
myModelConfig.setIndexOnContainedResourcesRecursively(true);
|
myModelConfig.setIndexOnContainedResourcesRecursively(true);
|
||||||
|
|
||||||
IIdType oid1;
|
IIdType oid1;
|
||||||
|
@ -332,6 +582,9 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
|
||||||
obs.getSubject().setReference("#pat");
|
obs.getSubject().setReference("#pat");
|
||||||
|
|
||||||
oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||||
|
|
||||||
|
// Create a dummy record so that an unconstrained query doesn't pass the test due to returning the only record
|
||||||
|
myObservationDao.create(new Observation(), mySrd);
|
||||||
}
|
}
|
||||||
|
|
||||||
String url = "/Observation?subject.organization.name=HealthCo";
|
String url = "/Observation?subject.organization.name=HealthCo";
|
||||||
|
@ -350,6 +603,8 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
|
||||||
public void testShouldResolveAThreeLinkChainWithQualifiersWhereAllResourcesStandAlone() throws Exception {
|
public void testShouldResolveAThreeLinkChainWithQualifiersWhereAllResourcesStandAlone() throws Exception {
|
||||||
|
|
||||||
// setup
|
// setup
|
||||||
|
myModelConfig.setIndexOnContainedResources(true);
|
||||||
|
|
||||||
IIdType oid1;
|
IIdType oid1;
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -379,6 +634,9 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
|
||||||
|
|
||||||
oid1 = myObservationDao.create(obs1, mySrd).getId().toUnqualifiedVersionless();
|
oid1 = myObservationDao.create(obs1, mySrd).getId().toUnqualifiedVersionless();
|
||||||
myObservationDao.create(obs2, mySrd);
|
myObservationDao.create(obs2, mySrd);
|
||||||
|
|
||||||
|
// Create a dummy record so that an unconstrained query doesn't pass the test due to returning the only record
|
||||||
|
myObservationDao.create(new Observation(), mySrd);
|
||||||
}
|
}
|
||||||
|
|
||||||
String url = "/Observation?subject:Patient.organization:Organization.name=HealthCo";
|
String url = "/Observation?subject:Patient.organization:Organization.name=HealthCo";
|
||||||
|
@ -396,6 +654,8 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
|
||||||
// This is the case that is most relevant to SMILE-2899
|
// This is the case that is most relevant to SMILE-2899
|
||||||
|
|
||||||
// setup
|
// setup
|
||||||
|
myModelConfig.setIndexOnContainedResources(true);
|
||||||
|
|
||||||
IIdType oid1;
|
IIdType oid1;
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -430,6 +690,9 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
|
||||||
obs2.getCode().setText("Observation 2");
|
obs2.getCode().setText("Observation 2");
|
||||||
obs2.getSubject().setReference(d.getId());
|
obs2.getSubject().setReference(d.getId());
|
||||||
myObservationDao.create(obs2, mySrd);
|
myObservationDao.create(obs2, mySrd);
|
||||||
|
|
||||||
|
// Create a dummy record so that an unconstrained query doesn't pass the test due to returning the only record
|
||||||
|
myObservationDao.create(new Observation(), mySrd);
|
||||||
}
|
}
|
||||||
|
|
||||||
String url = "/Observation?subject:Patient.organization:Organization.name=HealthCo";
|
String url = "/Observation?subject:Patient.organization:Organization.name=HealthCo";
|
||||||
|
@ -447,6 +710,8 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
|
||||||
// Adding support for this case in SMILE-3151
|
// Adding support for this case in SMILE-3151
|
||||||
|
|
||||||
// setup
|
// setup
|
||||||
|
myModelConfig.setIndexOnContainedResources(true);
|
||||||
|
|
||||||
IIdType oid1;
|
IIdType oid1;
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -477,6 +742,9 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
|
||||||
obs2.getSubject().setReference("#dev");
|
obs2.getSubject().setReference("#dev");
|
||||||
|
|
||||||
myObservationDao.create(obs2, mySrd);
|
myObservationDao.create(obs2, mySrd);
|
||||||
|
|
||||||
|
// Create a dummy record so that an unconstrained query doesn't pass the test due to returning the only record
|
||||||
|
myObservationDao.create(new Observation(), mySrd);
|
||||||
}
|
}
|
||||||
|
|
||||||
String url = "/Observation?subject:Patient.organization:Organization.name=HealthCo";
|
String url = "/Observation?subject:Patient.organization:Organization.name=HealthCo";
|
||||||
|
@ -500,6 +768,8 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
|
||||||
// Adding support for this case in SMILE-3151
|
// Adding support for this case in SMILE-3151
|
||||||
|
|
||||||
// setup
|
// setup
|
||||||
|
myModelConfig.setIndexOnContainedResources(true);
|
||||||
|
|
||||||
IIdType oid1;
|
IIdType oid1;
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -530,6 +800,9 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
|
||||||
obs2.getSubject().setReference("#loc");
|
obs2.getSubject().setReference("#loc");
|
||||||
|
|
||||||
myObservationDao.create(obs2, mySrd);
|
myObservationDao.create(obs2, mySrd);
|
||||||
|
|
||||||
|
// Create a dummy record so that an unconstrained query doesn't pass the test due to returning the only record
|
||||||
|
myObservationDao.create(new Observation(), mySrd);
|
||||||
}
|
}
|
||||||
|
|
||||||
String url = "/Observation?subject:Patient.organization:Organization.name=HealthCo";
|
String url = "/Observation?subject:Patient.organization:Organization.name=HealthCo";
|
||||||
|
@ -551,6 +824,7 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
|
||||||
// Type information for intermediate resources in the chain is not being retained in the indexes.
|
// Type information for intermediate resources in the chain is not being retained in the indexes.
|
||||||
|
|
||||||
// setup
|
// setup
|
||||||
|
myModelConfig.setIndexOnContainedResources(true);
|
||||||
myModelConfig.setIndexOnContainedResourcesRecursively(true);
|
myModelConfig.setIndexOnContainedResourcesRecursively(true);
|
||||||
|
|
||||||
IIdType oid1;
|
IIdType oid1;
|
||||||
|
@ -588,6 +862,9 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
|
||||||
obs2.getSubject().setReference("#dev");
|
obs2.getSubject().setReference("#dev");
|
||||||
|
|
||||||
myObservationDao.create(obs2, mySrd);
|
myObservationDao.create(obs2, mySrd);
|
||||||
|
|
||||||
|
// Create a dummy record so that an unconstrained query doesn't pass the test due to returning the only record
|
||||||
|
myObservationDao.create(new Observation(), mySrd);
|
||||||
}
|
}
|
||||||
|
|
||||||
String url = "/Observation?subject:Patient.organization:Organization.name=HealthCo";
|
String url = "/Observation?subject:Patient.organization:Organization.name=HealthCo";
|
||||||
|
@ -606,6 +883,8 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
|
||||||
public void testShouldResolveAFourLinkChainWhereAllResourcesStandAlone() throws Exception {
|
public void testShouldResolveAFourLinkChainWhereAllResourcesStandAlone() throws Exception {
|
||||||
|
|
||||||
// setup
|
// setup
|
||||||
|
myModelConfig.setIndexOnContainedResources(true);
|
||||||
|
|
||||||
IIdType oid1;
|
IIdType oid1;
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -630,6 +909,9 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
|
||||||
obs.getSubject().setReference(p.getId());
|
obs.getSubject().setReference(p.getId());
|
||||||
|
|
||||||
oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||||
|
|
||||||
|
// Create a dummy record so that an unconstrained query doesn't pass the test due to returning the only record
|
||||||
|
myObservationDao.create(new Observation(), mySrd);
|
||||||
}
|
}
|
||||||
|
|
||||||
String url = "/Observation?subject.organization.partof.name=HealthCo";
|
String url = "/Observation?subject.organization.partof.name=HealthCo";
|
||||||
|
@ -646,6 +928,8 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
|
||||||
public void testShouldResolveAFourLinkChainWhereTheLastReferenceIsContained() throws Exception {
|
public void testShouldResolveAFourLinkChainWhereTheLastReferenceIsContained() throws Exception {
|
||||||
|
|
||||||
// setup
|
// setup
|
||||||
|
myModelConfig.setIndexOnContainedResources(true);
|
||||||
|
|
||||||
IIdType oid1;
|
IIdType oid1;
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -670,6 +954,9 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
|
||||||
obs.getSubject().setReference(p.getId());
|
obs.getSubject().setReference(p.getId());
|
||||||
|
|
||||||
oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||||
|
|
||||||
|
// Create a dummy record so that an unconstrained query doesn't pass the test due to returning the only record
|
||||||
|
myObservationDao.create(new Observation(), mySrd);
|
||||||
}
|
}
|
||||||
|
|
||||||
String url = "/Observation?subject.organization.partof.name=HealthCo";
|
String url = "/Observation?subject.organization.partof.name=HealthCo";
|
||||||
|
@ -686,6 +973,7 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
|
||||||
public void testShouldResolveAFourLinkChainWhereTheLastTwoReferencesAreContained() throws Exception {
|
public void testShouldResolveAFourLinkChainWhereTheLastTwoReferencesAreContained() throws Exception {
|
||||||
|
|
||||||
// setup
|
// setup
|
||||||
|
myModelConfig.setIndexOnContainedResources(true);
|
||||||
myModelConfig.setIndexOnContainedResourcesRecursively(true);
|
myModelConfig.setIndexOnContainedResourcesRecursively(true);
|
||||||
IIdType oid1;
|
IIdType oid1;
|
||||||
|
|
||||||
|
@ -711,6 +999,9 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
|
||||||
obs.getSubject().setReference(p.getId());
|
obs.getSubject().setReference(p.getId());
|
||||||
|
|
||||||
oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||||
|
|
||||||
|
// Create a dummy record so that an unconstrained query doesn't pass the test due to returning the only record
|
||||||
|
myObservationDao.create(new Observation(), mySrd);
|
||||||
}
|
}
|
||||||
|
|
||||||
String url = "/Observation?subject.organization.partof.name=HealthCo";
|
String url = "/Observation?subject.organization.partof.name=HealthCo";
|
||||||
|
@ -727,6 +1018,8 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
|
||||||
public void testShouldResolveAFourLinkChainWithAContainedResourceInTheMiddle() throws Exception {
|
public void testShouldResolveAFourLinkChainWithAContainedResourceInTheMiddle() throws Exception {
|
||||||
|
|
||||||
// setup
|
// setup
|
||||||
|
myModelConfig.setIndexOnContainedResources(true);
|
||||||
|
|
||||||
IIdType oid1;
|
IIdType oid1;
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -755,6 +1048,9 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
|
||||||
oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||||
|
|
||||||
myCaptureQueriesListener.logInsertQueries();
|
myCaptureQueriesListener.logInsertQueries();
|
||||||
|
|
||||||
|
// Create a dummy record so that an unconstrained query doesn't pass the test due to returning the only record
|
||||||
|
myObservationDao.create(new Observation(), mySrd);
|
||||||
}
|
}
|
||||||
|
|
||||||
String url = "/Observation?subject.organization.partof.name=HealthCo";
|
String url = "/Observation?subject.organization.partof.name=HealthCo";
|
||||||
|
@ -773,6 +1069,7 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
|
||||||
public void testShouldResolveAFourLinkChainWhereTheFirstTwoReferencesAreContained() throws Exception {
|
public void testShouldResolveAFourLinkChainWhereTheFirstTwoReferencesAreContained() throws Exception {
|
||||||
|
|
||||||
// setup
|
// setup
|
||||||
|
myModelConfig.setIndexOnContainedResources(true);
|
||||||
myModelConfig.setIndexOnContainedResourcesRecursively(true);
|
myModelConfig.setIndexOnContainedResourcesRecursively(true);
|
||||||
IIdType oid1;
|
IIdType oid1;
|
||||||
|
|
||||||
|
@ -799,6 +1096,54 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
|
||||||
obs.getSubject().setReference("#pat");
|
obs.getSubject().setReference("#pat");
|
||||||
|
|
||||||
oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||||
|
|
||||||
|
// Create a dummy record so that an unconstrained query doesn't pass the test due to returning the only record
|
||||||
|
myObservationDao.create(new Observation(), mySrd);
|
||||||
|
}
|
||||||
|
|
||||||
|
String url = "/Observation?subject.organization.partof.name=HealthCo";
|
||||||
|
|
||||||
|
// execute
|
||||||
|
List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(url);
|
||||||
|
|
||||||
|
// validate
|
||||||
|
assertEquals(1L, oids.size());
|
||||||
|
assertThat(oids, contains(oid1.getIdPart()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testShouldResolveAFourLinkChainWhereTheFirstReferenceAndTheLastReferenceAreContained() throws Exception {
|
||||||
|
|
||||||
|
// setup
|
||||||
|
myModelConfig.setIndexOnContainedResources(true);
|
||||||
|
myModelConfig.setIndexOnContainedResourcesRecursively(true);
|
||||||
|
IIdType oid1;
|
||||||
|
|
||||||
|
{
|
||||||
|
Organization org = new Organization();
|
||||||
|
org.setId("parent");
|
||||||
|
org.setName("HealthCo");
|
||||||
|
|
||||||
|
Organization partOfOrg = new Organization();
|
||||||
|
partOfOrg.getContained().add(org);
|
||||||
|
partOfOrg.setId(IdType.newRandomUuid());
|
||||||
|
partOfOrg.getPartOf().setReference("#parent");
|
||||||
|
myOrganizationDao.create(partOfOrg, mySrd);
|
||||||
|
|
||||||
|
Patient p = new Patient();
|
||||||
|
p.setId("pat");
|
||||||
|
p.addName().setFamily("Smith").addGiven("John");
|
||||||
|
p.getManagingOrganization().setReference(partOfOrg.getId());
|
||||||
|
|
||||||
|
Observation obs = new Observation();
|
||||||
|
obs.getContained().add(p);
|
||||||
|
obs.getCode().setText("Observation 1");
|
||||||
|
obs.getSubject().setReference("#pat");
|
||||||
|
|
||||||
|
oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||||
|
|
||||||
|
// Create a dummy record so that an unconstrained query doesn't pass the test due to returning the only record
|
||||||
|
myObservationDao.create(new Observation(), mySrd);
|
||||||
}
|
}
|
||||||
|
|
||||||
String url = "/Observation?subject.organization.partof.name=HealthCo";
|
String url = "/Observation?subject.organization.partof.name=HealthCo";
|
||||||
|
@ -815,6 +1160,7 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
|
||||||
public void testShouldResolveAFourLinkChainWhereAllReferencesAreContained() throws Exception {
|
public void testShouldResolveAFourLinkChainWhereAllReferencesAreContained() throws Exception {
|
||||||
|
|
||||||
// setup
|
// setup
|
||||||
|
myModelConfig.setIndexOnContainedResources(true);
|
||||||
myModelConfig.setIndexOnContainedResourcesRecursively(true);
|
myModelConfig.setIndexOnContainedResourcesRecursively(true);
|
||||||
IIdType oid1;
|
IIdType oid1;
|
||||||
|
|
||||||
|
@ -840,6 +1186,9 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
|
||||||
obs.getSubject().setReference("#pat");
|
obs.getSubject().setReference("#pat");
|
||||||
|
|
||||||
oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||||
|
|
||||||
|
// Create a dummy record so that an unconstrained query doesn't pass the test due to returning the only record
|
||||||
|
myObservationDao.create(new Observation(), mySrd);
|
||||||
}
|
}
|
||||||
|
|
||||||
String url = "/Observation?subject.organization.partof.name=HealthCo";
|
String url = "/Observation?subject.organization.partof.name=HealthCo";
|
||||||
|
@ -852,6 +1201,64 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
|
||||||
assertThat(oids, contains(oid1.getIdPart()));
|
assertThat(oids, contains(oid1.getIdPart()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testShouldThrowAnExceptionForAFiveLinkChain() throws Exception {
|
||||||
|
|
||||||
|
// setup
|
||||||
|
myModelConfig.setIndexOnContainedResources(true);
|
||||||
|
myModelConfig.setIndexOnContainedResourcesRecursively(true);
|
||||||
|
|
||||||
|
String url = "/Observation?subject.organization.partof.partof.name=HealthCo";
|
||||||
|
|
||||||
|
try {
|
||||||
|
// execute
|
||||||
|
searchAndReturnUnqualifiedVersionlessIdValues(url);
|
||||||
|
fail("Expected an exception to be thrown");
|
||||||
|
} catch (InvalidRequestException e) {
|
||||||
|
assertEquals("The search chain subject.organization.partof.partof.name is too long. Only chains up to three references are supported.", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testQueryStructure() throws Exception {
|
||||||
|
|
||||||
|
// With indexing of contained resources turned off, we should not see UNION clauses in the query
|
||||||
|
countUnionStatementsInGeneratedQuery("/Observation?patient.name=Smith", 0);
|
||||||
|
countUnionStatementsInGeneratedQuery("/Observation?patient.organization.name=Smith", 0);
|
||||||
|
countUnionStatementsInGeneratedQuery("/Observation?patient.organization.partof.name=Smith", 0);
|
||||||
|
|
||||||
|
// With indexing of contained resources turned on, we take the UNION of several subselects that handle the different patterns of containment
|
||||||
|
// Keeping in mind that the number of clauses is one greater than the number of UNION keywords,
|
||||||
|
// this increases as the chain grows longer according to the Fibonacci sequence: (2, 3, 5, 8, 13)
|
||||||
|
myModelConfig.setIndexOnContainedResources(true);
|
||||||
|
countUnionStatementsInGeneratedQuery("/Observation?patient.name=Smith", 1);
|
||||||
|
countUnionStatementsInGeneratedQuery("/Observation?patient.organization.name=Smith", 2);
|
||||||
|
countUnionStatementsInGeneratedQuery("/Observation?patient.organization.partof.name=Smith", 4);
|
||||||
|
|
||||||
|
// With recursive indexing of contained resources turned on, even more containment patterns are considered
|
||||||
|
// This increases as the chain grows longer as powers of 2: (2, 4, 8, 16, 32)
|
||||||
|
myModelConfig.setIndexOnContainedResourcesRecursively(true);
|
||||||
|
countUnionStatementsInGeneratedQuery("/Observation?patient.name=Smith", 1);
|
||||||
|
countUnionStatementsInGeneratedQuery("/Observation?patient.organization.name=Smith", 3);
|
||||||
|
countUnionStatementsInGeneratedQuery("/Observation?patient.organization.partof.name=Smith", 7);
|
||||||
|
|
||||||
|
// If a reference in the chain has multiple potential target resource types, the number of subselects increases
|
||||||
|
countUnionStatementsInGeneratedQuery("/Observation?subject.name=Smith", 3);
|
||||||
|
|
||||||
|
// If such a reference if qualified to restrict the type, the number goes back down
|
||||||
|
countUnionStatementsInGeneratedQuery("/Observation?subject:Location.name=Smith", 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void countUnionStatementsInGeneratedQuery(String theUrl, int theExpectedNumberOfUnions) throws IOException {
|
||||||
|
myCaptureQueriesListener.clear();
|
||||||
|
searchAndReturnUnqualifiedVersionlessIdValues(theUrl);
|
||||||
|
List<SqlQuery> selectQueries = myCaptureQueriesListener.getSelectQueriesForCurrentThread();
|
||||||
|
assertEquals(1, selectQueries.size());
|
||||||
|
|
||||||
|
String sqlQuery = selectQueries.get(0).getSql(true, true).toLowerCase();
|
||||||
|
assertEquals(theExpectedNumberOfUnions, countMatches(sqlQuery, "union"), sqlQuery);
|
||||||
|
}
|
||||||
|
|
||||||
private List<String> searchAndReturnUnqualifiedVersionlessIdValues(String theUrl) throws IOException {
|
private List<String> searchAndReturnUnqualifiedVersionlessIdValues(String theUrl) throws IOException {
|
||||||
List<String> ids = new ArrayList<>();
|
List<String> ids = new ArrayList<>();
|
||||||
|
|
||||||
|
|
|
@ -166,6 +166,7 @@ public class SearchCoordinatorSvcImplTest {
|
||||||
@Test
|
@Test
|
||||||
public void testAsyncSearchFailDuringSearchSameCoordinator() {
|
public void testAsyncSearchFailDuringSearchSameCoordinator() {
|
||||||
initSearches();
|
initSearches();
|
||||||
|
initAsyncSearches();
|
||||||
|
|
||||||
SearchParameterMap params = new SearchParameterMap();
|
SearchParameterMap params = new SearchParameterMap();
|
||||||
params.add("name", new StringParam("ANAME"));
|
params.add("name", new StringParam("ANAME"));
|
||||||
|
@ -186,6 +187,7 @@ public class SearchCoordinatorSvcImplTest {
|
||||||
@Test
|
@Test
|
||||||
public void testAsyncSearchLargeResultSetBigCountSameCoordinator() {
|
public void testAsyncSearchLargeResultSetBigCountSameCoordinator() {
|
||||||
initSearches();
|
initSearches();
|
||||||
|
initAsyncSearches();
|
||||||
|
|
||||||
List<ResourcePersistentId> allResults = new ArrayList<>();
|
List<ResourcePersistentId> allResults = new ArrayList<>();
|
||||||
doAnswer(t -> {
|
doAnswer(t -> {
|
||||||
|
@ -281,6 +283,7 @@ public class SearchCoordinatorSvcImplTest {
|
||||||
@Test
|
@Test
|
||||||
public void testAsyncSearchLargeResultSetSameCoordinator() {
|
public void testAsyncSearchLargeResultSetSameCoordinator() {
|
||||||
initSearches();
|
initSearches();
|
||||||
|
initAsyncSearches();
|
||||||
|
|
||||||
SearchParameterMap params = new SearchParameterMap();
|
SearchParameterMap params = new SearchParameterMap();
|
||||||
params.add("name", new StringParam("ANAME"));
|
params.add("name", new StringParam("ANAME"));
|
||||||
|
@ -308,7 +311,9 @@ public class SearchCoordinatorSvcImplTest {
|
||||||
when(mySearchBuilderFactory.newSearchBuilder(any(), any(), any())).thenReturn(mySearchBuilder);
|
when(mySearchBuilderFactory.newSearchBuilder(any(), any(), any())).thenReturn(mySearchBuilder);
|
||||||
|
|
||||||
when(myTxManager.getTransaction(any())).thenReturn(mock(TransactionStatus.class));
|
when(myTxManager.getTransaction(any())).thenReturn(mock(TransactionStatus.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initAsyncSearches() {
|
||||||
when(myPersistedJpaBundleProviderFactory.newInstanceFirstPage(nullable(RequestDetails.class), nullable(Search.class), nullable(SearchCoordinatorSvcImpl.SearchTask.class), nullable(ISearchBuilder.class))).thenAnswer(t->{
|
when(myPersistedJpaBundleProviderFactory.newInstanceFirstPage(nullable(RequestDetails.class), nullable(Search.class), nullable(SearchCoordinatorSvcImpl.SearchTask.class), nullable(ISearchBuilder.class))).thenAnswer(t->{
|
||||||
RequestDetails requestDetails = t.getArgument(0, RequestDetails.class);
|
RequestDetails requestDetails = t.getArgument(0, RequestDetails.class);
|
||||||
Search search = t.getArgument(1, Search.class);
|
Search search = t.getArgument(1, Search.class);
|
||||||
|
@ -374,6 +379,7 @@ public class SearchCoordinatorSvcImplTest {
|
||||||
@Test
|
@Test
|
||||||
public void testAsyncSearchLargeResultSetSecondRequestSameCoordinator() {
|
public void testAsyncSearchLargeResultSetSecondRequestSameCoordinator() {
|
||||||
initSearches();
|
initSearches();
|
||||||
|
initAsyncSearches();
|
||||||
|
|
||||||
SearchParameterMap params = new SearchParameterMap();
|
SearchParameterMap params = new SearchParameterMap();
|
||||||
params.add("name", new StringParam("ANAME"));
|
params.add("name", new StringParam("ANAME"));
|
||||||
|
@ -412,6 +418,7 @@ public class SearchCoordinatorSvcImplTest {
|
||||||
@Test
|
@Test
|
||||||
public void testAsyncSearchSmallResultSetSameCoordinator() {
|
public void testAsyncSearchSmallResultSetSameCoordinator() {
|
||||||
initSearches();
|
initSearches();
|
||||||
|
initAsyncSearches();
|
||||||
|
|
||||||
SearchParameterMap params = new SearchParameterMap();
|
SearchParameterMap params = new SearchParameterMap();
|
||||||
params.add("name", new StringParam("ANAME"));
|
params.add("name", new StringParam("ANAME"));
|
||||||
|
|
Loading…
Reference in New Issue