Patient everything operation with offset query parameter throws exception (#6085)
* Patient $everything operation with _offset query parameter throws exception - tests and implementation * Patient $everything operation with _offset query parameter throws exception - added changelog
This commit is contained in:
parent
7e9faf84b8
commit
6c446277e3
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
type: add
|
||||
issue: 6070
|
||||
jira: SMILE-8503
|
||||
title: "Added paging support for `$everything` operation in synchronous search mode."
|
|
@ -19,7 +19,6 @@
|
|||
*/
|
||||
package ca.uhn.fhir.jpa.dao;
|
||||
|
||||
import ca.uhn.fhir.i18n.Msg;
|
||||
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
||||
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoPatient;
|
||||
import ca.uhn.fhir.jpa.api.dao.PatientEverythingParameters;
|
||||
|
@ -73,8 +72,7 @@ public class JpaResourceDaoPatient<T extends IBaseResource> extends BaseHapiFhir
|
|||
paramMap.setCount(theCount.getValue());
|
||||
}
|
||||
if (theOffset != null) {
|
||||
throw new IllegalArgumentException(
|
||||
Msg.code(1106) + "Everything operation does not support offset searching");
|
||||
paramMap.setOffset(theOffset.getValue());
|
||||
}
|
||||
if (theContent != null) {
|
||||
paramMap.add(Constants.PARAM_CONTENT, theContent);
|
||||
|
|
|
@ -1477,6 +1477,11 @@ public class QueryStack {
|
|||
mySqlBuilder.getSelect().addGroupings(firstPredicateBuilder.getResourceIdColumn());
|
||||
}
|
||||
|
||||
public void addOrdering() {
|
||||
BaseJoiningPredicateBuilder firstPredicateBuilder = mySqlBuilder.getOrCreateFirstPredicateBuilder();
|
||||
mySqlBuilder.getSelect().addOrderings(firstPredicateBuilder.getResourceIdColumn());
|
||||
}
|
||||
|
||||
public Condition createPredicateReferenceForEmbeddedChainedSearchResource(
|
||||
@Nullable DbColumn theSourceJoinColumn,
|
||||
String theResourceName,
|
||||
|
|
|
@ -571,8 +571,7 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
|
|||
*
|
||||
* @param theTargetPids
|
||||
*/
|
||||
private void extractTargetPidsFromIdParams(
|
||||
HashSet<Long> theTargetPids, List<ISearchQueryExecutor> theSearchQueryExecutors) {
|
||||
private void extractTargetPidsFromIdParams(Set<Long> theTargetPids) {
|
||||
// get all the IQueryParameterType objects
|
||||
// for _id -> these should all be StringParam values
|
||||
HashSet<String> ids = new HashSet<>();
|
||||
|
@ -601,10 +600,6 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
|
|||
for (JpaPid pid : idToPid.values()) {
|
||||
theTargetPids.add(pid.getId());
|
||||
}
|
||||
|
||||
// add the target pids to our executors as the first
|
||||
// results iterator to go through
|
||||
theSearchQueryExecutors.add(new ResolvedSearchQueryExecutor(new ArrayList<>(theTargetPids)));
|
||||
}
|
||||
|
||||
private void createChunkedQuery(
|
||||
|
@ -616,13 +611,29 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
|
|||
RequestDetails theRequest,
|
||||
List<Long> thePidList,
|
||||
List<ISearchQueryExecutor> theSearchQueryExecutors) {
|
||||
String sqlBuilderResourceName = myParams.getEverythingMode() == null ? myResourceName : null;
|
||||
if (myParams.getEverythingMode() != null) {
|
||||
createChunkedQueryForEverythingSearch(
|
||||
theParams, theOffset, theMaximumResults, theCountOnlyFlag, thePidList, theSearchQueryExecutors);
|
||||
} else {
|
||||
createChunkedQueryNormalSearch(
|
||||
theParams, sort, theOffset, theCountOnlyFlag, theRequest, thePidList, theSearchQueryExecutors);
|
||||
}
|
||||
}
|
||||
|
||||
private void createChunkedQueryNormalSearch(
|
||||
SearchParameterMap theParams,
|
||||
SortSpec sort,
|
||||
Integer theOffset,
|
||||
boolean theCountOnlyFlag,
|
||||
RequestDetails theRequest,
|
||||
List<Long> thePidList,
|
||||
List<ISearchQueryExecutor> theSearchQueryExecutors) {
|
||||
SearchQueryBuilder sqlBuilder = new SearchQueryBuilder(
|
||||
myContext,
|
||||
myStorageSettings,
|
||||
myPartitionSettings,
|
||||
myRequestPartitionId,
|
||||
sqlBuilderResourceName,
|
||||
myResourceName,
|
||||
mySqlBuilderFactory,
|
||||
myDialectProvider,
|
||||
theCountOnlyFlag);
|
||||
|
@ -640,67 +651,22 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
|
|||
}
|
||||
}
|
||||
|
||||
JdbcTemplate jdbcTemplate = new JdbcTemplate(myEntityManagerFactory.getDataSource());
|
||||
jdbcTemplate.setFetchSize(myFetchSize);
|
||||
if (theMaximumResults != null) {
|
||||
jdbcTemplate.setMaxRows(theMaximumResults);
|
||||
/*
|
||||
* If we're doing a filter, always use the resource table as the root - This avoids the possibility of
|
||||
* specific filters with ORs as their root from working around the natural resource type / deletion
|
||||
* status / partition IDs built into queries.
|
||||
*/
|
||||
if (theParams.containsKey(Constants.PARAM_FILTER)) {
|
||||
Condition partitionIdPredicate = sqlBuilder
|
||||
.getOrCreateResourceTablePredicateBuilder()
|
||||
.createPartitionIdPredicate(myRequestPartitionId);
|
||||
if (partitionIdPredicate != null) {
|
||||
sqlBuilder.addPredicate(partitionIdPredicate);
|
||||
}
|
||||
}
|
||||
|
||||
if (myParams.getEverythingMode() != null) {
|
||||
HashSet<Long> targetPids = new HashSet<>();
|
||||
if (myParams.get(IAnyResource.SP_RES_ID) != null) {
|
||||
// will add an initial search executor for
|
||||
// _id params
|
||||
extractTargetPidsFromIdParams(targetPids, theSearchQueryExecutors);
|
||||
} else {
|
||||
// For Everything queries, we make the query root by the ResourceLink table, since this query
|
||||
// is basically a reverse-include search. For type/Everything (as opposed to instance/Everything)
|
||||
// the one problem with this approach is that it doesn't catch Patients that have absolutely
|
||||
// nothing linked to them. So we do one additional query to make sure we catch those too.
|
||||
SearchQueryBuilder fetchPidsSqlBuilder = new SearchQueryBuilder(
|
||||
myContext,
|
||||
myStorageSettings,
|
||||
myPartitionSettings,
|
||||
myRequestPartitionId,
|
||||
myResourceName,
|
||||
mySqlBuilderFactory,
|
||||
myDialectProvider,
|
||||
theCountOnlyFlag);
|
||||
GeneratedSql allTargetsSql = fetchPidsSqlBuilder.generate(theOffset, myMaxResultsToFetch);
|
||||
String sql = allTargetsSql.getSql();
|
||||
Object[] args = allTargetsSql.getBindVariables().toArray(new Object[0]);
|
||||
|
||||
List<Long> output = jdbcTemplate.query(sql, args, new SingleColumnRowMapper<>(Long.class));
|
||||
|
||||
// we add a search executor to fetch unlinked patients first
|
||||
theSearchQueryExecutors.add(new ResolvedSearchQueryExecutor(output));
|
||||
}
|
||||
|
||||
List<String> typeSourceResources = new ArrayList<>();
|
||||
if (myParams.get(Constants.PARAM_TYPE) != null) {
|
||||
typeSourceResources.addAll(extractTypeSourceResourcesFromParams());
|
||||
}
|
||||
|
||||
queryStack3.addPredicateEverythingOperation(
|
||||
myResourceName, typeSourceResources, targetPids.toArray(new Long[0]));
|
||||
} else {
|
||||
/*
|
||||
* If we're doing a filter, always use the resource table as the root - This avoids the possibility of
|
||||
* specific filters with ORs as their root from working around the natural resource type / deletion
|
||||
* status / partition IDs built into queries.
|
||||
*/
|
||||
if (theParams.containsKey(Constants.PARAM_FILTER)) {
|
||||
Condition partitionIdPredicate = sqlBuilder
|
||||
.getOrCreateResourceTablePredicateBuilder()
|
||||
.createPartitionIdPredicate(myRequestPartitionId);
|
||||
if (partitionIdPredicate != null) {
|
||||
sqlBuilder.addPredicate(partitionIdPredicate);
|
||||
}
|
||||
}
|
||||
|
||||
// Normal search
|
||||
searchForIdsWithAndOr(sqlBuilder, queryStack3, myParams, theRequest);
|
||||
}
|
||||
// Normal search
|
||||
searchForIdsWithAndOr(sqlBuilder, queryStack3, myParams, theRequest);
|
||||
|
||||
// If we haven't added any predicates yet, we're doing a search for all resources. Make sure we add the
|
||||
// partition ID predicate in that case.
|
||||
|
@ -714,16 +680,10 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
|
|||
}
|
||||
|
||||
// Add PID list predicate for full text search and/or lastn operation
|
||||
if (thePidList != null && thePidList.size() > 0) {
|
||||
sqlBuilder.addResourceIdsPredicate(thePidList);
|
||||
}
|
||||
addPidListPredicate(thePidList, sqlBuilder);
|
||||
|
||||
// Last updated
|
||||
DateRangeParam lu = myParams.getLastUpdated();
|
||||
if (lu != null && !lu.isEmpty()) {
|
||||
Condition lastUpdatedPredicates = sqlBuilder.addPredicateLastUpdated(lu);
|
||||
sqlBuilder.addPredicate(lastUpdatedPredicates);
|
||||
}
|
||||
addLastUpdatePredicate(sqlBuilder);
|
||||
|
||||
/*
|
||||
* Exclude the pids already in the previous iterator. This is an optimization, as opposed
|
||||
|
@ -770,6 +730,11 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
|
|||
/*
|
||||
* Now perform the search
|
||||
*/
|
||||
executeSearch(theOffset, theSearchQueryExecutors, sqlBuilder);
|
||||
}
|
||||
|
||||
private void executeSearch(
|
||||
Integer theOffset, List<ISearchQueryExecutor> theSearchQueryExecutors, SearchQueryBuilder sqlBuilder) {
|
||||
GeneratedSql generatedSql = sqlBuilder.generate(theOffset, myMaxResultsToFetch);
|
||||
if (!generatedSql.isMatchNothing()) {
|
||||
SearchQueryExecutor executor =
|
||||
|
@ -778,6 +743,111 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
|
|||
}
|
||||
}
|
||||
|
||||
private void createChunkedQueryForEverythingSearch(
|
||||
SearchParameterMap theParams,
|
||||
Integer theOffset,
|
||||
Integer theMaximumResults,
|
||||
boolean theCountOnlyFlag,
|
||||
List<Long> thePidList,
|
||||
List<ISearchQueryExecutor> theSearchQueryExecutors) {
|
||||
|
||||
SearchQueryBuilder sqlBuilder = new SearchQueryBuilder(
|
||||
myContext,
|
||||
myStorageSettings,
|
||||
myPartitionSettings,
|
||||
myRequestPartitionId,
|
||||
null,
|
||||
mySqlBuilderFactory,
|
||||
myDialectProvider,
|
||||
theCountOnlyFlag);
|
||||
|
||||
QueryStack queryStack3 = new QueryStack(
|
||||
theParams, myStorageSettings, myContext, sqlBuilder, mySearchParamRegistry, myPartitionSettings);
|
||||
|
||||
JdbcTemplate jdbcTemplate = initializeJdbcTemplate(theMaximumResults);
|
||||
|
||||
Set<Long> targetPids = new HashSet<>();
|
||||
if (myParams.get(IAnyResource.SP_RES_ID) != null) {
|
||||
|
||||
extractTargetPidsFromIdParams(targetPids);
|
||||
|
||||
// add the target pids to our executors as the first
|
||||
// results iterator to go through
|
||||
theSearchQueryExecutors.add(new ResolvedSearchQueryExecutor(new ArrayList<>(targetPids)));
|
||||
} else {
|
||||
// For Everything queries, we make the query root by the ResourceLink table, since this query
|
||||
// is basically a reverse-include search. For type/Everything (as opposed to instance/Everything)
|
||||
// the one problem with this approach is that it doesn't catch Patients that have absolutely
|
||||
// nothing linked to them. So we do one additional query to make sure we catch those too.
|
||||
SearchQueryBuilder fetchPidsSqlBuilder = new SearchQueryBuilder(
|
||||
myContext,
|
||||
myStorageSettings,
|
||||
myPartitionSettings,
|
||||
myRequestPartitionId,
|
||||
myResourceName,
|
||||
mySqlBuilderFactory,
|
||||
myDialectProvider,
|
||||
theCountOnlyFlag);
|
||||
GeneratedSql allTargetsSql = fetchPidsSqlBuilder.generate(theOffset, myMaxResultsToFetch);
|
||||
String sql = allTargetsSql.getSql();
|
||||
Object[] args = allTargetsSql.getBindVariables().toArray(new Object[0]);
|
||||
|
||||
List<Long> output = jdbcTemplate.query(sql, args, new SingleColumnRowMapper<>(Long.class));
|
||||
|
||||
// we add a search executor to fetch unlinked patients first
|
||||
theSearchQueryExecutors.add(new ResolvedSearchQueryExecutor(output));
|
||||
}
|
||||
|
||||
List<String> typeSourceResources = new ArrayList<>();
|
||||
if (myParams.get(Constants.PARAM_TYPE) != null) {
|
||||
typeSourceResources.addAll(extractTypeSourceResourcesFromParams());
|
||||
}
|
||||
|
||||
queryStack3.addPredicateEverythingOperation(
|
||||
myResourceName, typeSourceResources, targetPids.toArray(new Long[0]));
|
||||
|
||||
// Add PID list predicate for full text search and/or lastn operation
|
||||
addPidListPredicate(thePidList, sqlBuilder);
|
||||
|
||||
/*
|
||||
* If offset is present, we want deduplicate the results by using GROUP BY
|
||||
* ORDER BY is required to make sure we return unique results for each page
|
||||
*/
|
||||
if (theOffset != null) {
|
||||
queryStack3.addGrouping();
|
||||
queryStack3.addOrdering();
|
||||
queryStack3.setUseAggregate(true);
|
||||
}
|
||||
|
||||
/*
|
||||
* Now perform the search
|
||||
*/
|
||||
executeSearch(theOffset, theSearchQueryExecutors, sqlBuilder);
|
||||
}
|
||||
|
||||
private void addPidListPredicate(List<Long> thePidList, SearchQueryBuilder theSqlBuilder) {
|
||||
if (thePidList != null && !thePidList.isEmpty()) {
|
||||
theSqlBuilder.addResourceIdsPredicate(thePidList);
|
||||
}
|
||||
}
|
||||
|
||||
private void addLastUpdatePredicate(SearchQueryBuilder theSqlBuilder) {
|
||||
DateRangeParam lu = myParams.getLastUpdated();
|
||||
if (lu != null && !lu.isEmpty()) {
|
||||
Condition lastUpdatedPredicates = theSqlBuilder.addPredicateLastUpdated(lu);
|
||||
theSqlBuilder.addPredicate(lastUpdatedPredicates);
|
||||
}
|
||||
}
|
||||
|
||||
private JdbcTemplate initializeJdbcTemplate(Integer theMaximumResults) {
|
||||
JdbcTemplate jdbcTemplate = new JdbcTemplate(myEntityManagerFactory.getDataSource());
|
||||
jdbcTemplate.setFetchSize(myFetchSize);
|
||||
if (theMaximumResults != null) {
|
||||
jdbcTemplate.setMaxRows(theMaximumResults);
|
||||
}
|
||||
return jdbcTemplate;
|
||||
}
|
||||
|
||||
private Collection<String> extractTypeSourceResourcesFromParams() {
|
||||
|
||||
List<List<IQueryParameterType>> listOfList = myParams.get(Constants.PARAM_TYPE);
|
||||
|
@ -2273,13 +2343,7 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
|
|||
// If we don't have a query yet, create one
|
||||
if (myResultsIterator == null) {
|
||||
if (myMaxResultsToFetch == null) {
|
||||
if (myParams.getLoadSynchronousUpTo() != null) {
|
||||
myMaxResultsToFetch = myParams.getLoadSynchronousUpTo();
|
||||
} else if (myParams.getOffset() != null && myParams.getCount() != null) {
|
||||
myMaxResultsToFetch = myParams.getCount();
|
||||
} else {
|
||||
myMaxResultsToFetch = myStorageSettings.getFetchSizeDefaultMaximum();
|
||||
}
|
||||
myMaxResultsToFetch = calculateMaxResultsToFetch();
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -2305,19 +2369,12 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
|
|||
|
||||
Long nextLong = myResultsIterator.next();
|
||||
if (myHavePerfTraceFoundIdHook) {
|
||||
HookParams params = new HookParams()
|
||||
.add(Integer.class, System.identityHashCode(this))
|
||||
.add(Object.class, nextLong);
|
||||
CompositeInterceptorBroadcaster.doCallHooks(
|
||||
myInterceptorBroadcaster,
|
||||
myRequest,
|
||||
Pointcut.JPA_PERFTRACE_SEARCH_FOUND_ID,
|
||||
params);
|
||||
callPerformanceTracingHook(nextLong);
|
||||
}
|
||||
|
||||
if (nextLong != null) {
|
||||
JpaPid next = JpaPid.fromId(nextLong);
|
||||
if (myPidSet.add(next)) {
|
||||
if (myPidSet.add(next) && doNotSkipNextPidForEverything()) {
|
||||
myNext = next;
|
||||
myNonSkipCount++;
|
||||
break;
|
||||
|
@ -2350,11 +2407,10 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
|
|||
if (myIncludesIterator != null) {
|
||||
while (myIncludesIterator.hasNext()) {
|
||||
JpaPid next = myIncludesIterator.next();
|
||||
if (next != null)
|
||||
if (myPidSet.add(next)) {
|
||||
myNext = next;
|
||||
break;
|
||||
}
|
||||
if (next != null && myPidSet.add(next) && doNotSkipNextPidForEverything()) {
|
||||
myNext = next;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (myNext == null) {
|
||||
myNext = NO_MORE;
|
||||
|
@ -2393,6 +2449,30 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
|
|||
}
|
||||
}
|
||||
|
||||
private Integer calculateMaxResultsToFetch() {
|
||||
if (myParams.getLoadSynchronousUpTo() != null) {
|
||||
return myParams.getLoadSynchronousUpTo();
|
||||
} else if (myParams.getOffset() != null && myParams.getCount() != null) {
|
||||
return myParams.getEverythingMode() != null
|
||||
? myParams.getOffset() + myParams.getCount()
|
||||
: myParams.getCount();
|
||||
} else {
|
||||
return myStorageSettings.getFetchSizeDefaultMaximum();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean doNotSkipNextPidForEverything() {
|
||||
return !(myParams.getEverythingMode() != null && (myOffset != null && myOffset >= myPidSet.size()));
|
||||
}
|
||||
|
||||
private void callPerformanceTracingHook(Long theNextLong) {
|
||||
HookParams params = new HookParams()
|
||||
.add(Integer.class, System.identityHashCode(this))
|
||||
.add(Object.class, theNextLong);
|
||||
CompositeInterceptorBroadcaster.doCallHooks(
|
||||
myInterceptorBroadcaster, myRequest, Pointcut.JPA_PERFTRACE_SEARCH_FOUND_ID, params);
|
||||
}
|
||||
|
||||
private void sendProcessingMsgAndFirePerformanceHook() {
|
||||
String msg = "Pass completed with no matching results seeking rows "
|
||||
+ myPidSet.size() + "-" + mySkipCount
|
||||
|
@ -2402,11 +2482,18 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
|
|||
}
|
||||
|
||||
private void initializeIteratorQuery(Integer theOffset, Integer theMaxResultsToFetch) {
|
||||
Integer offset = theOffset;
|
||||
if (myQueryList.isEmpty()) {
|
||||
// Capture times for Lucene/Elasticsearch queries as well
|
||||
mySearchRuntimeDetails.setQueryStopwatch(new StopWatch());
|
||||
|
||||
// setting offset to 0 to fetch all resource ids to guarantee
|
||||
// correct output result for everything operation during paging
|
||||
if (myParams.getEverythingMode() != null) {
|
||||
offset = 0;
|
||||
}
|
||||
myQueryList = createQuery(
|
||||
myParams, mySort, theOffset, theMaxResultsToFetch, false, myRequest, mySearchRuntimeDetails);
|
||||
myParams, mySort, offset, theMaxResultsToFetch, false, myRequest, mySearchRuntimeDetails);
|
||||
}
|
||||
|
||||
mySearchRuntimeDetails.setQueryStopwatch(new StopWatch());
|
||||
|
|
|
@ -42,6 +42,7 @@ import org.hl7.fhir.r4.model.UnsignedIntType;
|
|||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -1240,6 +1241,234 @@ public class ResourceProviderR4EverythingTest extends BaseResourceProviderR4Test
|
|||
assertThat(actualResourceIds).containsExactlyInAnyOrder(desiredPid, desiredPractitionerId, groupId);
|
||||
}
|
||||
|
||||
@Nested
|
||||
public class ResourceProviderR4EverythingWithOffsetTest {
|
||||
|
||||
// Patient 1 resources
|
||||
private IIdType o1Id;
|
||||
private IIdType e1Id;
|
||||
private IIdType p1Id;
|
||||
private IIdType c1Id;
|
||||
|
||||
// Patient 2 resources
|
||||
private IIdType o2Id;
|
||||
private IIdType p2Id;
|
||||
private IIdType c2Id;
|
||||
|
||||
// Patient 3 resources
|
||||
private IIdType o3Id;
|
||||
private IIdType p3Id;
|
||||
private IIdType c3Id;
|
||||
|
||||
// Patient 4 resources
|
||||
private IIdType o4Id;
|
||||
private IIdType p4Id;
|
||||
private IIdType c4Id;
|
||||
|
||||
// Resource id not linked to any Patient
|
||||
private IIdType c5Id;
|
||||
|
||||
@Test
|
||||
public void testPagingOverEverything_onPatientInstance_returnsCorrectBundles() {
|
||||
String methodName = "testEverythingPatientInstanceOffset";
|
||||
createPatientResources(methodName);
|
||||
|
||||
// There are 4 results, lets make 4 pages of 1.
|
||||
Parameters parameters = new Parameters();
|
||||
addOffsetAndCount(parameters, 0, 1);
|
||||
|
||||
// first page
|
||||
Parameters output = myClient.operation().onInstance(p1Id).named("everything").withParameters(parameters).execute();
|
||||
Bundle bundle = (Bundle) output.getParameter().get(0).getResource();
|
||||
List<IIdType> results = new ArrayList<>(validateAndGetIdListFromBundle(bundle, 1));
|
||||
|
||||
// second page
|
||||
Bundle nextBundle = getNextBundle(bundle);
|
||||
results.addAll(validateAndGetIdListFromBundle(nextBundle, 1));
|
||||
|
||||
// third page
|
||||
nextBundle = getNextBundle(nextBundle);
|
||||
results.addAll(validateAndGetIdListFromBundle(nextBundle, 1));
|
||||
|
||||
// fourth page
|
||||
nextBundle = getNextBundle(nextBundle);
|
||||
results.addAll(validateAndGetIdListFromBundle(nextBundle, 1));
|
||||
|
||||
// no next link
|
||||
assertNull(nextBundle.getLink("next"));
|
||||
assertThat(results).containsOnly(p1Id, c1Id, o1Id, e1Id);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPagingOverEverything_onPatientType_returnsCorrectBundles() {
|
||||
String methodName = "testEverythingPatientTypeOffset";
|
||||
createPatientResources(methodName);
|
||||
|
||||
// Test paging works.
|
||||
// There are 13 results, lets make 3 pages of 4 and 1 page of 1.
|
||||
Parameters parameters = new Parameters();
|
||||
addOffsetAndCount(parameters, 0, 4);
|
||||
|
||||
Parameters output = myClient.operation().onType(Patient.class).named("everything").withParameters(parameters).execute();
|
||||
validateEverythingBundle(output);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPagingOverEverything_onPatientTypeWithAndIdParameter_returnsCorrectBundles() {
|
||||
String methodName = "testEverythingPatientTypeWithAndIdParameter";
|
||||
createPatientResources(methodName);
|
||||
|
||||
// Test 4 patients using and List
|
||||
// e.g. _id=1&_id=2&_id=3&_id=4
|
||||
Parameters parameters = new Parameters();
|
||||
parameters.addParameter("_id", p1Id.getIdPart());
|
||||
parameters.addParameter("_id", p2Id.getIdPart());
|
||||
parameters.addParameter("_id", p3Id.getIdPart());
|
||||
parameters.addParameter("_id", p4Id.getIdPart());
|
||||
// Test for Patient 1-4 with _count=4 and _offset=0 with paging
|
||||
addOffsetAndCount(parameters, 0, 4);
|
||||
|
||||
Parameters output = myClient.operation().onType(Patient.class).named("everything").withParameters(parameters).execute();
|
||||
validateEverythingBundle(output);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPagingOverEverything_onPatientTypeWithOrIdParameter_returnsCorrectBundles() {
|
||||
String methodName = "testEverythingPatientTypeWithOrIdParameter";
|
||||
createPatientResources(methodName);
|
||||
|
||||
// Test 4 patients using or List
|
||||
// e.g. _id=1,2,3,4
|
||||
Parameters parameters = new Parameters();
|
||||
parameters.addParameter("_id", p1Id.getIdPart() + "," + p2Id.getIdPart() + "," + p3Id.getIdPart() + "," + p4Id.getIdPart());
|
||||
// Test for Patient 1-4 with _count=4 and _offset=0 with paging
|
||||
addOffsetAndCount(parameters, 0, 4);
|
||||
|
||||
Parameters output = myClient.operation().onType(Patient.class).named("everything").withParameters(parameters).execute();
|
||||
validateEverythingBundle(output);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPagingOverEverything_onPatientTypeWithOrAndIdParameter_returnsCorrectBundles() {
|
||||
String methodName = "testEverythingPatientTypeWithOrAndIdParameter";
|
||||
createPatientResources(methodName);
|
||||
|
||||
// Test combining 2 or-listed params
|
||||
// e.g. _id=1,2&_id=3,4
|
||||
Parameters parameters = new Parameters();
|
||||
parameters.addParameter("_id", p1Id.getIdPart() + "," + p2Id.getIdPart());
|
||||
parameters.addParameter("_id", p3Id.getIdPart() + "," + p4Id.getIdPart());
|
||||
// Test for Patient 1-4 with _count=4 and _offset=0 with paging
|
||||
addOffsetAndCount(parameters, 0, 4);
|
||||
|
||||
Parameters output = myClient.operation().onType(Patient.class).named("everything").withParameters(parameters).execute();
|
||||
validateEverythingBundle(output);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPagingOverEverything_onPatientTypeWithNotLinkedPatients_returnsCorrectBundles() {
|
||||
String methodName = "testEverythingPatientTypeWithNotLinkedPatients";
|
||||
|
||||
// setup
|
||||
p1Id = createPatient(methodName, "1");
|
||||
p2Id = createPatient(methodName, "2");
|
||||
p3Id = createPatient(methodName, "3");
|
||||
p4Id = createPatient(methodName, "4");
|
||||
|
||||
// Test patients without links
|
||||
Parameters parameters = new Parameters();
|
||||
addOffsetAndCount(parameters, 0, 1);
|
||||
|
||||
// first page
|
||||
Parameters output = myClient.operation().onType(Patient.class).named("everything").withParameters(parameters).execute();
|
||||
Bundle bundle = (Bundle) output.getParameter().get(0).getResource();
|
||||
List<IIdType> results = new ArrayList<>(validateAndGetIdListFromBundle(bundle, 1));
|
||||
|
||||
// second page
|
||||
Bundle nextBundle = getNextBundle(bundle);
|
||||
results.addAll(validateAndGetIdListFromBundle(nextBundle, 1));
|
||||
|
||||
// third page
|
||||
nextBundle = getNextBundle(nextBundle);
|
||||
results.addAll(validateAndGetIdListFromBundle(nextBundle, 1));
|
||||
|
||||
// fourth page
|
||||
nextBundle = getNextBundle(nextBundle);
|
||||
results.addAll(validateAndGetIdListFromBundle(nextBundle, 1));
|
||||
|
||||
// no next link
|
||||
assertNull(nextBundle.getLink("next"));
|
||||
assertThat(results).containsOnly(p1Id, p2Id, p3Id, p4Id);
|
||||
}
|
||||
|
||||
private void addOffsetAndCount(Parameters theParameters, int theOffset, int theCount) {
|
||||
theParameters.addParameter(new Parameters.ParametersParameterComponent()
|
||||
.setName("_count").setValue(new UnsignedIntType(theCount)));
|
||||
theParameters.addParameter(new Parameters.ParametersParameterComponent()
|
||||
.setName("_offset").setValue(new UnsignedIntType(theOffset)));
|
||||
}
|
||||
|
||||
private void validateEverythingBundle(Parameters theParameters) {
|
||||
// first page
|
||||
Bundle bundle = (Bundle) theParameters.getParameter().get(0).getResource();
|
||||
List<IIdType> results = new ArrayList<>(validateAndGetIdListFromBundle(bundle, 4));
|
||||
|
||||
// second page
|
||||
Bundle nextBundle = getNextBundle(bundle);
|
||||
results.addAll(validateAndGetIdListFromBundle(nextBundle, 4));
|
||||
|
||||
// third page
|
||||
nextBundle = getNextBundle(nextBundle);
|
||||
results.addAll(validateAndGetIdListFromBundle(nextBundle, 4));
|
||||
|
||||
// fourth page
|
||||
nextBundle = getNextBundle(nextBundle);
|
||||
results.addAll(validateAndGetIdListFromBundle(nextBundle, 1));
|
||||
|
||||
// no next link
|
||||
assertNull(nextBundle.getLink("next"));
|
||||
// make sure all resources are returned, order doesn't matter
|
||||
assertThat(results).containsOnly(p1Id, p2Id, p3Id, p4Id, c1Id, c2Id, c3Id, c4Id, o1Id, e1Id, o2Id, o3Id, o4Id);
|
||||
}
|
||||
|
||||
private Bundle getNextBundle(Bundle theBundle) {
|
||||
String next = theBundle.getLink("next").getUrl();
|
||||
return myClient.loadPage().byUrl(next).andReturnBundle(Bundle.class).execute();
|
||||
}
|
||||
|
||||
private List<IIdType> validateAndGetIdListFromBundle(Bundle theBundle, int theSize) {
|
||||
assertEquals(Bundle.BundleType.SEARCHSET, theBundle.getType());
|
||||
assertThat(theBundle.getEntry()).hasSize(theSize);
|
||||
return toUnqualifiedVersionlessIds(theBundle);
|
||||
}
|
||||
|
||||
private void createPatientResources(String theMethodName) {
|
||||
// Patient 1 resources
|
||||
o1Id = createOrganization(theMethodName, "1");
|
||||
e1Id = createEncounter(theMethodName, "1");
|
||||
p1Id = createPatientWithIndexAtOrganization(theMethodName, "1", o1Id);
|
||||
c1Id = createConditionWithEncounterForPatient(theMethodName, "1", p1Id, e1Id);
|
||||
|
||||
// Patient 2 resources
|
||||
o2Id = createOrganization(theMethodName, "2");
|
||||
p2Id = createPatientWithIndexAtOrganization(theMethodName, "2", o2Id);
|
||||
c2Id = createConditionForPatient(theMethodName, "2", p2Id);
|
||||
|
||||
// Patient 3 resources
|
||||
o3Id = createOrganization(theMethodName, "3");
|
||||
p3Id = createPatientWithIndexAtOrganization(theMethodName, "3", o3Id);
|
||||
c3Id = createConditionForPatient(theMethodName, "3", p3Id);
|
||||
|
||||
// Patient 4 resources
|
||||
o4Id = createOrganization(theMethodName, "4");
|
||||
p4Id = createPatientWithIndexAtOrganization(theMethodName, "4", o4Id);
|
||||
c4Id = createConditionForPatient(theMethodName, "4", p4Id);
|
||||
|
||||
// Resource not linked to any Patient
|
||||
c5Id = createConditionForPatient(theMethodName, "5", null);
|
||||
}
|
||||
}
|
||||
|
||||
private Bundle executeEverythingOperationOnInstance(IIdType theInstanceIdType) {
|
||||
return myClient
|
||||
.operation()
|
||||
|
@ -1250,12 +1479,22 @@ public class ResourceProviderR4EverythingTest extends BaseResourceProviderR4Test
|
|||
.execute();
|
||||
}
|
||||
|
||||
private IIdType createOrganization(String methodName, String s) {
|
||||
private IIdType createOrganization(String methodName, String theIndex) {
|
||||
Organization o1 = new Organization();
|
||||
o1.setName(methodName + s);
|
||||
o1.setName(methodName + theIndex);
|
||||
return myClient.create().resource(o1).execute().getId().toUnqualifiedVersionless();
|
||||
}
|
||||
|
||||
private IIdType createEncounter(String methodName, String theIndex) {
|
||||
Encounter e1 = new Encounter();
|
||||
e1.setLanguage(methodName + theIndex);
|
||||
return myClient.create().resource(e1).execute().getId().toUnqualifiedVersionless();
|
||||
}
|
||||
|
||||
public IIdType createPatient(String theMethodName, String theIndex) {
|
||||
return createPatientWithIndexAtOrganization(theMethodName, theIndex, null);
|
||||
}
|
||||
|
||||
public IIdType createPatientWithIndexAtOrganization(String theMethodName, String theIndex, IIdType theOrganizationId) {
|
||||
Patient p1 = new Patient();
|
||||
p1.addName().setFamily(theMethodName + theIndex);
|
||||
|
@ -1265,13 +1504,22 @@ public class ResourceProviderR4EverythingTest extends BaseResourceProviderR4Test
|
|||
}
|
||||
|
||||
public IIdType createConditionForPatient(String theMethodName, String theIndex, IIdType thePatientId) {
|
||||
return createConditionWithEncounterForPatient(theMethodName, theIndex, thePatientId, null);
|
||||
}
|
||||
|
||||
public IIdType createConditionWithEncounterForPatient(String theMethodName,
|
||||
String theIndex,
|
||||
IIdType thePatientId,
|
||||
IIdType theEncounterId) {
|
||||
Condition c = new Condition();
|
||||
c.addIdentifier().setValue(theMethodName + theIndex);
|
||||
if (thePatientId != null) {
|
||||
c.getSubject().setReferenceElement(thePatientId);
|
||||
}
|
||||
IIdType cId = myClient.create().resource(c).execute().getId().toUnqualifiedVersionless();
|
||||
return cId;
|
||||
if (theEncounterId != null) {
|
||||
c.setEncounter(new Reference(theEncounterId));
|
||||
}
|
||||
return myClient.create().resource(c).execute().getId().toUnqualifiedVersionless();
|
||||
}
|
||||
|
||||
private IIdType createMedicationRequestForPatient(IIdType thePatientId, String theIndex) {
|
||||
|
|
Loading…
Reference in New Issue