Additional changes to enable processing of lastn operations with large numbers of parameters.

This commit is contained in:
ianmarshall 2020-05-05 16:53:47 -04:00
parent 3e49e5615f
commit 4db5beeabf
13 changed files with 367 additions and 257 deletions

View File

@ -38,7 +38,7 @@ public abstract class BaseHapiFhirResourceDaoObservation<T extends IBaseResource
if (theSearchParameterMap.getSort() == null) { if (theSearchParameterMap.getSort() == null) {
SortSpec effectiveDtm = new SortSpec("date").setOrder(SortOrderEnum.DESC); SortSpec effectiveDtm = new SortSpec("date").setOrder(SortOrderEnum.DESC);
SortSpec observationCode = new SortSpec(IndexConstants.CODE_SEARCH_PARAM).setOrder(SortOrderEnum.ASC).setChain(effectiveDtm); SortSpec observationCode = new SortSpec(IndexConstants.CODE_SEARCH_PARAM).setOrder(SortOrderEnum.ASC).setChain(effectiveDtm);
theSearchParameterMap.setSort(new SortSpec(IndexConstants.SUBJECT_SEARCH_PARAM).setChain(observationCode)); theSearchParameterMap.setSort(new SortSpec(IndexConstants.SUBJECT_SEARCH_PARAM).setOrder(SortOrderEnum.ASC).setChain(observationCode));
} }
} }

View File

