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:
volodymyr-korzh 2024-07-11 15:24:54 -06:00 committed by GitHub
parent 7e9faf84b8
commit 6c446277e3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 447 additions and 104 deletions

View File

@ -0,0 +1,5 @@
---
type: add
issue: 6070
jira: SMILE-8503
title: "Added paging support for `$everything` operation in synchronous search mode."

View File

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

View File

@ -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,

View File

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

View File

@ -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) {