New indexing for number and quantity search parameters (#3464)

* New Add Index forced-INCLUDE columns, and Online option

* Remove forced-include.  Simpler just to add all columns to index.

* New date index definitions.

* Oops.  Remove test for deleted feature.

* Cleanup

* Drop IDX_SP_DATE_UPDATED

* Add online option to drop index

* Update annotations to match and redefine FK

* Push sp->resource FK down to control name

* remove dead end

* Cleanup date tests

* New token query test cases for sql extraction

* New token search param indexing.

* Continue to allow the legacy hibernate names while we update the indexing.

* Fix jpa annotations with overloaded field

* Decide on token sorting - we do it.

* review comments

* fixme

* disable dead data migration too

* review fixups

* Test cases for numeric search

* add ne and ap

* Test for Numeric SP

* start

* finish number

* new indexing for quantity

* Move config

* cleanup
This commit is contained in:
michaelabuckley 2022-03-11 11:23:06 -05:00 committed by GitHub
parent 80d1a5a6f8
commit 03b6c589c8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 649 additions and 191 deletions

View File

@ -285,7 +285,7 @@ public class TestUtil {
Validate.notNull(fk);
Validate.isTrue(isNotBlank(fk.name()), "Foreign key on " + theAnnotatedElement + " has no name()");
List<String> legacySPHibernateFKNames = Arrays.asList(
"FKC97MPK37OKWU8QVTCEG2NH9VN", "FKCLTIHNC5TGPRJ9BHPT7XI5OTB", "FKGXSREUTYMMFJUWDSWV3Y887DO");
"FKC97MPK37OKWU8QVTCEG2NH9VN", "FKGXSREUTYMMFJUWDSWV3Y887DO");
if (legacySPHibernateFKNames.contains(fk.name())) {
// wipmb temporarily allow the hibernate legacy sp fk names
} else {

View File

@ -165,7 +165,7 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
{
Builder.BuilderWithTableName tokenTable = version.onTable("HFJ_SPIDX_TOKEN");
// replace and drop IDX_SP_DATE_HASH for sorting
// replace and drop IDX_SP_TOKEN_HASH for sorting
tokenTable
.addIndex("20220208.1", "IDX_SP_TOKEN_HASH_V2")
.unique(false).online(true)
@ -270,6 +270,157 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
batchChunk.addIndex("20220227.4", "IDX_BT2WC_II_SEQ").unique(false).withColumns("INSTANCE_ID", "SEQ");
batchChunk.addForeignKey("20220227.5", "FK_BT2WC_INSTANCE").toColumn("INSTANCE_ID").references("BT2_JOB_INSTANCE", "ID");
replaceNumericSPIndices(version);
replaceQuantitySPIndices(version);
}
/**
* new numeric search indexing
* @see ca.uhn.fhir.jpa.search.builder.predicate.NumberPredicateBuilder
* @see ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamNumber
*/
private void replaceNumericSPIndices(Builder theVersion) {
Builder.BuilderWithTableName numberTable = theVersion.onTable("HFJ_SPIDX_NUMBER");
// Main query index
numberTable
.addIndex("20220304.1", "IDX_SP_NUMBER_HASH_VAL_V2")
.unique(false)
.online(true)
.withColumns("HASH_IDENTITY", "SP_VALUE", "RES_ID", "PARTITION_ID");
numberTable.dropIndexOnline("20220304.2", "IDX_SP_NUMBER_HASH_VAL");
// for joining to other queries
{
numberTable
.addIndex("20220304.3", "IDX_SP_NUMBER_RESID_V2")
.unique(false).online(true)
.withColumns("RES_ID", "HASH_IDENTITY", "SP_VALUE", "PARTITION_ID");
// some engines tie the FK constraint to a particular index.
// So we need to drop and recreate the constraint to drop the old RES_ID index.
// Rename it while we're at it. FK7ULX3J1GG3V7MAQREJGC7YBC4 was not a pretty name.
numberTable.dropForeignKey("20220304.4", "FKCLTIHNC5TGPRJ9BHPT7XI5OTB", "HFJ_RESOURCE");
numberTable.dropIndexOnline("20220304.5", "IDX_SP_NUMBER_RESID");
numberTable.dropIndexOnline("20220304.6", "FKCLTIHNC5TGPRJ9BHPT7XI5OTB");
numberTable.addForeignKey("20220304.7", "FK_SP_NUMBER_RES")
.toColumn("RES_ID").references("HFJ_RESOURCE", "RES_ID");
}
// obsolete
numberTable.dropIndexOnline("20220304.8", "IDX_SP_NUMBER_UPDATED");
}
/**
* new quantity search indexing
* @see ca.uhn.fhir.jpa.search.builder.predicate.QuantityPredicateBuilder
* @see ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity
* @see ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantityNormalized
*/
private void replaceQuantitySPIndices(Builder theVersion) {
{
Builder.BuilderWithTableName quantityTable = theVersion.onTable("HFJ_SPIDX_QUANTITY");
// bare quantity
quantityTable
.addIndex("20220304.11", "IDX_SP_QUANTITY_HASH_V2")
.unique(false)
.online(true)
.withColumns("HASH_IDENTITY", "SP_VALUE", "RES_ID", "PARTITION_ID");
quantityTable.dropIndexOnline("20220304.12", "IDX_SP_QUANTITY_HASH");
// quantity with system+units
quantityTable
.addIndex("20220304.13", "IDX_SP_QUANTITY_HASH_SYSUN_V2")
.unique(false)
.online(true)
.withColumns("HASH_IDENTITY_SYS_UNITS", "SP_VALUE", "RES_ID", "PARTITION_ID");
quantityTable.dropIndexOnline("20220304.14", "IDX_SP_QUANTITY_HASH_SYSUN");
// quantity with units
quantityTable
.addIndex("20220304.15", "IDX_SP_QUANTITY_HASH_UN_V2")
.unique(false)
.online(true)
.withColumns("HASH_IDENTITY_AND_UNITS", "SP_VALUE", "RES_ID", "PARTITION_ID");
quantityTable.dropIndexOnline("20220304.16", "IDX_SP_QUANTITY_HASH_UN");
// for joining to other queries and sorts
{
quantityTable
.addIndex("20220304.17", "IDX_SP_QUANTITY_RESID_V2")
.unique(false).online(true)
.withColumns("RES_ID", "HASH_IDENTITY", "HASH_IDENTITY_SYS_UNITS", "HASH_IDENTITY_AND_UNITS", "SP_VALUE", "PARTITION_ID");
// some engines tie the FK constraint to a particular index.
// So we need to drop and recreate the constraint to drop the old RES_ID index.
// Rename it while we're at it. FK7ULX3J1GG3V7MAQREJGC7YBC4 was not a pretty name.
quantityTable.dropForeignKey("20220304.18", "FKN603WJJOI1A6ASEWXBBD78BI5", "HFJ_RESOURCE");
quantityTable.dropIndexOnline("20220304.19", "IDX_SP_QUANTITY_RESID");
quantityTable.dropIndexOnline("20220304.20", "FKN603WJJOI1A6ASEWXBBD78BI5");
quantityTable.addForeignKey("20220304.21", "FK_SP_QUANTITY_RES")
.toColumn("RES_ID").references("HFJ_RESOURCE", "RES_ID");
}
// obsolete
quantityTable.dropIndexOnline("20220304.22", "IDX_SP_QUANTITY_UPDATED");
}
{
Builder.BuilderWithTableName quantityNormTable = theVersion.onTable("HFJ_SPIDX_QUANTITY_NRML");
// bare quantity
quantityNormTable
.addIndex("20220304.23", "IDX_SP_QNTY_NRML_HASH_V2")
.unique(false)
.online(true)
.withColumns("HASH_IDENTITY", "SP_VALUE", "RES_ID", "PARTITION_ID");
quantityNormTable.dropIndexOnline("20220304.24", "IDX_SP_QNTY_NRML_HASH");
// quantity with system+units
quantityNormTable
.addIndex("20220304.25", "IDX_SP_QNTY_NRML_HASH_SYSUN_V2")
.unique(false)
.online(true)
.withColumns("HASH_IDENTITY_SYS_UNITS", "SP_VALUE", "RES_ID", "PARTITION_ID");
quantityNormTable.dropIndexOnline("20220304.26", "IDX_SP_QNTY_NRML_HASH_SYSUN");
// quantity with units
quantityNormTable
.addIndex("20220304.27", "IDX_SP_QNTY_NRML_HASH_UN_V2")
.unique(false)
.online(true)
.withColumns("HASH_IDENTITY_AND_UNITS", "SP_VALUE", "RES_ID", "PARTITION_ID");
quantityNormTable.dropIndexOnline("20220304.28", "IDX_SP_QNTY_NRML_HASH_UN");
// for joining to other queries and sorts
{
quantityNormTable
.addIndex("20220304.29", "IDX_SP_QNTY_NRML_RESID_V2")
.unique(false).online(true)
.withColumns("RES_ID", "HASH_IDENTITY", "HASH_IDENTITY_SYS_UNITS", "HASH_IDENTITY_AND_UNITS", "SP_VALUE", "PARTITION_ID");
// some engines tie the FK constraint to a particular index.
// So we need to drop and recreate the constraint to drop the old RES_ID index.
// Rename it while we're at it. FK7ULX3J1GG3V7MAQREJGC7YBC4 was not a pretty name.
quantityNormTable.dropForeignKey("20220304.30", "FKRCJOVMUH5KC0O6FVBLE319PYV", "HFJ_RESOURCE");
quantityNormTable.dropIndexOnline("20220304.31", "IDX_SP_QNTY_NRML_RESID");
quantityNormTable.dropIndexOnline("20220304.32", "FKRCJOVMUH5KC0O6FVBLE319PYV");
quantityNormTable.addForeignKey("20220304.33", "FK_SP_QUANTITYNM_RES")
.toColumn("RES_ID").references("HFJ_RESOURCE", "RES_ID");
}
// obsolete
quantityNormTable.dropIndexOnline("20220304.34", "IDX_SP_QNTY_NRML_UPDATED");
}
}
/**
@ -1263,7 +1414,8 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
spidxNumber
.addIndex("20180903.13", "IDX_SP_NUMBER_HASH_VAL")
.unique(false)
.withColumns("HASH_IDENTITY", "SP_VALUE");
.withColumns("HASH_IDENTITY", "SP_VALUE")
.doNothing();
spidxNumber
.addTask(new CalculateHashesTask(VersionEnum.V3_5_0, "20180903.14")
.addCalculator("HASH_IDENTITY", t -> BaseResourceIndexedSearchParam.calculateHashIdentity(new PartitionSettings(), RequestPartitionId.defaultPartition(), t.getResourceType(), t.getString("SP_NAME")))

View File

@ -1,23 +0,0 @@
package ca.uhn.fhir.jpa.config;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.dao.DaoTestDataBuilder;
import ca.uhn.fhir.jpa.partition.SystemRequestDetails;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class TestDataBuilderConfig {
@Autowired
FhirContext myFhirContext;
@Autowired
DaoRegistry myDaoRegistry;
@Bean
DaoTestDataBuilder testDataBuilder() {
return new DaoTestDataBuilder(myFhirContext, myDaoRegistry, new SystemRequestDetails());
}
}

View File

@ -0,0 +1,79 @@
package ca.uhn.fhir.jpa.dao;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.partition.SystemRequestDetails;
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
import ca.uhn.fhir.jpa.searchparam.ResourceSearch;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.rest.api.SortSpec;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.server.method.SortParameter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
import javax.annotation.Nonnull;
import java.util.List;
/**
* Simplistic implementation of FHIR queries.
*/
public class TestDaoSearch {
@Configuration
public static class Config {
@Bean
TestDaoSearch testDaoSearch(
@Autowired FhirContext theFhirContext,
@Autowired DaoRegistry theDaoRegistry,
@Autowired MatchUrlService theMatchUrlService
) {
return new TestDaoSearch(theFhirContext, theDaoRegistry, theMatchUrlService);
}
}
final MatchUrlService myMatchUrlService;
final DaoRegistry myDaoRegistry;
final FhirContext myFhirCtx;
public TestDaoSearch(FhirContext theFhirCtx, DaoRegistry theDaoRegistry, MatchUrlService theMatchUrlService) {
myMatchUrlService = theMatchUrlService;
myDaoRegistry = theDaoRegistry;
myFhirCtx = theFhirCtx;
}
public List<String> searchForIds(String theQueryUrl) {
// fake out the server url parsing
IBundleProvider result = searchForBundleProvider(theQueryUrl);
List<String> resourceIds = result.getAllResourceIds();
return resourceIds;
}
public IBundleProvider searchForBundleProvider(String theQueryUrl) {
ResourceSearch search = myMatchUrlService.getResourceSearch(theQueryUrl);
SearchParameterMap map = search.getSearchParameterMap();
map.setLoadSynchronous(true);
SystemRequestDetails request = fakeRequestDetailsFromUrl(theQueryUrl);
SortSpec sort = (SortSpec) new SortParameter(myFhirCtx).translateQueryParametersIntoServerArgument(request, null);
if (sort != null) {
map.setSort(sort);
}
IFhirResourceDao<?> dao = myDaoRegistry.getResourceDao(search.getResourceName());
IBundleProvider result = dao.search(map, request);
return result;
}
@Nonnull
private SystemRequestDetails fakeRequestDetailsFromUrl(String theQueryUrl) {
SystemRequestDetails request = new SystemRequestDetails();
UriComponents uriComponents = UriComponentsBuilder.fromUriString(theQueryUrl).build();
uriComponents.getQueryParams()
.forEach((key, value) -> request.addParameter(key, value.toArray(new String[0])));
return request;
}
}

View File

@ -8,38 +8,23 @@ import ca.uhn.fhir.jpa.config.TestR4Config;
import ca.uhn.fhir.jpa.dao.BaseDateSearchDaoTests;
import ca.uhn.fhir.jpa.dao.BaseJpaTest;
import ca.uhn.fhir.jpa.dao.DaoTestDataBuilder;
import ca.uhn.fhir.jpa.partition.SystemRequestDetails;
import ca.uhn.fhir.jpa.dao.TestDaoSearch;
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
import ca.uhn.fhir.jpa.searchparam.ResourceSearch;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.rest.api.SortSpec;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.server.method.SortParameter;
import ca.uhn.fhir.test.utilities.ITestDataBuilder;
import org.apache.commons.lang3.tuple.ImmutableTriple;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.Observation;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
import javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import static org.hamcrest.MatcherAssert.assertThat;
@ -49,10 +34,14 @@ import static org.hamcrest.Matchers.hasItems;
import static org.hamcrest.Matchers.not;
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = {TestR4Config.class, TestHibernateSearchAddInConfig.NoFT.class})
@ContextConfiguration(classes = {
TestR4Config.class,
TestHibernateSearchAddInConfig.NoFT.class,
DaoTestDataBuilder.Config.class,
TestDaoSearch.Config.class
})
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
public class FhirResourceDaoR4StandardQueriesNoFTTest extends BaseJpaTest {
private static final Logger ourLog = LoggerFactory.getLogger(FhirResourceDaoR4StandardQueriesNoFTTest.class);
@Autowired
PlatformTransactionManager myTxManager;
@Autowired
@ -64,6 +53,11 @@ public class FhirResourceDaoR4StandardQueriesNoFTTest extends BaseJpaTest {
protected DaoRegistry myDaoRegistry;
@Autowired
MatchUrlService myMatchUrlService;
@RegisterExtension
@Autowired
DaoTestDataBuilder myDataBuilder;
@Autowired
TestDaoSearch myTestDaoSearch;
@Override
protected PlatformTransactionManager getTxManager() {
@ -75,53 +69,17 @@ public class FhirResourceDaoR4StandardQueriesNoFTTest extends BaseJpaTest {
return myFhirCtx;
}
@Nested
public class DateSearchTests extends BaseDateSearchDaoTests {
@Override
protected Fixture constructFixture() {
DaoTestDataBuilder testDataBuilder = new DaoTestDataBuilder(myFhirCtx, myDaoRegistry, new SystemRequestDetails());
return new TestDataBuilderFixture<>(testDataBuilder, myObservationDao);
}
}
public static class TokenTestCase {
private Consumer<IBaseResource>[] myBuilders;
private List<ImmutableTriple<Boolean, String, String>> mySearchCases = new ArrayList<>();
public static TokenTestCase onObservation(Consumer<IBaseResource>... theBuilders) {
TokenTestCase result = new TokenTestCase();
result.myBuilders = theBuilders;
return result;
}
public TokenTestCase finds(String theMessage, String theQuery) {
mySearchCases.add(new ImmutableTriple(true,theMessage, theQuery));
return this;
}
public TokenTestCase doesNotFind(String theMessage, String theQuery) {
mySearchCases.add(new ImmutableTriple(false,theMessage, theQuery));
return this;
return new TestDataBuilderFixture<>(myDataBuilder, myObservationDao);
}
}
@Nested
public class TokenSearch {
// wipmb make this generic and share with ES, and Mongo.
/*
String criteria = "_has:Condition:subject:code=http://snomed.info/sct|55822003,http://snomed.info/sct|55822005&" +
"_has:Condition:asserter:code=http://snomed.info/sct|55822003,http://snomed.info/sct|55822004";
*/
ITestDataBuilder myDataBuilder = new DaoTestDataBuilder(myFhirCtx, myDaoRegistry, new SystemRequestDetails());
Set<IIdType> myCreatedIds = new HashSet<>();
@AfterEach
public void cleanup() {
ourLog.info("cleanup {}", myCreatedIds);
myCreatedIds.forEach(myObservationDao::delete);
}
@Nested
public class Queries {
@ -194,59 +152,262 @@ public class FhirResourceDaoR4StandardQueriesNoFTTest extends BaseJpaTest {
String idExA = withObservation(myDataBuilder.withObservationCode("http://example.org", "AValue")).getIdPart();
String idExM = withObservation(myDataBuilder.withObservationCode("http://example.org", "MValue")).getIdPart();
List<String> allIds = searchForIds("/Observation?_sort=code");
List<String> allIds = myTestDaoSearch.searchForIds("/Observation?_sort=code");
assertThat(allIds, hasItems(idAlphaA, idAlphaM, idAlphaZ, idExA, idExD, idExM));
allIds = searchForIds("/Observation?_sort=code&code=http://example.org|");
allIds = myTestDaoSearch.searchForIds("/Observation?_sort=code&code=http://example.org|");
assertThat(allIds, hasItems(idExA, idExD, idExM));
}
}
@SafeVarargs
private IIdType withObservation(Consumer<IBaseResource>... theBuilder) {
myObservationId = myDataBuilder.createObservation(theBuilder);
myCreatedIds.add(myObservationId);
return myObservationId;
}
private void assertFind(String theMessage, String theUrl) {
List<String> resourceIds = searchForIds(theUrl);
List<String> resourceIds = myTestDaoSearch.searchForIds(theUrl);
assertThat(theMessage, resourceIds, hasItem(equalTo(myObservationId.getIdPart())));
}
private void assertNotFind(String theMessage, String theUrl) {
List<String> resourceIds = searchForIds(theUrl);
List<String> resourceIds = myTestDaoSearch.searchForIds(theUrl);
assertThat(theMessage, resourceIds, not(hasItem(equalTo(myObservationId.getIdPart()))));
}
}
private List<String> searchForIds(String theQueryUrl) {
// fake out the server url parsing
ResourceSearch search = myMatchUrlService.getResourceSearch(theQueryUrl);
SearchParameterMap map = search.getSearchParameterMap();
map.setLoadSynchronous(true);
SystemRequestDetails request = fakeRequestDetailsFromUrl(theQueryUrl);
SortSpec sort = (SortSpec) new SortParameter(myFhirCtx).translateQueryParametersIntoServerArgument(request, null);
if (sort != null) {
map.setSort(sort);
}
IBundleProvider result = myObservationDao.search(map);
@Nested
public class NumericSearch {
IIdType myResourceId;
List<String> resourceIds = result.getAllResourceIds();
return resourceIds;
@Nested
public class Queries {
@Test
public void eq() {
withRiskAssessmentWithProbabilty(0.6);
assertNotFind("when gt", "/RiskAssessment?probability=0.5");
// fixme we break the spec here.
// assertFind("when a little gt - default is approx", "/RiskAssessment?probability=0.599");
// assertFind("when a little lt - default is approx", "/RiskAssessment?probability=0.601");
assertFind("when eq", "/RiskAssessment?probability=0.6");
assertNotFind("when lt", "/RiskAssessment?probability=0.7");
}
@Test
public void ne() {
withRiskAssessmentWithProbabilty(0.6);
assertFind("when gt", "/RiskAssessment?probability=ne0.5");
assertNotFind("when eq", "/RiskAssessment?probability=ne0.6");
assertFind("when lt", "/RiskAssessment?probability=ne0.7");
}
@Test
public void ap() {
withRiskAssessmentWithProbabilty(0.6);
assertNotFind("when gt", "/RiskAssessment?probability=ap0.5");
assertFind("when a little gt", "/RiskAssessment?probability=ap0.58");
assertFind("when eq", "/RiskAssessment?probability=ap0.6");
assertFind("when a little lt", "/RiskAssessment?probability=ap0.62");
assertNotFind("when lt", "/RiskAssessment?probability=ap0.7");
}
@Test
public void gt() {
withRiskAssessmentWithProbabilty(0.6);
assertFind("when gt", "/RiskAssessment?probability=gt0.5");
assertNotFind("when eq", "/RiskAssessment?probability=gt0.6");
assertNotFind("when lt", "/RiskAssessment?probability=gt0.7");
}
@Test
public void ge() {
withRiskAssessmentWithProbabilty(0.6);
assertFind("when gt", "/RiskAssessment?probability=ge0.5");
assertFind("when eq", "/RiskAssessment?probability=ge0.6");
assertNotFind("when lt", "/RiskAssessment?probability=ge0.7");
}
@Test
public void lt() {
withRiskAssessmentWithProbabilty(0.6);
assertNotFind("when gt", "/RiskAssessment?probability=lt0.5");
assertNotFind("when eq", "/RiskAssessment?probability=lt0.6");
assertFind("when lt", "/RiskAssessment?probability=lt0.7");
}
@Test
public void le() {
withRiskAssessmentWithProbabilty(0.6);
assertNotFind("when gt", "/RiskAssessment?probability=le0.5");
assertFind("when eq", "/RiskAssessment?probability=le0.6");
assertFind("when lt", "/RiskAssessment?probability=le0.7");
}
private void assertFind(String theMessage, String theUrl) {
List<String> resourceIds = myTestDaoSearch.searchForIds(theUrl);
assertThat(theMessage, resourceIds, hasItem(equalTo(myResourceId.getIdPart())));
}
private void assertNotFind(String theMessage, String theUrl) {
List<String> resourceIds = myTestDaoSearch.searchForIds(theUrl);
assertThat(theMessage, resourceIds, not(hasItem(equalTo(myResourceId.getIdPart()))));
}
}
private IIdType withRiskAssessmentWithProbabilty(double theValue) {
myResourceId = myDataBuilder.createResource("RiskAssessment", myDataBuilder.withPrimitiveAttribute("prediction.probabilityDecimal", theValue));
return myResourceId;
}
@Nested
public class Sorting {
@Test
public void sortByNumeric() {
String idAlpha7 = withRiskAssessmentWithProbabilty(0.7).getIdPart();
String idAlpha2 = withRiskAssessmentWithProbabilty(0.2).getIdPart();
String idAlpha5 = withRiskAssessmentWithProbabilty(0.5).getIdPart();
List<String> allIds = myTestDaoSearch.searchForIds("/RiskAssessment?_sort=probability");
assertThat(allIds, hasItems(idAlpha2, idAlpha5, idAlpha7));
}
}
@Nonnull
private SystemRequestDetails fakeRequestDetailsFromUrl(String theQueryUrl) {
SystemRequestDetails request = new SystemRequestDetails();
UriComponents uriComponents = UriComponentsBuilder.fromUriString(theQueryUrl).build();
uriComponents.getQueryParams().entrySet().forEach(nextEntry -> {
request.addParameter(nextEntry.getKey(), nextEntry.getValue().toArray(new String[0]));
});
return request;
}
@Nested
public class QuantitySearch {
IIdType myResourceId;
@Nested
public class Queries {
@Test
public void eq() {
withObservationWithValueQuantity(0.6);
assertNotFind("when gt", "/Observation?value-quantity=0.5||mmHg");
assertNotFind("when gt unitless", "/Observation?value-quantity=0.5");
// fixme we break the spec here.
// assertFind("when a little gt - default is approx", "/Observation?value-quantity=0.599");
// assertFind("when a little lt - default is approx", "/Observation?value-quantity=0.601");
// fixme we don't seem to support "units", only "code".
assertFind("when eq with units", "/Observation?value-quantity=0.6||mm[Hg]");
assertFind("when eq unitless", "/Observation?value-quantity=0.6");
assertNotFind("when lt", "/Observation?value-quantity=0.7||mmHg");
assertNotFind("when lt", "/Observation?value-quantity=0.7");
}
@Test
public void ne() {
withObservationWithValueQuantity(0.6);
assertFind("when gt", "/Observation?value-quantity=ne0.5");
assertNotFind("when eq", "/Observation?value-quantity=ne0.6");
assertFind("when lt", "/Observation?value-quantity=ne0.7");
}
@Test
public void ap() {
withObservationWithValueQuantity(0.6);
assertNotFind("when gt", "/Observation?value-quantity=ap0.5");
assertFind("when a little gt", "/Observation?value-quantity=ap0.58");
assertFind("when eq", "/Observation?value-quantity=ap0.6");
assertFind("when a little lt", "/Observation?value-quantity=ap0.62");
assertNotFind("when lt", "/Observation?value-quantity=ap0.7");
}
@Test
public void gt() {
withObservationWithValueQuantity(0.6);
assertFind("when gt", "/Observation?value-quantity=gt0.5");
assertNotFind("when eq", "/Observation?value-quantity=gt0.6");
assertNotFind("when lt", "/Observation?value-quantity=gt0.7");
}
@Test
public void ge() {
withObservationWithValueQuantity(0.6);
assertFind("when gt", "/Observation?value-quantity=ge0.5");
assertFind("when eq", "/Observation?value-quantity=ge0.6");
assertNotFind("when lt", "/Observation?value-quantity=ge0.7");
}
@Test
public void lt() {
withObservationWithValueQuantity(0.6);
assertNotFind("when gt", "/Observation?value-quantity=lt0.5");
assertNotFind("when eq", "/Observation?value-quantity=lt0.6");
assertFind("when lt", "/Observation?value-quantity=lt0.7");
}
@Test
public void le() {
withObservationWithValueQuantity(0.6);
assertNotFind("when gt", "/Observation?value-quantity=le0.5");
assertFind("when eq", "/Observation?value-quantity=le0.6");
assertFind("when lt", "/Observation?value-quantity=le0.7");
}
private void assertFind(String theMessage, String theUrl) {
List<String> resourceIds = myTestDaoSearch.searchForIds(theUrl);
assertThat(theMessage, resourceIds, hasItem(equalTo(myResourceId.getIdPart())));
}
private void assertNotFind(String theMessage, String theUrl) {
List<String> resourceIds = myTestDaoSearch.searchForIds(theUrl);
assertThat(theMessage, resourceIds, not(hasItem(equalTo(myResourceId.getIdPart()))));
}
}
private IIdType withObservationWithValueQuantity(double theValue) {
// IBase quantity = myDataBuilder.withElementOfType("Quantity",
// myDataBuilder.withPrimitiveAttribute("value", theValue),
// myDataBuilder.withPrimitiveAttribute("unit", "mmHg"),
// myDataBuilder.withPrimitiveAttribute("system", "http://unitsofmeasure.org"));
myResourceId = myDataBuilder.createObservation(myDataBuilder.withAttribute("valueQuantity",
myDataBuilder.withPrimitiveAttribute("value", theValue),
myDataBuilder.withPrimitiveAttribute("unit", "mmHg"),
myDataBuilder.withPrimitiveAttribute("system", "http://unitsofmeasure.org"),
myDataBuilder.withPrimitiveAttribute("code", "mm[Hg]")
));
return myResourceId;
}
@Nested
public class Sorting {
@Test
public void sortByNumeric() {
String idAlpha7 = withObservationWithValueQuantity(0.7).getIdPart();
String idAlpha2 = withObservationWithValueQuantity(0.2).getIdPart();
String idAlpha5 = withObservationWithValueQuantity(0.5).getIdPart();
List<String> allIds = myTestDaoSearch.searchForIds("/Observation?_sort=value-quantity");
assertThat(allIds, hasItems(idAlpha2, idAlpha5, idAlpha7));
}
}
}
}

View File

@ -6,11 +6,10 @@ import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc;
import ca.uhn.fhir.jpa.bulk.export.api.IBulkDataExportJobSchedulingHelper;
import ca.uhn.fhir.jpa.bulk.export.api.IBulkDataExportSvc;
import ca.uhn.fhir.jpa.config.TestDataBuilderConfig;
import ca.uhn.fhir.jpa.config.TestHibernateSearchAddInConfig;
import ca.uhn.fhir.jpa.config.TestR4Config;
import ca.uhn.fhir.jpa.dao.BaseJpaTest;
import ca.uhn.fhir.jpa.dao.DaoTestDataBuilder;
import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc;
import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
@ -50,7 +49,7 @@ import static org.hamcrest.Matchers.not;
@ExtendWith(SpringExtension.class)
@RequiresDocker
@ContextConfiguration(classes = {
TestR4Config.class, TestHibernateSearchAddInConfig.Elasticsearch.class, TestDataBuilderConfig.class
TestR4Config.class, TestHibernateSearchAddInConfig.Elasticsearch.class, DaoTestDataBuilder.Config.class
})
public class TokenAutocompleteElasticsearchIT extends BaseJpaTest{
public static final Coding erythrocyte_by_volume = new Coding("http://loinc.org", "789-8", "Erythrocytes [#/volume] in Blood by Automated count");

View File

@ -20,17 +20,13 @@ package ca.uhn.fhir.jpa.model.entity;
* #L%
*/
import javax.persistence.Column;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.MappedSuperclass;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextField;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import javax.persistence.Column;
import javax.persistence.MappedSuperclass;
@MappedSuperclass
public abstract class ResourceIndexedSearchParamBaseQuantity extends BaseResourceIndexedSearchParam {
@ -63,10 +59,6 @@ public abstract class ResourceIndexedSearchParamBaseQuantity extends BaseResourc
@Column(name = "HASH_IDENTITY", nullable = true)
private Long myHashIdentity;
@ManyToOne(optional = false, fetch = FetchType.LAZY, cascade = {})
@JoinColumn(name = "RES_ID", referencedColumnName = "RES_ID", nullable = false)
private ResourceTable myResource;
/**
* Constructor
*/
@ -166,15 +158,4 @@ public abstract class ResourceIndexedSearchParamBaseQuantity extends BaseResourc
return hash(thePartitionSettings, theRequestPartitionId, theResourceType, theParamName, theUnits);
}
@Override
public ResourceTable getResource() {
return myResource;
}
@Override
public BaseResourceIndexedSearchParam setResource(ResourceTable theResource) {
myResource = theResource;
setResourceType(theResource.getResourceType());
return this;
}
}

View File

@ -49,9 +49,8 @@ import java.util.Objects;
@Entity
@Table(name = "HFJ_SPIDX_NUMBER", indexes = {
// We used to have an index with name IDX_SP_NUMBER - Dont reuse
@Index(name = "IDX_SP_NUMBER_HASH_VAL", columnList = "HASH_IDENTITY,SP_VALUE"),
@Index(name = "IDX_SP_NUMBER_UPDATED", columnList = "SP_UPDATED"),
@Index(name = "IDX_SP_NUMBER_RESID", columnList = "RES_ID")
@Index(name = "IDX_SP_NUMBER_HASH_VAL_V2", columnList = "HASH_IDENTITY,SP_VALUE,RES_ID,PARTITION_ID"),
@Index(name = "IDX_SP_NUMBER_RESID_V2", columnList = "RES_ID, HASH_IDENTITY, SP_VALUE, PARTITION_ID")
})
public class ResourceIndexedSearchParamNumber extends BaseResourceIndexedSearchParam {
@ -72,7 +71,7 @@ public class ResourceIndexedSearchParamNumber extends BaseResourceIndexedSearchP
private Long myHashIdentity;
@ManyToOne(optional = false, fetch = FetchType.LAZY, cascade = {})
@JoinColumn(foreignKey = @ForeignKey(name = "FKCLTIHNC5TGPRJ9BHPT7XI5OTB"),
@JoinColumn(foreignKey = @ForeignKey(name = "FK_SP_NUMBER_RES"),
name = "RES_ID", referencedColumnName = "RES_ID", nullable = false)
private ResourceTable myResource;

View File

@ -20,41 +20,42 @@ package ca.uhn.fhir.jpa.model.entity;
* #L%
*/
import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.isBlank;
import java.math.BigDecimal;
import java.util.Objects;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.param.QuantityParam;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.ScaledNumberField;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.param.QuantityParam;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.ForeignKey;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import java.math.BigDecimal;
import java.util.Objects;
import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.isBlank;
//@formatter:off
@Embeddable
@Entity
@Table(name = "HFJ_SPIDX_QUANTITY", indexes = {
// We used to have an index named IDX_SP_QUANTITY - Dont reuse
@Index(name = "IDX_SP_QUANTITY_HASH", columnList = "HASH_IDENTITY,SP_VALUE"),
@Index(name = "IDX_SP_QUANTITY_HASH_UN", columnList = "HASH_IDENTITY_AND_UNITS,SP_VALUE"),
@Index(name = "IDX_SP_QUANTITY_HASH_SYSUN", columnList = "HASH_IDENTITY_SYS_UNITS,SP_VALUE"),
@Index(name = "IDX_SP_QUANTITY_UPDATED", columnList = "SP_UPDATED"),
@Index(name = "IDX_SP_QUANTITY_RESID", columnList = "RES_ID")
@Index(name = "IDX_SP_QUANTITY_HASH_V2", columnList = "HASH_IDENTITY,SP_VALUE,RES_ID,PARTITION_ID"),
@Index(name = "IDX_SP_QUANTITY_HASH_UN_V2", columnList = "HASH_IDENTITY_AND_UNITS,SP_VALUE,RES_ID,PARTITION_ID"),
@Index(name = "IDX_SP_QUANTITY_HASH_SYSUN_V2", columnList = "HASH_IDENTITY_SYS_UNITS,SP_VALUE,RES_ID,PARTITION_ID"),
@Index(name = "IDX_SP_QUANTITY_RESID_V2", columnList = "RES_ID,HASH_IDENTITY,HASH_IDENTITY_SYS_UNITS,HASH_IDENTITY_AND_UNITS,SP_VALUE,PARTITION_ID")
})
public class ResourceIndexedSearchParamQuantity extends ResourceIndexedSearchParamBaseQuantity {
@ -70,6 +71,11 @@ public class ResourceIndexedSearchParamQuantity extends ResourceIndexedSearchPar
@ScaledNumberField
public Double myValue;
@ManyToOne(optional = false, fetch = FetchType.LAZY, cascade = {})
@JoinColumn(foreignKey = @ForeignKey(name = "FK_SP_QUANTITY_RES"),
name = "RES_ID", referencedColumnName = "RES_ID", nullable = false)
private ResourceTable myResource;
public ResourceIndexedSearchParamQuantity() {
super();
}
@ -196,4 +202,15 @@ public class ResourceIndexedSearchParamQuantity extends ResourceIndexedSearchPar
return retval;
}
@Override
public ResourceTable getResource() {
return myResource;
}
@Override
public BaseResourceIndexedSearchParam setResource(ResourceTable theResource) {
myResource = theResource;
setResourceType(theResource.getResourceType());
return this;
}
}

View File

@ -20,43 +20,43 @@ package ca.uhn.fhir.jpa.model.entity;
* #L%
*/
import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.isBlank;
import java.math.BigDecimal;
import java.util.Objects;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.util.UcumServiceUtil;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.param.QuantityParam;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.fhir.ucum.Pair;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.ScaledNumberField;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.ForeignKey;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import java.math.BigDecimal;
import java.util.Objects;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.param.QuantityParam;
import ca.uhn.fhir.jpa.model.util.UcumServiceUtil;
import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.isBlank;
//@formatter:off
@Embeddable
@Entity
@Table(name = "HFJ_SPIDX_QUANTITY_NRML", indexes = {
@Index(name = "IDX_SP_QNTY_NRML_HASH", columnList = "HASH_IDENTITY,SP_VALUE"),
@Index(name = "IDX_SP_QNTY_NRML_HASH_UN", columnList = "HASH_IDENTITY_AND_UNITS,SP_VALUE"),
@Index(name = "IDX_SP_QNTY_NRML_HASH_SYSUN", columnList = "HASH_IDENTITY_SYS_UNITS,SP_VALUE"),
@Index(name = "IDX_SP_QNTY_NRML_UPDATED", columnList = "SP_UPDATED"),
@Index(name = "IDX_SP_QNTY_NRML_RESID", columnList = "RES_ID")
@Index(name = "IDX_SP_QNTY_NRML_HASH_V2", columnList = "HASH_IDENTITY,SP_VALUE,RES_ID,PARTITION_ID"),
@Index(name = "IDX_SP_QNTY_NRML_HASH_UN_V2", columnList = "HASH_IDENTITY_AND_UNITS,SP_VALUE,RES_ID,PARTITION_ID"),
@Index(name = "IDX_SP_QNTY_NRML_HASH_SYSUN_V2", columnList = "HASH_IDENTITY_SYS_UNITS,SP_VALUE,RES_ID,PARTITION_ID"),
@Index(name = "IDX_SP_QNTY_NRML_RESID_V2", columnList = "RES_ID,HASH_IDENTITY,HASH_IDENTITY_SYS_UNITS,HASH_IDENTITY_AND_UNITS,SP_VALUE,PARTITION_ID")
})
/**
* Support UCUM service
@ -79,6 +79,11 @@ public class ResourceIndexedSearchParamQuantityNormalized extends ResourceIndexe
@ScaledNumberField
public Double myValue;
@ManyToOne(optional = false, fetch = FetchType.LAZY, cascade = {})
@JoinColumn(foreignKey = @ForeignKey(name = "FK_SP_QUANTITYNM_RES"),
name = "RES_ID", referencedColumnName = "RES_ID", nullable = false)
private ResourceTable myResource;
public ResourceIndexedSearchParamQuantityNormalized() {
super();
}
@ -222,4 +227,16 @@ public class ResourceIndexedSearchParamQuantityNormalized extends ResourceIndexe
return retval;
}
@Override
public ResourceTable getResource() {
return myResource;
}
@Override
public BaseResourceIndexedSearchParam setResource(ResourceTable theResource) {
myResource = theResource;
setResourceType(theResource.getResourceType());
return this;
}
}

View File

@ -5,13 +5,25 @@ import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.partition.SystemRequestDetails;
import ca.uhn.fhir.test.utilities.ITestDataBuilder;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.SetMultimap;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
public class DaoTestDataBuilder implements ITestDataBuilder, AfterEachCallback {
private static final Logger ourLog = LoggerFactory.getLogger(DaoTestDataBuilder.class);
public class DaoTestDataBuilder implements ITestDataBuilder {
final FhirContext myFhirCtx;
final DaoRegistry myDaoRegistry;
SystemRequestDetails mySrd;
final SetMultimap<String, IIdType> myIds = HashMultimap.create();
public DaoTestDataBuilder(FhirContext theFhirCtx, DaoRegistry theDaoRegistry, SystemRequestDetails theSrd) {
myFhirCtx = theFhirCtx;
@ -24,7 +36,9 @@ public class DaoTestDataBuilder implements ITestDataBuilder {
//noinspection rawtypes
IFhirResourceDao dao = myDaoRegistry.getResourceDao(theResource.getClass());
//noinspection unchecked
return dao.create(theResource, mySrd).getId().toUnqualifiedVersionless();
IIdType id = dao.create(theResource, mySrd).getId().toUnqualifiedVersionless();
myIds.put(theResource.fhirType(), id);
return id;
}
@Override
@ -39,4 +53,34 @@ public class DaoTestDataBuilder implements ITestDataBuilder {
public FhirContext getFhirContext() {
return myFhirCtx;
}
/**
* Delete anything created
*/
public void cleanup() {
ourLog.info("cleanup {}", myIds);
myIds.keySet().forEach(nextType->{
IFhirResourceDao<?> dao = myDaoRegistry.getResourceDao(nextType);
myIds.get(nextType).forEach(dao::delete);
});
myIds.clear();
}
@Override
public void afterEach(ExtensionContext context) throws Exception {
cleanup();
}
@Configuration
public static class Config {
@Bean
DaoTestDataBuilder testDataBuilder(
@Autowired FhirContext myFhirContext,
@Autowired DaoRegistry myDaoRegistry
) {
return new DaoTestDataBuilder(myFhirContext, myDaoRegistry, new SystemRequestDetails());
}
}
}

View File

@ -185,6 +185,39 @@ public interface ITestDataBuilder {
};
}
default <T extends IBase> Consumer<T> withPrimitiveAttribute(String thePath, Object theValue) {
return t->{
FhirTerser terser = getFhirContext().newTerser();
terser.addElement(t, thePath, ""+theValue);
};
}
default <T extends IBase> Consumer<T> withAttribute(String thePath, Consumer<IBase>... theModifiers) {
return t->{
FhirTerser terser = getFhirContext().newTerser();
IBase element = terser.addElement(t, thePath);
applyElementModifiers(element, theModifiers);
};
}
/**
* Create an Element and apply modifiers
* @param theElementType the FHIR Element type to create
* @param theModifiers modifiers to apply after construction
* @return the Element
*/
default IBase withElementOfType(String theElementType, Consumer<IBase>... theModifiers) {
IBase element = getFhirContext().getElementDefinition(theElementType).newInstance();
applyElementModifiers(element, theModifiers);
return element;
}
default void applyElementModifiers(IBase element, Consumer<IBase>[] theModifiers) {
for (Consumer<IBase> nextModifier : theModifiers) {
nextModifier.accept(element);
}
}
default Consumer<IBaseResource> withObservationCode(@Nullable String theSystem, @Nullable String theCode) {
return withObservationCode(theSystem, theCode, null);
}
@ -252,5 +285,4 @@ public interface ITestDataBuilder {
activeChild.getMutator().addValue(theTarget, booleanType);
}
}