@ -351,16 +351,19 @@ public class SearchBuilder implements ISearchBuilder {
} }
} }
Integer myMaxObservationsPerCode = null; Integer myMaxObservationsPerCode = null;
String[] maxCountParams = theRequest.getParameters().get("max"); // String[] maxCountParams = theRequest.getParameters().get("max");
if (maxCountParams != null && maxCountParams.length > 0) { // if (maxCountParams != null && maxCountParams.length > 0) {
myMaxObservationsPerCode = Integer.valueOf(maxCountParams[0]); // myMaxObservationsPerCode = Integer.valueOf(maxCountParams[0]);
if(myParams.getLastNMax() != null) {
myMaxObservationsPerCode = myParams.getLastNMax();
} else { } else {
throw new InvalidRequestException("Max parameter is required for $lastn operation"); throw new InvalidRequestException("Max parameter is required for $lastn operation");
} }
List<String> lastnResourceIds = myIElasticsearchSvc.executeLastN(myParams, myMaxObservationsPerCode); List<String> lastnResourceIds = myIElasticsearchSvc.executeLastN(myParams, myMaxObservationsPerCode);
for (String lastnResourceId : lastnResourceIds) { // for (String lastnResourceId : lastnResourceIds) {
pids.add(myIdHelperService.resolveResourcePersistentIds(myRequestPartitionId, myResourceName, lastnResourceId)); // pids.add(myIdHelperService.resolveResourcePersistentIds(myRequestPartitionId, myResourceName, lastnResourceId));
} // }
pids = normalizeIdListForLastNInClause(lastnResourceIds);
} }
if (pids.isEmpty()) { if (pids.isEmpty()) {
// Will never match // Will never match
@ -411,6 +414,50 @@ public class SearchBuilder implements ISearchBuilder {
return query; return query;
} }
private List<ResourcePersistentId> normalizeIdListForLastNInClause(List<String> lastnResourceIds) {
List<ResourcePersistentId> retVal = new ArrayList<>();
for (String lastnResourceId : lastnResourceIds) {
retVal.add(new ResourcePersistentId(Long.parseLong(lastnResourceId)));
}
/*
The following is a workaround to a known issue involving Hibernate. If queries are used with "in" clauses with large and varying
numbers of parameters, this can overwhelm Hibernate's QueryPlanCache and deplete heap space. See the following link for more info:
https://stackoverflow.com/questions/31557076/spring-hibernate-query-plan-cache-memory-usage.
Normalizing the number of parameters in the "in" clause stabilizes the size of the QueryPlanCache, so long as the number of
arguments never exceeds the maximum specified below.
*/
int listSize = retVal.size();
if(listSize > 1 && listSize < 10) {
padIdListWithPlaceholders(retVal, 10);
} else if (listSize > 10 && listSize < 100) {
padIdListWithPlaceholders(retVal, 100);
} else if (listSize > 100 && listSize < 200) {
padIdListWithPlaceholders(retVal, 200);
} else if (listSize > 200 && listSize < 500) {
padIdListWithPlaceholders(retVal, 500);
} else if (listSize > 500 && listSize < 1000) {
padIdListWithPlaceholders(retVal, 1000);
} else if (listSize > 1000 && listSize < 500) {
padIdListWithPlaceholders(retVal, 5000);
} else if (listSize > 5000 && listSize < 10000) {
padIdListWithPlaceholders(retVal, 10000);
} else if (listSize > 10000 && listSize < 20000) {
padIdListWithPlaceholders(retVal, 20000);
} else if (listSize > 20000 && listSize < 30000) {
padIdListWithPlaceholders(retVal, 30000);
}
return retVal;
}
private void padIdListWithPlaceholders(List<ResourcePersistentId> theIdList, int preferredListSize) {
while(theIdList.size() < preferredListSize) {
theIdList.add(new ResourcePersistentId(-1L));
}
}
/** /**
* @return Returns {@literal true} if any search parameter sorts were found, or false if * @return Returns {@literal true} if any search parameter sorts were found, or false if
* no sorts were found, or only non-search parameters ones (e.g. _id, _lastUpdated) * no sorts were found, or only non-search parameters ones (e.g. _id, _lastUpdated)

View File

@ -29,6 +29,8 @@ import ca.uhn.fhir.rest.api.CacheControlDirective;
import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.param.ReferenceAndListParam;
import ca.uhn.fhir.rest.param.TokenAndListParam;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.dstu3.model.Observation; import org.hl7.fhir.dstu3.model.Observation;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;

View File

@ -29,6 +29,8 @@ import ca.uhn.fhir.rest.api.CacheControlDirective;
import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.param.ReferenceAndListParam;
import ca.uhn.fhir.rest.param.TokenAndListParam;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r5.model.Observation; import org.hl7.fhir.r5.model.Observation;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;

View File

@ -3,19 +3,17 @@ package ca.uhn.fhir.jpa.provider.dstu3;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoObservation; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoObservation;
import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.model.api.annotation.Description; import ca.uhn.fhir.model.api.annotation.Description;
import ca.uhn.fhir.model.valueset.BundleTypeEnum; import ca.uhn.fhir.model.valueset.BundleTypeEnum;
import ca.uhn.fhir.rest.annotation.*; import ca.uhn.fhir.rest.annotation.*;
import ca.uhn.fhir.rest.api.SearchTotalModeEnum; import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.SortOrderEnum; import ca.uhn.fhir.rest.api.SortOrderEnum;
import ca.uhn.fhir.rest.api.SortSpec; import ca.uhn.fhir.rest.api.SortSpec;
import ca.uhn.fhir.rest.api.SummaryEnum;
import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.*; import ca.uhn.fhir.rest.param.*;
import org.hl7.fhir.dstu3.model.Observation; import org.hl7.fhir.dstu3.model.Observation;
import org.hl7.fhir.dstu3.model.UnsignedIntType;
import java.util.Set; import org.hl7.fhir.instance.model.api.IPrimitiveType;
/* /*
* #%L * #%L
@ -50,17 +48,9 @@ public class BaseJpaResourceProviderObservationDstu3 extends JpaResourceProvider
ca.uhn.fhir.rest.api.server.RequestDetails theRequestDetails, ca.uhn.fhir.rest.api.server.RequestDetails theRequestDetails,
@Description(shortDefinition="Search the contents of the resource's data using a filter") @Description(formalDefinition = "Results from this method are returned across multiple pages. This parameter controls the size of those pages.")
@OperationParam(name=ca.uhn.fhir.rest.api.Constants.PARAM_FILTER) @OperationParam(name = Constants.PARAM_COUNT)
StringAndListParam theFtFilter, UnsignedIntType theCount,
@Description(shortDefinition="Search the contents of the resource's data using a fulltext search")
@OperationParam(name=ca.uhn.fhir.rest.api.Constants.PARAM_CONTENT)
StringAndListParam theFtContent,
@Description(shortDefinition="Search the contents of the resource's narrative using a fulltext search")
@OperationParam(name=ca.uhn.fhir.rest.api.Constants.PARAM_TEXT)
StringAndListParam theFtText,
@Description(shortDefinition="The classification of the type of observation") @Description(shortDefinition="The classification of the type of observation")
@OperationParam(name="category") @OperationParam(name="category")
@ -70,10 +60,6 @@ public class BaseJpaResourceProviderObservationDstu3 extends JpaResourceProvider
@OperationParam(name="code") @OperationParam(name="code")
TokenAndListParam theCode, TokenAndListParam theCode,
@Description(shortDefinition="Obtained date/time. If the obtained element is a period, a date that falls in the period")
@OperationParam(name="date")
DateRangeParam theDate,
@Description(shortDefinition="The subject that the observation is about (if patient)") @Description(shortDefinition="The subject that the observation is about (if patient)")
@OperationParam(name="patient") @OperationParam(name="patient")
ReferenceAndListParam thePatient, ReferenceAndListParam thePatient,
@ -82,69 +68,31 @@ public class BaseJpaResourceProviderObservationDstu3 extends JpaResourceProvider
@OperationParam(name="subject" ) @OperationParam(name="subject" )
ReferenceAndListParam theSubject, ReferenceAndListParam theSubject,
@IncludeParam(reverse=true) @Description(shortDefinition="The maximum number of observations to return for each observation code")
Set<Include> theRevIncludes, @OperationParam(name = "max", typeName = "integer", min = 0, max = 1)
@Description(shortDefinition="Only return resources which were last updated as specified by the given range") IPrimitiveType<Integer> theMax,
@OperationParam(name="_lastUpdated")
DateRangeParam theLastUpdated,
@IncludeParam(allow= {
"Observation:based-on",
"Observation:derived-from",
"Observation:device",
"Observation:encounter",
"Observation:focus",
"Observation:has-member",
"Observation:part-of",
"Observation:patient",
"Observation:performer",
"Observation:specimen",
"Observation:subject",
"*"
})
Set<Include> theIncludes,
@Sort @Sort
SortSpec theSort, SortSpec theSort
@ca.uhn.fhir.rest.annotation.Count
Integer theCount,
SummaryEnum theSummaryMode,
SearchTotalModeEnum theSearchTotalMode
) { ) {
startRequest(theServletRequest); startRequest(theServletRequest);
try { try {
SearchParameterMap paramMap = new SearchParameterMap(); SearchParameterMap paramMap = new SearchParameterMap();
paramMap.add(ca.uhn.fhir.rest.api.Constants.PARAM_FILTER, theFtFilter); paramMap.add(Observation.SP_CATEGORY, theCategory);
paramMap.add(ca.uhn.fhir.rest.api.Constants.PARAM_CONTENT, theFtContent); paramMap.add(Observation.SP_CODE, theCode);
paramMap.add(ca.uhn.fhir.rest.api.Constants.PARAM_TEXT, theFtText); paramMap.add(Observation.SP_PATIENT, thePatient);
paramMap.add("category", theCategory); paramMap.add(Observation.SP_SUBJECT, theSubject);
paramMap.add("code", theCode); paramMap.setLastNMax(theMax.getValue());
paramMap.add("date", theDate); if (theCount != null) {
paramMap.add("patient", thePatient); paramMap.setCount(theCount.getValue());
paramMap.add("subject", theSubject);
paramMap.setRevIncludes(theRevIncludes);
paramMap.setLastUpdated(theLastUpdated);
paramMap.setIncludes(theIncludes);
paramMap.setLastN(true);
if (theSort == null) {
SortSpec effectiveDtm = new SortSpec("date").setOrder(SortOrderEnum.DESC);
SortSpec observationCode = new SortSpec("code").setOrder(SortOrderEnum.ASC).setChain(effectiveDtm);
if (thePatient != null && theSubject == null) {
theSort = new SortSpec("patient").setChain(observationCode);
} else {
theSort = new SortSpec("subject").setChain(observationCode);
} }
}
paramMap.setSort(theSort);
paramMap.setCount(theCount);
paramMap.setSummaryMode(theSummaryMode);
paramMap.setSearchTotalMode(theSearchTotalMode);
return ((IFhirResourceDaoObservation<org.hl7.fhir.dstu3.model.Observation>) getDao()).observationsLastN(paramMap, theRequestDetails, theServletResponse); if (theSort != null) {
paramMap.setSort(theSort);
}
return ((IFhirResourceDaoObservation<Observation>) getDao()).observationsLastN(paramMap, theRequestDetails, theServletResponse);
} finally { } finally {
endRequest(theServletRequest); endRequest(theServletRequest);
} }

View File

@ -3,20 +3,17 @@ package ca.uhn.fhir.jpa.provider.r4;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoObservation; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoObservation;
import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.model.api.annotation.Description; import ca.uhn.fhir.model.api.annotation.Description;
import ca.uhn.fhir.model.valueset.BundleTypeEnum; import ca.uhn.fhir.model.valueset.BundleTypeEnum;
import ca.uhn.fhir.rest.annotation.*; import ca.uhn.fhir.rest.annotation.*;
import ca.uhn.fhir.rest.api.SearchTotalModeEnum; import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.SortOrderEnum; import ca.uhn.fhir.rest.api.SortOrderEnum;
import ca.uhn.fhir.rest.api.SortSpec; import ca.uhn.fhir.rest.api.SortSpec;
import ca.uhn.fhir.rest.api.SummaryEnum;
import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.*; import ca.uhn.fhir.rest.param.*;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.r4.model.*; import org.hl7.fhir.r4.model.*;
import java.util.Set;
/* /*
* #%L * #%L
* HAPI FHIR JPA Server * HAPI FHIR JPA Server
@ -50,17 +47,9 @@ public class BaseJpaResourceProviderObservationR4 extends JpaResourceProviderR4<
ca.uhn.fhir.rest.api.server.RequestDetails theRequestDetails, ca.uhn.fhir.rest.api.server.RequestDetails theRequestDetails,
@Description(shortDefinition="Search the contents of the resource's data using a filter") @Description(formalDefinition = "Results from this method are returned across multiple pages. This parameter controls the size of those pages.")
@OperationParam(name=ca.uhn.fhir.rest.api.Constants.PARAM_FILTER) @OperationParam(name = Constants.PARAM_COUNT)
StringAndListParam theFtFilter, UnsignedIntType theCount,
@Description(shortDefinition="Search the contents of the resource's data using a fulltext search")
@OperationParam(name=ca.uhn.fhir.rest.api.Constants.PARAM_CONTENT)
StringAndListParam theFtContent,
@Description(shortDefinition="Search the contents of the resource's narrative using a fulltext search")
@OperationParam(name=ca.uhn.fhir.rest.api.Constants.PARAM_TEXT)
StringAndListParam theFtText,
@Description(shortDefinition="The classification of the type of observation") @Description(shortDefinition="The classification of the type of observation")
@OperationParam(name="category") @OperationParam(name="category")
@ -70,10 +59,6 @@ public class BaseJpaResourceProviderObservationR4 extends JpaResourceProviderR4<
@OperationParam(name="code") @OperationParam(name="code")
TokenAndListParam theCode, TokenAndListParam theCode,
@Description(shortDefinition="Obtained date/time. If the obtained element is a period, a date that falls in the period")
@OperationParam(name="date")
DateRangeParam theDate,
@Description(shortDefinition="The subject that the observation is about (if patient)") @Description(shortDefinition="The subject that the observation is about (if patient)")
@OperationParam(name="patient") @OperationParam(name="patient")
ReferenceAndListParam thePatient, ReferenceAndListParam thePatient,
@ -82,67 +67,29 @@ public class BaseJpaResourceProviderObservationR4 extends JpaResourceProviderR4<
@OperationParam(name="subject" ) @OperationParam(name="subject" )
ReferenceAndListParam theSubject, ReferenceAndListParam theSubject,
@IncludeParam(reverse=true) @Description(shortDefinition="The maximum number of observations to return for each observation code")
Set<Include> theRevIncludes, @OperationParam(name = "max", typeName = "integer", min = 0, max = 1)
@Description(shortDefinition="Only return resources which were last updated as specified by the given range") IPrimitiveType<Integer> theMax,
@OperationParam(name="_lastUpdated")
DateRangeParam theLastUpdated,
@IncludeParam(allow= {
"Observation:based-on",
"Observation:derived-from",
"Observation:device",
"Observation:encounter",
"Observation:focus",
"Observation:has-member",
"Observation:part-of",
"Observation:patient",
"Observation:performer",
"Observation:specimen",
"Observation:subject",
"*"
})
Set<Include> theIncludes,
@Sort @Sort
SortSpec theSort, SortSpec theSort
@ca.uhn.fhir.rest.annotation.Count
Integer theCount,
SummaryEnum theSummaryMode,
SearchTotalModeEnum theSearchTotalMode
) { ) {
startRequest(theServletRequest); startRequest(theServletRequest);
try { try {
SearchParameterMap paramMap = new SearchParameterMap(); SearchParameterMap paramMap = new SearchParameterMap();
paramMap.add(ca.uhn.fhir.rest.api.Constants.PARAM_FILTER, theFtFilter);
paramMap.add(ca.uhn.fhir.rest.api.Constants.PARAM_CONTENT, theFtContent);
paramMap.add(ca.uhn.fhir.rest.api.Constants.PARAM_TEXT, theFtText);
paramMap.add("category", theCategory); paramMap.add("category", theCategory);
paramMap.add("code", theCode); paramMap.add("code", theCode);
paramMap.add("date", theDate);
paramMap.add("patient", thePatient); paramMap.add("patient", thePatient);
paramMap.add("subject", theSubject); paramMap.add("subject", theSubject);
paramMap.setRevIncludes(theRevIncludes); paramMap.setLastNMax(theMax.getValue());
paramMap.setLastUpdated(theLastUpdated); if (theCount != null) {
paramMap.setIncludes(theIncludes); paramMap.setCount(theCount.getValue());
/* paramMap.setLastN(true);
if (theSort == null) {
SortSpec effectiveDtm = new SortSpec("date").setOrder(SortOrderEnum.DESC);
SortSpec observationCode = new SortSpec("code").setOrder(SortOrderEnum.ASC).setChain(effectiveDtm);
if (thePatient != null && theSubject == null) {
theSort = new SortSpec("patient").setChain(observationCode);
} else {
theSort = new SortSpec("subject").setChain(observationCode);
} }
} */
if (theSort != null) {
paramMap.setSort(theSort); paramMap.setSort(theSort);
paramMap.setCount(theCount); }
paramMap.setSummaryMode(theSummaryMode);
paramMap.setSearchTotalMode(theSearchTotalMode);
return ((IFhirResourceDaoObservation<Observation>) getDao()).observationsLastN(paramMap, theRequestDetails, theServletResponse); return ((IFhirResourceDaoObservation<Observation>) getDao()).observationsLastN(paramMap, theRequestDetails, theServletResponse);
} finally { } finally {

View File

@ -3,20 +3,18 @@ package ca.uhn.fhir.jpa.provider.r5;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoObservation; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoObservation;
import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.model.api.annotation.Description; import ca.uhn.fhir.model.api.annotation.Description;
import ca.uhn.fhir.model.valueset.BundleTypeEnum; import ca.uhn.fhir.model.valueset.BundleTypeEnum;
import ca.uhn.fhir.rest.annotation.*; import ca.uhn.fhir.rest.annotation.*;
import ca.uhn.fhir.rest.api.SearchTotalModeEnum; import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.SortOrderEnum; import ca.uhn.fhir.rest.api.SortOrderEnum;
import ca.uhn.fhir.rest.api.SortSpec; import ca.uhn.fhir.rest.api.SortSpec;
import ca.uhn.fhir.rest.api.SummaryEnum;
import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.*; import ca.uhn.fhir.rest.param.*;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.r5.model.UnsignedIntType;
import org.hl7.fhir.r5.model.Observation; import org.hl7.fhir.r5.model.Observation;
import java.util.Set;
/* /*
* #%L * #%L
* HAPI FHIR JPA Server * HAPI FHIR JPA Server
@ -50,17 +48,9 @@ public class BaseJpaResourceProviderObservationR5 extends JpaResourceProviderR5<
ca.uhn.fhir.rest.api.server.RequestDetails theRequestDetails, ca.uhn.fhir.rest.api.server.RequestDetails theRequestDetails,
@Description(shortDefinition="Search the contents of the resource's data using a filter") @Description(formalDefinition = "Results from this method are returned across multiple pages. This parameter controls the size of those pages.")
@OperationParam(name=ca.uhn.fhir.rest.api.Constants.PARAM_FILTER) @OperationParam(name = Constants.PARAM_COUNT)
StringAndListParam theFtFilter, UnsignedIntType theCount,
@Description(shortDefinition="Search the contents of the resource's data using a fulltext search")
@OperationParam(name=ca.uhn.fhir.rest.api.Constants.PARAM_CONTENT)
StringAndListParam theFtContent,
@Description(shortDefinition="Search the contents of the resource's narrative using a fulltext search")
@OperationParam(name=ca.uhn.fhir.rest.api.Constants.PARAM_TEXT)
StringAndListParam theFtText,
@Description(shortDefinition="The classification of the type of observation") @Description(shortDefinition="The classification of the type of observation")
@OperationParam(name="category") @OperationParam(name="category")
@ -70,10 +60,6 @@ public class BaseJpaResourceProviderObservationR5 extends JpaResourceProviderR5<
@OperationParam(name="code") @OperationParam(name="code")
TokenAndListParam theCode, TokenAndListParam theCode,
@Description(shortDefinition="Obtained date/time. If the obtained element is a period, a date that falls in the period")
@OperationParam(name="date")
DateRangeParam theDate,
@Description(shortDefinition="The subject that the observation is about (if patient)") @Description(shortDefinition="The subject that the observation is about (if patient)")
@OperationParam(name="patient") @OperationParam(name="patient")
ReferenceAndListParam thePatient, ReferenceAndListParam thePatient,
@ -82,69 +68,31 @@ public class BaseJpaResourceProviderObservationR5 extends JpaResourceProviderR5<
@OperationParam(name="subject" ) @OperationParam(name="subject" )
ReferenceAndListParam theSubject, ReferenceAndListParam theSubject,
@IncludeParam(reverse=true) @Description(shortDefinition="The maximum number of observations to return for each observation code")
Set<Include> theRevIncludes, @OperationParam(name = "max", typeName = "integer", min = 0, max = 1)
@Description(shortDefinition="Only return resources which were last updated as specified by the given range") IPrimitiveType<Integer> theMax,
@OperationParam(name="_lastUpdated")
DateRangeParam theLastUpdated,
@IncludeParam(allow= {
"Observation:based-on",
"Observation:derived-from",
"Observation:device",
"Observation:encounter",
"Observation:focus",
"Observation:has-member",
"Observation:part-of",
"Observation:patient",
"Observation:performer",
"Observation:specimen",
"Observation:subject",
"*"
})
Set<Include> theIncludes,
@Sort @Sort
SortSpec theSort, SortSpec theSort
@ca.uhn.fhir.rest.annotation.Count
Integer theCount,
SummaryEnum theSummaryMode,
SearchTotalModeEnum theSearchTotalMode
) { ) {
startRequest(theServletRequest); startRequest(theServletRequest);
try { try {
SearchParameterMap paramMap = new SearchParameterMap(); SearchParameterMap paramMap = new SearchParameterMap();
paramMap.add(ca.uhn.fhir.rest.api.Constants.PARAM_FILTER, theFtFilter);
paramMap.add(ca.uhn.fhir.rest.api.Constants.PARAM_CONTENT, theFtContent);
paramMap.add(ca.uhn.fhir.rest.api.Constants.PARAM_TEXT, theFtText);
paramMap.add("category", theCategory); paramMap.add("category", theCategory);
paramMap.add("code", theCode); paramMap.add("code", theCode);
paramMap.add("date", theDate);
paramMap.add("patient", thePatient); paramMap.add("patient", thePatient);
paramMap.add("subject", theSubject); paramMap.add("subject", theSubject);
paramMap.setRevIncludes(theRevIncludes); paramMap.setLastNMax(theMax.getValue());
paramMap.setLastUpdated(theLastUpdated); if (theCount != null) {
paramMap.setIncludes(theIncludes); paramMap.setCount(theCount.getValue());
paramMap.setLastN(true);
if (theSort == null) {
SortSpec effectiveDtm = new SortSpec("date").setOrder(SortOrderEnum.DESC);
SortSpec observationCode = new SortSpec("code").setOrder(SortOrderEnum.ASC).setChain(effectiveDtm);
if (thePatient != null && theSubject == null) {
theSort = new SortSpec("patient").setChain(observationCode);
} else {
theSort = new SortSpec("subject").setChain(observationCode);
} }
}
paramMap.setSort(theSort);
paramMap.setCount(theCount);
paramMap.setSummaryMode(theSummaryMode);
paramMap.setSearchTotalMode(theSearchTotalMode);
return ((IFhirResourceDaoObservation<org.hl7.fhir.r5.model.Observation>) getDao()).observationsLastN(paramMap, theRequestDetails, theServletResponse); if (theSort != null) {
paramMap.setSort(theSort);
}
return ((IFhirResourceDaoObservation<Observation>) getDao()).observationsLastN(paramMap, theRequestDetails, theServletResponse);
} finally { } finally {
endRequest(theServletRequest); endRequest(theServletRequest);
} }

View File

@ -2,6 +2,7 @@ package ca.uhn.fhir.jpa.search.lastn;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.model.dstu2.resource.Observation;
import ca.uhn.fhir.rest.param.ReferenceParam; import ca.uhn.fhir.rest.param.ReferenceParam;
import ca.uhn.fhir.rest.param.TokenParam; import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.jpa.search.lastn.json.CodeJson; import ca.uhn.fhir.jpa.search.lastn.json.CodeJson;
@ -42,6 +43,7 @@ import org.shadehapi.elasticsearch.search.sort.SortOrder;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.function.Function;
import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isBlank;
@ -200,21 +202,64 @@ public class ElasticsearchSvcImpl implements IElasticsearchSvc {
@Override @Override
public List<String> executeLastN(SearchParameterMap theSearchParameterMap, Integer theMaxObservationsPerCode) { public List<String> executeLastN(SearchParameterMap theSearchParameterMap, Integer theMaxObservationsPerCode) {
String[] topHitsInclude = {OBSERVATION_IDENTIFIER_FIELD_NAME}; String[] topHitsInclude = {OBSERVATION_IDENTIFIER_FIELD_NAME};
SearchRequest myLastNRequest = buildObservationsSearchRequest(theSearchParameterMap, createCompositeAggregationBuilder(theMaxObservationsPerCode, topHitsInclude));
try { try {
SearchResponse lastnResponse = executeSearchRequest(myLastNRequest); List<SearchResponse> responses = buildAndExecuteSearch(theSearchParameterMap, theMaxObservationsPerCode, topHitsInclude);
return buildObservationIdList(lastnResponse); List<String> observationIds = new ArrayList<>();
for (SearchResponse response : responses) {
// observationIds.addAll(buildObservationIdList(response));
observationIds.addAll(buildObservationList(response, t -> t.getIdentifier(), theSearchParameterMap));
}
return observationIds;
} catch (IOException theE) { } catch (IOException theE) {
throw new InvalidRequestException("Unable to execute LastN request", theE); throw new InvalidRequestException("Unable to execute LastN request", theE);
} }
} }
@VisibleForTesting private List<SearchResponse> buildAndExecuteSearch(SearchParameterMap theSearchParameterMap, Integer theMaxObservationsPerCode, String[] topHitsInclude) {
List<ObservationJson> executeLastNWithAllFields(SearchParameterMap theSearchParameterMap, Integer theMaxObservationsPerCode) { List<SearchResponse> responses = new ArrayList<>();
SearchRequest myLastNRequest = buildObservationsSearchRequest(theSearchParameterMap, createCompositeAggregationBuilder(theMaxObservationsPerCode, null)); if (theSearchParameterMap.containsKey(IndexConstants.PATIENT_SEARCH_PARAM) || theSearchParameterMap.containsKey(IndexConstants.SUBJECT_SEARCH_PARAM)) {
ArrayList<String> subjectReferenceCriteria = new ArrayList<>();
List<List<IQueryParameterType>> patientParams = new ArrayList<>();
if (theSearchParameterMap.get(IndexConstants.PATIENT_SEARCH_PARAM) != null) {
patientParams.addAll(theSearchParameterMap.get(IndexConstants.PATIENT_SEARCH_PARAM));
}
if (theSearchParameterMap.get(IndexConstants.SUBJECT_SEARCH_PARAM) != null) {
patientParams.addAll(theSearchParameterMap.get(IndexConstants.SUBJECT_SEARCH_PARAM));
}
for (List<? extends IQueryParameterType> nextSubjectList : patientParams) {
subjectReferenceCriteria.addAll(getReferenceValues(nextSubjectList));
}
for (String subject : subjectReferenceCriteria) {
SearchRequest myLastNRequest = buildObservationsSearchRequest(subject, theSearchParameterMap, createCompositeAggregationBuilder(theMaxObservationsPerCode, topHitsInclude));
try { try {
SearchResponse lastnResponse = executeSearchRequest(myLastNRequest); SearchResponse lastnResponse = executeSearchRequest(myLastNRequest);
return buildObservationDocumentList(lastnResponse); responses.add(lastnResponse);
} catch (IOException theE) {
throw new InvalidRequestException("Unable to execute LastN request", theE);
}
}
} else {
SearchRequest myLastNRequest = buildObservationsSearchRequest(theSearchParameterMap, createObservationCodeAggregationBuilder(theMaxObservationsPerCode, topHitsInclude));
try {
SearchResponse lastnResponse = executeSearchRequest(myLastNRequest);
responses.add(lastnResponse);
} catch (IOException theE) {
throw new InvalidRequestException("Unable to execute LastN request", theE);
}
}
return responses;
}
@VisibleForTesting
List<ObservationJson> executeLastNWithAllFields(SearchParameterMap theSearchParameterMap, Integer theMaxObservationsPerCode) {
try {
List<SearchResponse> responses = buildAndExecuteSearch(theSearchParameterMap, theMaxObservationsPerCode, null);
List<ObservationJson> observationDocuments = new ArrayList<>();
for (SearchResponse response : responses) {
observationDocuments.addAll(buildObservationList(response, t -> t, theSearchParameterMap));
}
return observationDocuments;
} catch (IOException theE) { } catch (IOException theE) {
throw new InvalidRequestException("Unable to execute LastN request", theE); throw new InvalidRequestException("Unable to execute LastN request", theE);
} }
@ -227,8 +272,7 @@ public class ElasticsearchSvcImpl implements IElasticsearchSvc {
return buildCodeResult(codeSearchResponse); return buildCodeResult(codeSearchResponse);
} }
@VisibleForTesting private SearchRequest buildObservationCodesSearchRequest(int theMaxResultSetSize) {
SearchRequest buildObservationCodesSearchRequest(int theMaxResultSetSize) {
SearchRequest searchRequest = new SearchRequest(IndexConstants.CODE_INDEX); SearchRequest searchRequest = new SearchRequest(IndexConstants.CODE_INDEX);
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
// Query // Query
@ -239,27 +283,30 @@ public class ElasticsearchSvcImpl implements IElasticsearchSvc {
} }
private CompositeAggregationBuilder createCompositeAggregationBuilder(int theMaxNumberObservationsPerCode, String[] theTopHitsInclude) { private CompositeAggregationBuilder createCompositeAggregationBuilder(int theMaxNumberObservationsPerCode, String[] theTopHitsInclude) {
CompositeValuesSourceBuilder<?> subjectValuesBuilder = new TermsValuesSourceBuilder("subject").field("subject");
List<CompositeValuesSourceBuilder<?>> compositeAggSubjectSources = new ArrayList();
compositeAggSubjectSources.add(subjectValuesBuilder);
CompositeAggregationBuilder compositeAggregationSubjectBuilder = new CompositeAggregationBuilder(GROUP_BY_SUBJECT, compositeAggSubjectSources);
compositeAggregationSubjectBuilder.subAggregation(createObservationCodeAggregationBuilder(theMaxNumberObservationsPerCode, theTopHitsInclude));
compositeAggregationSubjectBuilder.size(10000);
return compositeAggregationSubjectBuilder;
}
private TermsAggregationBuilder createObservationCodeAggregationBuilder(int theMaxNumberObservationsPerCode, String[] theTopHitsInclude) {
TermsAggregationBuilder observationCodeAggregationBuilder = new TermsAggregationBuilder(GROUP_BY_CODE, ValueType.STRING).field("codeconceptid"); TermsAggregationBuilder observationCodeAggregationBuilder = new TermsAggregationBuilder(GROUP_BY_CODE, ValueType.STRING).field("codeconceptid");
// Top Hits Aggregation // Top Hits Aggregation
observationCodeAggregationBuilder.subAggregation(AggregationBuilders.topHits("most_recent_effective") observationCodeAggregationBuilder.subAggregation(AggregationBuilders.topHits("most_recent_effective")
.sort("effectivedtm", SortOrder.DESC) .sort("effectivedtm", SortOrder.DESC)
.fetchSource(theTopHitsInclude, null).size(theMaxNumberObservationsPerCode)); .fetchSource(theTopHitsInclude, null).size(theMaxNumberObservationsPerCode));
observationCodeAggregationBuilder.size(10000); observationCodeAggregationBuilder.size(10000);
CompositeValuesSourceBuilder<?> subjectValuesBuilder = new TermsValuesSourceBuilder("subject").field("subject"); return observationCodeAggregationBuilder;
List<CompositeValuesSourceBuilder<?>> compositeAggSubjectSources = new ArrayList();
compositeAggSubjectSources.add(subjectValuesBuilder);
CompositeAggregationBuilder compositeAggregationSubjectBuilder = new CompositeAggregationBuilder(GROUP_BY_SUBJECT, compositeAggSubjectSources);
compositeAggregationSubjectBuilder.subAggregation(observationCodeAggregationBuilder);
compositeAggregationSubjectBuilder.size(10000);
return compositeAggregationSubjectBuilder;
} }
private SearchResponse executeSearchRequest(SearchRequest searchRequest) throws IOException { public SearchResponse executeSearchRequest(SearchRequest searchRequest) throws IOException {
return myRestHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); return myRestHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
} }
private List<String> buildObservationIdList(SearchResponse theSearchResponse) throws IOException { private List<String> buildObservationIdList(SearchResponse theSearchResponse) throws IOException {
List<String> theObservationList = new ArrayList<>(); List<String> theObservationList = new ArrayList<>();
for (ParsedComposite.ParsedBucket subjectBucket : getSubjectBuckets(theSearchResponse)) { for (ParsedComposite.ParsedBucket subjectBucket : getSubjectBuckets(theSearchResponse)) {
@ -288,12 +335,43 @@ public class ElasticsearchSvcImpl implements IElasticsearchSvc {
return theObservationList; return theObservationList;
} }
private <T> List<T> buildObservationList(SearchResponse theSearchResponse, Function<ObservationJson,T> setValue, SearchParameterMap theSearchParameterMap) throws IOException {
List<T> theObservationList = new ArrayList<>();
if (theSearchParameterMap.containsKey(IndexConstants.PATIENT_SEARCH_PARAM) || theSearchParameterMap.containsKey(IndexConstants.SUBJECT_SEARCH_PARAM)) {
for (ParsedComposite.ParsedBucket subjectBucket : getSubjectBuckets(theSearchResponse)) {
for (Terms.Bucket observationCodeBucket : getObservationCodeBuckets(subjectBucket)) {
for (SearchHit lastNMatch : getLastNMatches(observationCodeBucket)) {
String indexedObservation = lastNMatch.getSourceAsString();
ObservationJson observationJson = objectMapper.readValue(indexedObservation, ObservationJson.class);
theObservationList.add(setValue.apply(observationJson));
}
}
}
} else {
for (Terms.Bucket observationCodeBucket : getObservationCodeBuckets(theSearchResponse)) {
for (SearchHit lastNMatch : getLastNMatches(observationCodeBucket)) {
String indexedObservation = lastNMatch.getSourceAsString();
ObservationJson observationJson = objectMapper.readValue(indexedObservation, ObservationJson.class);
theObservationList.add(setValue.apply(observationJson));
}
}
}
return theObservationList;
}
private List<ParsedComposite.ParsedBucket> getSubjectBuckets(SearchResponse theSearchResponse) { private List<ParsedComposite.ParsedBucket> getSubjectBuckets(SearchResponse theSearchResponse) {
Aggregations responseAggregations = theSearchResponse.getAggregations(); Aggregations responseAggregations = theSearchResponse.getAggregations();
ParsedComposite aggregatedSubjects = responseAggregations.get(GROUP_BY_SUBJECT); ParsedComposite aggregatedSubjects = responseAggregations.get(GROUP_BY_SUBJECT);
return aggregatedSubjects.getBuckets(); return aggregatedSubjects.getBuckets();
} }
private List<? extends Terms.Bucket> getObservationCodeBuckets(SearchResponse theSearchResponse) {
Aggregations responseAggregations = theSearchResponse.getAggregations();
ParsedTerms aggregatedObservationCodes = responseAggregations.get(GROUP_BY_CODE);
return aggregatedObservationCodes.getBuckets();
}
private List<? extends Terms.Bucket> getObservationCodeBuckets(ParsedComposite.ParsedBucket theSubjectBucket) { private List<? extends Terms.Bucket> getObservationCodeBuckets(ParsedComposite.ParsedBucket theSubjectBucket) {
Aggregations observationCodeAggregations = theSubjectBucket.getAggregations(); Aggregations observationCodeAggregations = theSubjectBucket.getAggregations();
ParsedTerms aggregatedObservationCodes = observationCodeAggregations.get(GROUP_BY_CODE); ParsedTerms aggregatedObservationCodes = observationCodeAggregations.get(GROUP_BY_CODE);
@ -338,6 +416,24 @@ public class ElasticsearchSvcImpl implements IElasticsearchSvc {
return searchRequest; return searchRequest;
} }
private SearchRequest buildObservationsSearchRequest(String theSubjectParam, SearchParameterMap theSearchParameterMap, AggregationBuilder theAggregationBuilder) {
SearchRequest searchRequest = new SearchRequest(IndexConstants.OBSERVATION_INDEX);
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
// Query
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
boolQueryBuilder.must(QueryBuilders.termQuery("subject", theSubjectParam));
addCategoriesCriteria(boolQueryBuilder, theSearchParameterMap);
addObservationCodeCriteria(boolQueryBuilder, theSearchParameterMap);
searchSourceBuilder.query(boolQueryBuilder);
searchSourceBuilder.size(0);
// Aggregation by order codes
searchSourceBuilder.aggregation(theAggregationBuilder);
searchRequest.source(searchSourceBuilder);
return searchRequest;
}
private Boolean searchParamsHaveLastNCriteria(SearchParameterMap theSearchParameterMap) { private Boolean searchParamsHaveLastNCriteria(SearchParameterMap theSearchParameterMap) {
return theSearchParameterMap != null && return theSearchParameterMap != null &&
(theSearchParameterMap.containsKey(IndexConstants.PATIENT_SEARCH_PARAM) || theSearchParameterMap.containsKey(IndexConstants.SUBJECT_SEARCH_PARAM) || (theSearchParameterMap.containsKey(IndexConstants.PATIENT_SEARCH_PARAM) || theSearchParameterMap.containsKey(IndexConstants.SUBJECT_SEARCH_PARAM) ||
@ -519,11 +615,12 @@ public class ElasticsearchSvcImpl implements IElasticsearchSvc {
myRestHighLevelClient.deleteByQuery(deleteByQueryRequest, RequestOptions.DEFAULT); myRestHighLevelClient.deleteByQuery(deleteByQueryRequest, RequestOptions.DEFAULT);
} }
public void deleteObservationIndex(String theObservationIdentifier) throws IOException { /* public void deleteObservationIndex(String theObservationIdentifier) throws IOException {
DeleteByQueryRequest deleteByQueryRequest = new DeleteByQueryRequest(IndexConstants.OBSERVATION_DOCUMENT_TYPE); DeleteByQueryRequest deleteByQueryRequest = new DeleteByQueryRequest(IndexConstants.OBSERVATION_DOCUMENT_TYPE);
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
boolQueryBuilder.must(QueryBuilders.termsQuery(OBSERVATION_IDENTIFIER_FIELD_NAME, theObservationIdentifier)); boolQueryBuilder.must(QueryBuilders.termsQuery(OBSERVATION_IDENTIFIER_FIELD_NAME, theObservationIdentifier));
deleteByQueryRequest.setQuery(boolQueryBuilder); deleteByQueryRequest.setQuery(boolQueryBuilder);
myRestHighLevelClient.deleteByQuery(deleteByQueryRequest, RequestOptions.DEFAULT); myRestHighLevelClient.deleteByQuery(deleteByQueryRequest, RequestOptions.DEFAULT);
} }
*/
} }

View File

@ -5,6 +5,7 @@ import ca.uhn.fhir.jpa.api.dao.*;
import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.config.TestR4ConfigWithElasticsearchClient; import ca.uhn.fhir.jpa.config.TestR4ConfigWithElasticsearchClient;
import ca.uhn.fhir.jpa.dao.BaseJpaTest; import ca.uhn.fhir.jpa.dao.BaseJpaTest;
import ca.uhn.fhir.jpa.rp.r4.ObservationResourceProvider;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.rest.param.*; import ca.uhn.fhir.rest.param.*;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
@ -57,6 +58,8 @@ public class FhirResourceDaoR4SearchLastNIT extends BaseJpaTest {
return myPlatformTransactionManager; return myPlatformTransactionManager;
} }
ObservationResourceProvider observationRp = new ObservationResourceProvider();
private final String observationCd0 = "code0"; private final String observationCd0 = "code0";
private final String observationCd1 = "code1"; private final String observationCd1 = "code1";
private final String observationCd2 = "code2"; private final String observationCd2 = "code2";
@ -101,6 +104,8 @@ public class FhirResourceDaoR4SearchLastNIT extends BaseJpaTest {
dataLoaded = true; dataLoaded = true;
} }
observationRp.setDao(myObservationDao);
} }
private void createObservationsForPatient(IIdType thePatientId) { private void createObservationsForPatient(IIdType thePatientId) {
@ -138,9 +143,13 @@ public class FhirResourceDaoR4SearchLastNIT extends BaseJpaTest {
} }
@Test @Test
public void testLastNNoParams() { public void testLastNAllPatients() {
SearchParameterMap params = new SearchParameterMap(); SearchParameterMap params = new SearchParameterMap();
ReferenceParam subjectParam1 = new ReferenceParam("Patient", "", patient0Id.getValue());
ReferenceParam subjectParam2 = new ReferenceParam("Patient", "", patient1Id.getValue());
ReferenceParam subjectParam3 = new ReferenceParam("Patient", "", patient2Id.getValue());
params.add(Observation.SP_SUBJECT, buildReferenceAndListParam(subjectParam1, subjectParam2, subjectParam3));
List<String> sortedPatients = new ArrayList<>(); List<String> sortedPatients = new ArrayList<>();
sortedPatients.add(patient0Id.getValue()); sortedPatients.add(patient0Id.getValue());
@ -155,14 +164,39 @@ public class FhirResourceDaoR4SearchLastNIT extends BaseJpaTest {
executeTestCase(params, sortedPatients, sortedObservationCodes, null,90); executeTestCase(params, sortedPatients, sortedObservationCodes, null,90);
} }
@Test
public void testLastNNoPatients() {
SearchParameterMap params = new SearchParameterMap();
params.setLastNMax(1);
List<String> sortedPatients = new ArrayList<>();
List<String> sortedObservationCodes = new ArrayList<>();
sortedObservationCodes.add(observationCd0);
sortedObservationCodes.add(observationCd1);
sortedObservationCodes.add(observationCd2);
// executeTestCase(params, sortedPatients, sortedObservationCodes, null,3);
params.setLastN(true);
Map<String, String[]> requestParameters = new HashMap<>();
when(mySrd.getParameters()).thenReturn(requestParameters);
List<String> actual = toUnqualifiedVersionlessIdValues(myObservationDao.observationsLastN(params, mockSrd(),null));
assertEquals(3, actual.size());
}
private void executeTestCase(SearchParameterMap params, List<String> sortedPatients, List<String> sortedObservationCodes, List<String> theCategories, int expectedObservationCount) { private void executeTestCase(SearchParameterMap params, List<String> sortedPatients, List<String> sortedObservationCodes, List<String> theCategories, int expectedObservationCount) {
List<String> actual; List<String> actual;
params.setLastN(true); params.setLastN(true);
Map<String, String[]> requestParameters = new HashMap<>(); Map<String, String[]> requestParameters = new HashMap<>();
String[] maxParam = new String[1]; // String[] maxParam = new String[1];
maxParam[0] = "100"; // maxParam[0] = "100";
requestParameters.put("max", maxParam); // requestParameters.put("max", maxParam);
params.setLastNMax(100);
when(mySrd.getParameters()).thenReturn(requestParameters); when(mySrd.getParameters()).thenReturn(requestParameters);
actual = toUnqualifiedVersionlessIdValues(myObservationDao.observationsLastN(params, mockSrd(),null)); actual = toUnqualifiedVersionlessIdValues(myObservationDao.observationsLastN(params, mockSrd(),null));
@ -297,6 +331,11 @@ public class FhirResourceDaoR4SearchLastNIT extends BaseJpaTest {
// One category parameter. // One category parameter.
SearchParameterMap params = new SearchParameterMap(); SearchParameterMap params = new SearchParameterMap();
ReferenceParam subjectParam1 = new ReferenceParam("Patient", "", patient0Id.getValue());
ReferenceParam subjectParam2 = new ReferenceParam("Patient", "", patient1Id.getValue());
ReferenceParam subjectParam3 = new ReferenceParam("Patient", "", patient2Id.getValue());
params.add(Observation.SP_SUBJECT, buildReferenceAndListParam(subjectParam1, subjectParam2, subjectParam3));
TokenParam categoryParam = new TokenParam(categorySystem, categoryCd0); TokenParam categoryParam = new TokenParam(categorySystem, categoryCd0);
params.add(Observation.SP_CATEGORY, buildTokenAndListParam(categoryParam)); params.add(Observation.SP_CATEGORY, buildTokenAndListParam(categoryParam));
List<String> myCategories = new ArrayList<>(); List<String> myCategories = new ArrayList<>();
@ -315,6 +354,7 @@ public class FhirResourceDaoR4SearchLastNIT extends BaseJpaTest {
// Another category parameter. // Another category parameter.
params = new SearchParameterMap(); params = new SearchParameterMap();
params.add(Observation.SP_SUBJECT, buildReferenceAndListParam(subjectParam1, subjectParam2, subjectParam3));
categoryParam = new TokenParam(categorySystem, categoryCd2); categoryParam = new TokenParam(categorySystem, categoryCd2);
params.add(Observation.SP_CATEGORY, buildTokenAndListParam(categoryParam)); params.add(Observation.SP_CATEGORY, buildTokenAndListParam(categoryParam));
myCategories = new ArrayList<>(); myCategories = new ArrayList<>();
@ -333,6 +373,11 @@ public class FhirResourceDaoR4SearchLastNIT extends BaseJpaTest {
// Two category parameters. // Two category parameters.
SearchParameterMap params = new SearchParameterMap(); SearchParameterMap params = new SearchParameterMap();
ReferenceParam subjectParam1 = new ReferenceParam("Patient", "", patient0Id.getValue());
ReferenceParam subjectParam2 = new ReferenceParam("Patient", "", patient1Id.getValue());
ReferenceParam subjectParam3 = new ReferenceParam("Patient", "", patient2Id.getValue());
params.add(Observation.SP_SUBJECT, buildReferenceAndListParam(subjectParam1, subjectParam2, subjectParam3));
TokenParam categoryParam1 = new TokenParam(categorySystem, categoryCd0); TokenParam categoryParam1 = new TokenParam(categorySystem, categoryCd0);
TokenParam categoryParam2 = new TokenParam(categorySystem, categoryCd1); TokenParam categoryParam2 = new TokenParam(categorySystem, categoryCd1);
params.add(Observation.SP_CATEGORY, buildTokenAndListParam(categoryParam1, categoryParam2)); params.add(Observation.SP_CATEGORY, buildTokenAndListParam(categoryParam1, categoryParam2));
@ -357,6 +402,11 @@ public class FhirResourceDaoR4SearchLastNIT extends BaseJpaTest {
// One code parameter. // One code parameter.
SearchParameterMap params = new SearchParameterMap(); SearchParameterMap params = new SearchParameterMap();
ReferenceParam subjectParam1 = new ReferenceParam("Patient", "", patient0Id.getValue());
ReferenceParam subjectParam2 = new ReferenceParam("Patient", "", patient1Id.getValue());
ReferenceParam subjectParam3 = new ReferenceParam("Patient", "", patient2Id.getValue());
params.add(Observation.SP_SUBJECT, buildReferenceAndListParam(subjectParam1, subjectParam2, subjectParam3));
TokenParam code = new TokenParam(codeSystem, observationCd0); TokenParam code = new TokenParam(codeSystem, observationCd0);
params.add(Observation.SP_CODE, buildTokenAndListParam(code)); params.add(Observation.SP_CODE, buildTokenAndListParam(code));
List<String> sortedObservationCodes = new ArrayList<>(); List<String> sortedObservationCodes = new ArrayList<>();
@ -371,6 +421,7 @@ public class FhirResourceDaoR4SearchLastNIT extends BaseJpaTest {
// Another code parameter. // Another code parameter.
params = new SearchParameterMap(); params = new SearchParameterMap();
params.add(Observation.SP_SUBJECT, buildReferenceAndListParam(subjectParam1, subjectParam2, subjectParam3));
code = new TokenParam(codeSystem, observationCd2); code = new TokenParam(codeSystem, observationCd2);
params.add(Observation.SP_CODE, buildTokenAndListParam(code)); params.add(Observation.SP_CODE, buildTokenAndListParam(code));
sortedObservationCodes = new ArrayList<>(); sortedObservationCodes = new ArrayList<>();
@ -385,6 +436,11 @@ public class FhirResourceDaoR4SearchLastNIT extends BaseJpaTest {
// Two code parameters. // Two code parameters.
SearchParameterMap params = new SearchParameterMap(); SearchParameterMap params = new SearchParameterMap();
ReferenceParam subjectParam1 = new ReferenceParam("Patient", "", patient0Id.getValue());
ReferenceParam subjectParam2 = new ReferenceParam("Patient", "", patient1Id.getValue());
ReferenceParam subjectParam3 = new ReferenceParam("Patient", "", patient2Id.getValue());
params.add(Observation.SP_SUBJECT, buildReferenceAndListParam(subjectParam1, subjectParam2, subjectParam3));
TokenParam codeParam1 = new TokenParam(codeSystem, observationCd0); TokenParam codeParam1 = new TokenParam(codeSystem, observationCd0);
TokenParam codeParam2 = new TokenParam(codeSystem, observationCd1); TokenParam codeParam2 = new TokenParam(codeSystem, observationCd1);
params.add(Observation.SP_CODE, buildTokenAndListParam(codeParam1, codeParam2)); params.add(Observation.SP_CODE, buildTokenAndListParam(codeParam1, codeParam2));

View File

@ -79,6 +79,8 @@ public class PersistObservationIndexedSearchParamLastNR4IT {
private final String CODEFIRSTCODINGSYSTEM = "http://mycodes.org/fhir/observation-code"; private final String CODEFIRSTCODINGSYSTEM = "http://mycodes.org/fhir/observation-code";
private final String CODEFIRSTCODINGCODE = "test-code"; private final String CODEFIRSTCODINGCODE = "test-code";
private ReferenceAndListParam multiSubjectParams = null;
@Test @Test
public void testIndexObservationSingle() { public void testIndexObservationSingle() {
indexSingleObservation(); indexSingleObservation();
@ -177,6 +179,8 @@ public class PersistObservationIndexedSearchParamLastNR4IT {
// Check that all observations were indexed. // Check that all observations were indexed.
SearchParameterMap searchParameterMap = new SearchParameterMap(); SearchParameterMap searchParameterMap = new SearchParameterMap();
searchParameterMap.add(Observation.SP_SUBJECT, multiSubjectParams);
//searchParameterMap.
List<String> observationIdsOnly = elasticsearchSvc.executeLastN(searchParameterMap, 10); List<String> observationIdsOnly = elasticsearchSvc.executeLastN(searchParameterMap, 10);
assertEquals(100, observationIdsOnly.size()); assertEquals(100, observationIdsOnly.size());
@ -223,10 +227,14 @@ public class PersistObservationIndexedSearchParamLastNR4IT {
categoryCodeableConcept2.setCoding(category2); categoryCodeableConcept2.setCoding(category2);
categoryConcepts2.add(categoryCodeableConcept2); categoryConcepts2.add(categoryCodeableConcept2);
ReferenceOrListParam subjectParams = new ReferenceOrListParam();
for (int patientCount = 0; patientCount < 10; patientCount++) { for (int patientCount = 0; patientCount < 10; patientCount++) {
String subjectId = String.valueOf(patientCount); String subjectId = String.valueOf(patientCount);
ReferenceParam subjectParam = new ReferenceParam("Patient", "", subjectId);
subjectParams.addOr(subjectParam);
for (int entryCount = 0; entryCount < 10; entryCount++) { for (int entryCount = 0; entryCount < 10; entryCount++) {
Observation observation = new Observation(); Observation observation = new Observation();
@ -254,6 +262,9 @@ public class PersistObservationIndexedSearchParamLastNR4IT {
} }
} }
multiSubjectParams = new ReferenceAndListParam().addAnd(subjectParams);
} }
@Test @Test
@ -265,6 +276,7 @@ public class PersistObservationIndexedSearchParamLastNR4IT {
assertNotNull(observation); assertNotNull(observation);
SearchParameterMap searchParameterMap = new SearchParameterMap(); SearchParameterMap searchParameterMap = new SearchParameterMap();
searchParameterMap.add(Observation.SP_SUBJECT, multiSubjectParams);
List<String> observationIdsOnly = elasticsearchSvc.executeLastN(searchParameterMap, 10); List<String> observationIdsOnly = elasticsearchSvc.executeLastN(searchParameterMap, 10);
assertEquals(100, observationIdsOnly.size()); assertEquals(100, observationIdsOnly.size());
assertTrue(observationIdsOnly.contains("55")); assertTrue(observationIdsOnly.contains("55"));

View File

@ -56,23 +56,40 @@ public class LastNElasticsearchSvcMultipleObservationsIT {
} }
@Test @Test
public void testLastNNoCriteriaQuery() { public void testLastNAllPatientsQuery() {
// execute Observation ID search (Composite Aggregation) last 3 observations for each patient // execute Observation ID search (Composite Aggregation) last 3 observations for each patient
List<ObservationJson> observations = elasticsearchSvc.executeLastNWithAllFields(null, 3); SearchParameterMap searchParameterMap = new SearchParameterMap();
ReferenceParam subjectParam = new ReferenceParam("Patient", "", "0");
searchParameterMap.add(Observation.SP_SUBJECT, buildReferenceAndListParam(subjectParam));
subjectParam = new ReferenceParam("Patient", "", "1");
searchParameterMap.add(Observation.SP_SUBJECT, buildReferenceAndListParam(subjectParam));
subjectParam = new ReferenceParam("Patient", "", "2");
searchParameterMap.add(Observation.SP_SUBJECT, buildReferenceAndListParam(subjectParam));
subjectParam = new ReferenceParam("Patient", "", "3");
searchParameterMap.add(Observation.SP_SUBJECT, buildReferenceAndListParam(subjectParam));
subjectParam = new ReferenceParam("Patient", "", "4");
searchParameterMap.add(Observation.SP_SUBJECT, buildReferenceAndListParam(subjectParam));
subjectParam = new ReferenceParam("Patient", "", "5");
searchParameterMap.add(Observation.SP_SUBJECT, buildReferenceAndListParam(subjectParam));
subjectParam = new ReferenceParam("Patient", "", "6");
searchParameterMap.add(Observation.SP_SUBJECT, buildReferenceAndListParam(subjectParam));
subjectParam = new ReferenceParam("Patient", "", "7");
searchParameterMap.add(Observation.SP_SUBJECT, buildReferenceAndListParam(subjectParam));
subjectParam = new ReferenceParam("Patient", "", "8");
searchParameterMap.add(Observation.SP_SUBJECT, buildReferenceAndListParam(subjectParam));
subjectParam = new ReferenceParam("Patient", "", "9");
searchParameterMap.add(Observation.SP_SUBJECT, buildReferenceAndListParam(subjectParam));
validateQueryResponse(observations); List<ObservationJson> observations = elasticsearchSvc.executeLastNWithAllFields(searchParameterMap, 3);
} assertEquals(60, observations.size());
private void validateQueryResponse(List<ObservationJson> observationIdsOnly) {
assertEquals(60, observationIdsOnly.size());
// Observation documents should be grouped by subject, then by observation code, and then sorted by effective date/time // Observation documents should be grouped by subject, then by observation code, and then sorted by effective date/time
// within each observation code. Verify the grouping by creating a nested Map. // within each observation code. Verify the grouping by creating a nested Map.
Map<String, Map<String, List<Date>>> queriedPatientObservationMap = new HashMap<>(); Map<String, Map<String, List<Date>>> queriedPatientObservationMap = new HashMap<>();
ObservationJson previousObservationJson = null; ObservationJson previousObservationJson = null;
for (ObservationJson observationJson : observationIdsOnly) { for (ObservationJson observationJson : observations) {
assertNotNull(observationJson.getIdentifier()); assertNotNull(observationJson.getIdentifier());
assertNotNull(observationJson.getSubject()); assertNotNull(observationJson.getSubject());
assertNotNull(observationJson.getCode_concept_id()); assertNotNull(observationJson.getCode_concept_id());
@ -373,4 +390,19 @@ public class LastNElasticsearchSvcMultipleObservationsIT {
} }
@Test
public void testLastNNoParamsQuery() {
SearchParameterMap searchParameterMap = new SearchParameterMap();
List<ObservationJson> observations = elasticsearchSvc.executeLastNWithAllFields(searchParameterMap, 1);
assertEquals(2, observations.size());
String observationCode1 = observations.get(0).getCode_coding_code_system_hash();
String observationCode2 = observations.get(1).getCode_coding_code_system_hash();
assertNotEquals(observationCode1, observationCode2);
}
} }

View File

@ -47,9 +47,9 @@ public class TestElasticsearchConfig {
return embeddedElastic; return embeddedElastic;
} }
@PreDestroy // @PreDestroy
public void stop() { // public void stop() {
embeddedElasticSearch().stop(); // embeddedElasticSearch().stop();
} // }
} }

View File

@ -62,6 +62,7 @@ public class SearchParameterMap implements Serializable {
private SearchTotalModeEnum mySearchTotalMode; private SearchTotalModeEnum mySearchTotalMode;
private QuantityParam myNearDistanceParam; private QuantityParam myNearDistanceParam;
private boolean myLastN; private boolean myLastN;
private Integer myLastNMax;
/** /**
* Constructor * Constructor
@ -322,6 +323,24 @@ public class SearchParameterMap implements Serializable {
} }
/**
* If set, tells the server the maximum number of observations to return for each
* observation code in the result set of a lastn operation
*/
public Integer getLastNMax() {
return myLastNMax;
}
/**
* If set, tells the server the maximum number of observations to return for each
* observation code in the result set of a lastn operation
*/
public SearchParameterMap setLastNMax(Integer theLastNMax) {
myLastNMax = theLastNMax;
return this;
}
/** /**
* This method creates a URL query string representation of the parameters in this * This method creates a URL query string representation of the parameters in this
* object, excluding the part before the parameters, e.g. * object, excluding the part before the parameters, e.g.