Sort by Bundle chained composition SPs (#5785)

Add support for sorting by special Bundle chained composition SPs
This commit is contained in:
Michael Buckley 2024-03-15 14:50:50 -04:00 committed by GitHub
parent 7344f01217
commit d6bc8f2e1b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 171 additions and 124 deletions

View File

@ -0,0 +1,4 @@
---
type: add
issue: 5784
title: "Add support to _sort for chained `composition` Bundle SearchParameters"

View File

@ -843,6 +843,11 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
RuntimeSearchParam param = null;
if (param == null) {
// do we have a composition param defined for the whole chain?
param = mySearchParamRegistry.getActiveSearchParam(myResourceName, theSort.getParamName());
}
/*
* If we have a sort like _sort=subject.name and we have an
* uplifted refchain for that combination we can do it more efficiently
@ -851,7 +856,7 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
* to "name" in this example) so that we know what datatype it is.
*/
String paramName = theSort.getParamName();
if (myStorageSettings.isIndexOnUpliftedRefchains()) {
if (param == null && myStorageSettings.isIndexOnUpliftedRefchains()) {
String[] chains = StringUtils.split(paramName, '.');
if (chains.length == 2) {

View File

@ -5,13 +5,10 @@ import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
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.jpa.test.BaseJpaR4Test;
import ca.uhn.fhir.jpa.util.SqlQuery;
import ca.uhn.fhir.parser.StrictErrorHandler;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.ReferenceParam;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.AuditEvent;
@ -20,7 +17,6 @@ import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.Composition;
import org.hl7.fhir.r4.model.Device;
import org.hl7.fhir.r4.model.DomainResource;
import org.hl7.fhir.r4.model.Encounter;
import org.hl7.fhir.r4.model.Enumerations;
import org.hl7.fhir.r4.model.IdType;
@ -41,14 +37,13 @@ import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.springframework.beans.factory.annotation.Autowired;
import java.io.IOException;
import java.sql.Date;
import java.util.ArrayList;
import java.util.List;
import static org.apache.commons.lang3.StringUtils.countMatches;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.hasSize;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.fail;
@ -117,7 +112,7 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
@Test
public void testShouldResolveATwoLinkChainWithStandAloneResourcesWithoutContainedResourceIndexing() throws Exception {
public void testShouldResolveATwoLinkChainWithStandAloneResourcesWithoutContainedResourceIndexing() {
// setup
IIdType oid1;
@ -141,7 +136,7 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
String url = "/Observation?subject.name=Smith";
// execute
List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(url);
List<String> oids = myTestDaoSearch.searchForIds(url);
// validate
assertEquals(1L, oids.size());
@ -149,7 +144,7 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
}
@Test
public void testShouldResolveATwoLinkChainWithStandAloneResources() throws Exception {
public void testShouldResolveATwoLinkChainWithStandAloneResources() {
// setup
myStorageSettings.setIndexOnContainedResources(true);
@ -175,7 +170,7 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
String url = "/Observation?subject.name=Smith";
// execute
List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(url);
List<String> oids = myTestDaoSearch.searchForIds(url);
// validate
assertEquals(1L, oids.size());
@ -183,7 +178,7 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
}
@Test
public void testShouldResolveATwoLinkChainWithStandAloneResources_CommonReference() throws Exception {
public void testShouldResolveATwoLinkChainWithStandAloneResources_CommonReference() {
// setup
myStorageSettings.setIndexOnContainedResources(true);
@ -218,7 +213,7 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
// execute
myCaptureQueriesListener.clear();
List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(url);
List<String> oids = myTestDaoSearch.searchForIds(url);
myCaptureQueriesListener.logSelectQueries();
// validate
@ -227,7 +222,7 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
}
@Test
public void testShouldResolveATwoLinkChainWithStandAloneResources_CompoundReference() throws Exception {
public void testShouldResolveATwoLinkChainWithStandAloneResources_CompoundReference() {
// setup
myStorageSettings.setIndexOnContainedResources(true);
@ -265,7 +260,7 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
// execute
myCaptureQueriesListener.clear();
List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(url, myAuditEventDao);
List<String> oids = myTestDaoSearch.searchForIds(url);
myCaptureQueriesListener.logSelectQueries();
// validate
@ -274,7 +269,7 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
}
@Test
public void testShouldResolveATwoLinkChainWithContainedResources_CompoundReference() throws Exception {
public void testShouldResolveATwoLinkChainWithContainedResources_CompoundReference() {
// setup
myStorageSettings.setIndexOnContainedResources(true);
@ -313,7 +308,7 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
// execute
myCaptureQueriesListener.clear();
List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(url, myAuditEventDao);
List<String> oids = myTestDaoSearch.searchForIds(url);
myCaptureQueriesListener.logSelectQueries();
// validate
@ -322,7 +317,7 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
}
@Test
public void testShouldResolveATwoLinkChainWithAContainedResource() throws Exception {
public void testShouldResolveATwoLinkChainWithAContainedResource() {
// setup
myStorageSettings.setIndexOnContainedResources(true);
@ -355,7 +350,7 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
String url = "/Observation?subject.name=Smith";
// execute
List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(url);
List<String> oids = myTestDaoSearch.searchForIds(url);
// validate
assertEquals(1L, oids.size());
@ -363,7 +358,7 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
}
@Test
public void testShouldNotResolveATwoLinkChainWithAContainedResourceWhenContainedResourceIndexingIsTurnedOff() throws Exception {
public void testShouldNotResolveATwoLinkChainWithAContainedResourceWhenContainedResourceIndexingIsTurnedOff() {
// setup
IIdType oid1;
@ -378,24 +373,24 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
obs.setValue(new StringType("Test"));
obs.getSubject().setReference("#pat");
oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
// Create a dummy record so that an unconstrained query doesn't pass the test due to returning the only record
// Create a dummy record so that an unconstrained query doesn't pass the test due to returning the only record
myObservationDao.create(new Observation(), mySrd);
}
String url = "/Observation?subject.name=Smith";
// execute
List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(url);
List<String> oids = myTestDaoSearch.searchForIds(url);
// validate
assertEquals(0L, oids.size());
}
@Test
@Disabled
public void testShouldResolveATwoLinkChainWithQualifiersWithAContainedResource() throws Exception {
@Disabled("Known limitation")
public void testShouldResolveATwoLinkChainWithQualifiersWithAContainedResource() {
// TODO: This test fails because of a known limitation in qualified searches over contained resources.
// Type information for intermediate resources in the chain is not being retained in the indexes.
// setup
@ -435,7 +430,7 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
String url = "/Observation?subject:Patient.name=Smith";
// execute
List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(url);
List<String> oids = myTestDaoSearch.searchForIds(url);
// validate
assertEquals(1L, oids.size());
@ -443,7 +438,7 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
}
@Test
public void testShouldResolveATwoLinkChainToAContainedReference() throws Exception {
public void testShouldResolveATwoLinkChainToAContainedReference() {
// Adding support for this case in SMILE-3151
// setup
@ -477,7 +472,7 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
String url = "/Observation?subject.organization=" + orgId.getValueAsString();
// execute
List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(url);
List<String> oids = myTestDaoSearch.searchForIds(url);
// validate
assertEquals(1L, oids.size());
@ -485,7 +480,7 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
}
@Test
public void testShouldResolveATwoLinkChainToAStandAloneReference() throws Exception {
public void testShouldResolveATwoLinkChainToAStandAloneReference() {
// Adding support for this case in SMILE-3151
// setup
@ -519,7 +514,7 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
String url = "/Observation?subject.organization=" + orgId.getValueAsString();
// execute
List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(url);
List<String> oids = myTestDaoSearch.searchForIds(url);
// validate
assertEquals(1L, oids.size());
@ -527,7 +522,7 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
}
@Test
public void testShouldResolveATwoLinkChainWithAContainedResource_CommonReference() throws Exception {
public void testShouldResolveATwoLinkChainWithAContainedResource_CommonReference() {
// setup
myStorageSettings.setIndexOnContainedResources(true);
@ -558,7 +553,7 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
// execute
myCaptureQueriesListener.clear();
List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(url);
List<String> oids = myTestDaoSearch.searchForIds(url);
myCaptureQueriesListener.logSelectQueries();
// validate
@ -567,7 +562,7 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
}
@Test
public void testShouldResolveAThreeLinkChainWhereAllResourcesStandAloneWithoutContainedResourceIndexing() throws Exception {
public void testShouldResolveAThreeLinkChainWhereAllResourcesStandAloneWithoutContainedResourceIndexing() {
// setup
IIdType oid1;
@ -611,7 +606,7 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
String url = "/Observation?subject.organization.name=HealthCo";
// execute
List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(url);
List<String> oids = myTestDaoSearch.searchForIds(url);
// validate
assertEquals(1L, oids.size());
@ -619,7 +614,7 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
}
@Test
public void testShouldResolveAThreeLinkChainWhereAllResourcesStandAlone() throws Exception {
public void testShouldResolveAThreeLinkChainWhereAllResourcesStandAlone() {
// setup
myStorageSettings.setIndexOnContainedResources(true);
@ -665,7 +660,7 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
String url = "/Observation?subject.organization.name=HealthCo";
// execute
List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(url);
List<String> oids = myTestDaoSearch.searchForIds(url);
// validate
assertEquals(1L, oids.size());
@ -673,7 +668,7 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
}
@Test
public void testShouldResolveAThreeLinkChainWithAContainedResourceAtTheEndOfTheChain() throws Exception {
public void testShouldResolveAThreeLinkChainWithAContainedResourceAtTheEndOfTheChain() {
// This is the case that is most relevant to SMILE-2899
// setup
@ -706,7 +701,7 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
String url = "/Observation?subject.organization.name=HealthCo";
// execute
List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(url);
List<String> oids = myTestDaoSearch.searchForIds(url);
// validate
assertEquals(1L, oids.size());
@ -714,7 +709,7 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
}
@Test
public void testShouldResolveAThreeLinkChainWithAContainedResourceAtTheEndOfTheChain_CommonReference() throws Exception {
public void testShouldResolveAThreeLinkChainWithAContainedResourceAtTheEndOfTheChain_CommonReference() {
// setup
myStorageSettings.setIndexOnContainedResources(true);
@ -750,7 +745,7 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
// execute
myCaptureQueriesListener.clear();
List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(url);
List<String> oids = myTestDaoSearch.searchForIds(url);
myCaptureQueriesListener.logSelectQueries();
// validate
@ -759,7 +754,7 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
}
@Test
public void testShouldResolveAThreeLinkChainWithAContainedResourceAtTheBeginningOfTheChain() throws Exception {
public void testShouldResolveAThreeLinkChainWithAContainedResourceAtTheBeginningOfTheChain() {
// Adding support for this case in SMILE-3151
// setup
@ -792,7 +787,7 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
String url = "/Observation?subject.organization.name=HealthCo";
// execute
List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(url);
List<String> oids = myTestDaoSearch.searchForIds(url);
// validate
assertEquals(1L, oids.size());
@ -800,7 +795,7 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
}
@Test
public void testShouldResolveAThreeLinkChainWithAContainedResourceAtTheBeginningOfTheChain_CommonReference() throws Exception {
public void testShouldResolveAThreeLinkChainWithAContainedResourceAtTheBeginningOfTheChain_CommonReference() {
// setup
myStorageSettings.setIndexOnContainedResources(true);
@ -835,7 +830,7 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
// execute
myCaptureQueriesListener.clear();
List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(url);
List<String> oids = myTestDaoSearch.searchForIds(url);
myCaptureQueriesListener.logSelectQueries();
// validate
@ -844,7 +839,7 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
}
@Test
public void testShouldNotResolveAThreeLinkChainWithAllContainedResourcesWhenRecursiveContainedIndexesAreDisabled() throws Exception {
public void testShouldNotResolveAThreeLinkChainWithAllContainedResourcesWhenRecursiveContainedIndexesAreDisabled() {
// setup
myStorageSettings.setIndexOnContainedResources(true);
@ -867,23 +862,23 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
obs.getCode().setText("Observation 1");
obs.getSubject().setReference("#pat");
oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
// Create a dummy record so that an unconstrained query doesn't pass the test due to returning the only record
// Create a dummy record so that an unconstrained query doesn't pass the test due to returning the only record
myObservationDao.create(new Observation(), mySrd);
}
String url = "/Observation?subject.organization.name=HealthCo";
// execute
List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(url);
List<String> oids = myTestDaoSearch.searchForIds(url);
// validate
assertEquals(0L, oids.size());
}
@Test
public void testShouldResolveAThreeLinkChainWithAllContainedResources() throws Exception {
public void testShouldResolveAThreeLinkChainWithAllContainedResources() {
// setup
myStorageSettings.setIndexOnContainedResources(true);
@ -918,7 +913,7 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
// execute
myCaptureQueriesListener.clear();
List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(url);
List<String> oids = myTestDaoSearch.searchForIds(url);
myCaptureQueriesListener.logSelectQueries();
// validate
@ -927,7 +922,7 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
}
@Test
public void testShouldResolveAThreeLinkChainWithQualifiersWhereAllResourcesStandAlone() throws Exception {
public void testShouldResolveAThreeLinkChainWithQualifiersWhereAllResourcesStandAlone() {
// setup
myStorageSettings.setIndexOnContainedResources(true);
@ -969,7 +964,7 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
String url = "/Observation?subject:Patient.organization:Organization.name=HealthCo";
// execute
List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(url);
List<String> oids = myTestDaoSearch.searchForIds(url);
// validate
assertEquals(1L, oids.size());
@ -977,7 +972,7 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
}
@Test
public void testShouldResolveAThreeLinkChainWithQualifiersWithAContainedResourceAtTheEndOfTheChain() throws Exception {
public void testShouldResolveAThreeLinkChainWithQualifiersWithAContainedResourceAtTheEndOfTheChain() {
// This is the case that is most relevant to SMILE-2899
// setup
@ -1025,7 +1020,7 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
String url = "/Observation?subject:Patient.organization:Organization.name=HealthCo";
// execute
List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(url);
List<String> oids = myTestDaoSearch.searchForIds(url);
// validate
assertEquals(1L, oids.size());
@ -1033,7 +1028,7 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
}
@Test
public void testShouldResolveAThreeLinkChainWithQualifiersWithAContainedResourceAtTheBeginning() throws Exception {
public void testShouldResolveAThreeLinkChainWithQualifiersWithAContainedResourceAtTheBeginning() {
// Adding support for this case in SMILE-3151
// setup
@ -1078,7 +1073,7 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
// execute
myCaptureQueriesListener.clear();
List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(url);
List<String> oids = myTestDaoSearch.searchForIds(url);
myCaptureQueriesListener.logSelectQueries();
// validate
@ -1087,8 +1082,8 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
}
@Test
@Disabled
public void testShouldResolveAThreeLinkChainWithQualifiersWithAContainedResourceAtTheBeginning_NotDistinctSourcePaths() throws Exception {
@Disabled("Known limitation")
public void testShouldResolveAThreeLinkChainWithQualifiersWithAContainedResourceAtTheBeginning_NotDistinctSourcePaths() {
// TODO: This test fails because of a known limitation in qualified searches over contained resources.
// Type information for intermediate resources in the chain is not being retained in the indexes.
@ -1136,7 +1131,7 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
// execute
myCaptureQueriesListener.clear();
List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(url);
List<String> oids = myTestDaoSearch.searchForIds(url);
myCaptureQueriesListener.logSelectQueries();
// validate
@ -1145,8 +1140,8 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
}
@Test
@Disabled
public void testShouldResolveAThreeLinkChainWithQualifiersWithAllContainedResources() throws Exception {
@Disabled("Known limitation")
public void testShouldResolveAThreeLinkChainWithQualifiersWithAllContainedResources() {
// TODO: This test fails because of a known limitation in qualified searches over contained resources.
// Type information for intermediate resources in the chain is not being retained in the indexes.
@ -1198,7 +1193,7 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
// execute
myCaptureQueriesListener.clear();
List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(url);
List<String> oids = myTestDaoSearch.searchForIds(url);
myCaptureQueriesListener.logSelectQueries();
// validate
@ -1207,7 +1202,7 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
}
@Test
public void testShouldResolveAFourLinkChainWhereAllResourcesStandAlone() throws Exception {
public void testShouldResolveAFourLinkChainWhereAllResourcesStandAlone() {
// setup
myStorageSettings.setIndexOnContainedResources(true);
@ -1244,7 +1239,7 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
String url = "/Observation?subject.organization.partof.name=HealthCo";
// execute
List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(url);
List<String> oids = myTestDaoSearch.searchForIds(url);
// validate
assertEquals(1L, oids.size());
@ -1252,7 +1247,7 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
}
@Test
public void testShouldResolveAFourLinkChainWhereTheLastReferenceIsContained() throws Exception {
public void testShouldResolveAFourLinkChainWhereTheLastReferenceIsContained() {
// setup
myStorageSettings.setIndexOnContainedResources(true);
@ -1289,7 +1284,7 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
String url = "/Observation?subject.organization.partof.name=HealthCo";
// execute
List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(url);
List<String> oids = myTestDaoSearch.searchForIds(url);
// validate
assertEquals(1L, oids.size());
@ -1297,7 +1292,7 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
}
@Test
public void testShouldResolveAFourLinkChainWhereTheLastTwoReferencesAreContained() throws Exception {
public void testShouldResolveAFourLinkChainWhereTheLastTwoReferencesAreContained() {
// setup
myStorageSettings.setIndexOnContainedResources(true);
@ -1334,7 +1329,7 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
String url = "/Observation?subject.organization.partof.name=HealthCo";
// execute
List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(url);
List<String> oids = myTestDaoSearch.searchForIds(url);
// validate
assertEquals(1L, oids.size());
@ -1342,7 +1337,7 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
}
@Test
public void testShouldResolveAFourLinkChainWithAContainedResourceInTheMiddle() throws Exception {
public void testShouldResolveAFourLinkChainWithAContainedResourceInTheMiddle() {
// setup
myStorageSettings.setIndexOnContainedResources(true);
@ -1384,7 +1379,7 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
// execute
myCaptureQueriesListener.clear();
List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(url);
List<String> oids = myTestDaoSearch.searchForIds(url);
myCaptureQueriesListener.logSelectQueries();
// validate
@ -1393,7 +1388,7 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
}
@Test
public void testShouldResolveAFourLinkChainWhereTheFirstTwoReferencesAreContained() throws Exception {
public void testShouldResolveAFourLinkChainWhereTheFirstTwoReferencesAreContained() {
// setup
myStorageSettings.setIndexOnContainedResources(true);
@ -1431,7 +1426,7 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
String url = "/Observation?subject.organization.partof.name=HealthCo";
// execute
List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(url);
List<String> oids = myTestDaoSearch.searchForIds(url);
// validate
assertEquals(1L, oids.size());
@ -1439,7 +1434,7 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
}
@Test
public void testShouldResolveAFourLinkChainWhereTheFirstReferenceAndTheLastReferenceAreContained() throws Exception {
public void testShouldResolveAFourLinkChainWhereTheFirstReferenceAndTheLastReferenceAreContained() {
// setup
myStorageSettings.setIndexOnContainedResources(true);
@ -1476,7 +1471,7 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
String url = "/Observation?subject.organization.partof.name=HealthCo";
// execute
List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(url);
List<String> oids = myTestDaoSearch.searchForIds(url);
// validate
assertEquals(1L, oids.size());
@ -1484,7 +1479,7 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
}
@Test
public void testShouldResolveAFourLinkChainWhereAllReferencesAreContained() throws Exception {
public void testShouldResolveAFourLinkChainWhereAllReferencesAreContained() {
// setup
myStorageSettings.setIndexOnContainedResources(true);
@ -1524,7 +1519,7 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
// execute
myCaptureQueriesListener.clear();
List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(url);
List<String> oids = myTestDaoSearch.searchForIds(url);
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
// validate
@ -1533,7 +1528,7 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
}
@Test
public void testShouldThrowAnExceptionForAFiveLinkChain() throws Exception {
public void testShouldThrowAnExceptionForAFiveLinkChain() {
// setup
myStorageSettings.setIndexOnContainedResources(true);
@ -1543,7 +1538,7 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
try {
// execute
searchAndReturnUnqualifiedVersionlessIdValues(url);
myTestDaoSearch.searchForIds(url);
fail("Expected an exception to be thrown");
} catch (InvalidRequestException e) {
assertEquals(Msg.code(2007) + "The search chain subject.organization.partof.partof.name is too long. Only chains up to three references are supported.", e.getMessage());
@ -1551,7 +1546,7 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
}
@Test
public void testQueryStructure() throws Exception {
public void testQueryStructure() {
// With indexing of contained resources turned off, we should not see UNION clauses in the query
countUnionStatementsInGeneratedQuery("/Observation?patient.name=Smith", 0);
@ -1584,16 +1579,19 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
@ParameterizedTest
@CsvSource({
// search url expected count
"/Bundle?composition.patient.identifier=system|value-1&composition.patient.birthdate=1980-01-01, 1", // correct identifier, correct birthdate
"/Bundle?composition.patient.birthdate=1980-01-01&composition.patient.identifier=system|value-1, 1", // correct birthdate, correct identifier
"/Bundle?composition.patient.identifier=system|value-1&composition.patient.birthdate=2000-01-01, 0", // correct identifier, incorrect birthdate
"/Bundle?composition.patient.birthdate=2000-01-01&composition.patient.identifier=system|value-1, 0", // incorrect birthdate, correct identifier
"/Bundle?composition.patient.identifier=system|value-2&composition.patient.birthdate=1980-01-01, 0", // incorrect identifier, correct birthdate
"/Bundle?composition.patient.birthdate=1980-01-01&composition.patient.identifier=system|value-2, 0", // correct birthdate, incorrect identifier
"/Bundle?composition.patient.identifier=system|value-2&composition.patient.birthdate=2000-01-01, 0", // incorrect identifier, incorrect birthdate
"/Bundle?composition.patient.birthdate=2000-01-01&composition.patient.identifier=system|value-2, 0", // incorrect birthdate, incorrect identifier
"/Bundle?composition.patient.identifier=system|value-1&composition.patient.birthdate=1980-01-01, 1, correct identifier correct birthdate",
"/Bundle?composition.patient.birthdate=1980-01-01&composition.patient.identifier=system|value-1, 1, correct birthdate correct identifier",
"/Bundle?composition.patient.identifier=system|value-1&composition.patient.birthdate=2000-01-01, 0, correct identifier incorrect birthdate",
"/Bundle?composition.patient.birthdate=2000-01-01&composition.patient.identifier=system|value-1, 0, incorrect birthdate correct identifier",
"/Bundle?composition.patient.identifier=system|value-2&composition.patient.birthdate=1980-01-01, 0, incorrect identifier correct birthdate",
"/Bundle?composition.patient.birthdate=1980-01-01&composition.patient.identifier=system|value-2, 0, correct birthdate incorrect identifier",
"/Bundle?composition.patient.identifier=system|value-2&composition.patient.birthdate=2000-01-01, 0, incorrect identifier incorrect birthdate",
"/Bundle?composition.patient.birthdate=2000-01-01&composition.patient.identifier=system|value-2, 0, incorrect birthdate incorrect identifier",
// try sort by composition sp
"/Bundle?composition.patient.identifier=system|value-1&_sort=composition.patient.birthdate, 1, correct identifier sort by birthdate",
})
public void testMultipleChainedBundleCompositionSearchParameters(String theSearchUrl, int theExpectedCount) {
public void testMultipleChainedBundleCompositionSearchParameters(String theSearchUrl, int theExpectedCount, String theMessage) {
createSearchParameter("bundle-composition-patient-birthdate",
"composition.patient.birthdate",
"Bundle",
@ -1610,8 +1608,8 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
createDocumentBundleWithPatientDetails("1980-01-01", "system", "value-1");
SearchParameterMap params = myMatchUrlService.getResourceSearch(theSearchUrl).getSearchParameterMap().setLoadSynchronous(true);
assertSearchReturns(myBundleDao, params, theExpectedCount);
List<String> ids = myTestDaoSearch.searchForIds(theSearchUrl);
assertThat(theMessage, ids, hasSize(theExpectedCount));
}
private void createSearchParameter(String theId, String theCode, String theBase, String theExpression, Enumerations.SearchParamType theType) {
@ -1651,9 +1649,9 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
assertEquals(theExpectedCount, theDao.search(theSearchParams, mySrd).size());
}
private void countUnionStatementsInGeneratedQuery(String theUrl, int theExpectedNumberOfUnions) throws IOException {
private void countUnionStatementsInGeneratedQuery(String theUrl, int theExpectedNumberOfUnions) {
myCaptureQueriesListener.clear();
searchAndReturnUnqualifiedVersionlessIdValues(theUrl);
myTestDaoSearch.searchForIds(theUrl);
List<SqlQuery> selectQueries = myCaptureQueriesListener.getSelectQueriesForCurrentThread();
assertEquals(1, selectQueries.size());
@ -1661,18 +1659,4 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
assertEquals(theExpectedNumberOfUnions, countMatches(sqlQuery, "union"), sqlQuery);
}
private List<String> searchAndReturnUnqualifiedVersionlessIdValues(String theUrl) throws IOException {
return searchAndReturnUnqualifiedVersionlessIdValues(theUrl, myObservationDao);
}
private List<String> searchAndReturnUnqualifiedVersionlessIdValues(String theUrl, IFhirResourceDao<? extends DomainResource> theObservationDao) {
List<String> ids = new ArrayList<>();
ResourceSearch search = myMatchUrlService.getResourceSearch(theUrl);
SearchParameterMap map = search.getSearchParameterMap();
map.setLoadSynchronous(true);
IBundleProvider result = theObservationDao.search(map);
return result.getAllResourceIds();
}
}

View File

@ -3,6 +3,7 @@ package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.test.BaseJpaR4Test;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.SortOrderEnum;
import ca.uhn.fhir.rest.api.SortSpec;
@ -10,13 +11,18 @@ import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.DateParam;
import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.param.ParamPrefixEnum;
import ca.uhn.fhir.rest.param.ReferenceParam;
import ca.uhn.fhir.rest.param.TokenParam;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.Composition;
import org.hl7.fhir.r4.model.DateTimeType;
import org.hl7.fhir.r4.model.Enumerations;
import org.hl7.fhir.r4.model.Enumerations.AdministrativeGender;
import org.hl7.fhir.r4.model.Observation;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.SearchParameter;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
@ -28,7 +34,7 @@ import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.junit.jupiter.api.Assertions.assertEquals;
@SuppressWarnings({"unchecked", "deprecation"})
@SuppressWarnings({"deprecation"})
public class FhirResourceDaoR4SortTest extends BaseJpaR4Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4SortTest.class);
@ -51,7 +57,7 @@ public class FhirResourceDaoR4SortTest extends BaseJpaR4Test {
}
@Test
public void testSortOnId() throws Exception {
public void testSortOnId() {
// Numeric ID
Patient p01 = new Patient();
p01.setActive(true);
@ -147,7 +153,7 @@ public class FhirResourceDaoR4SortTest extends BaseJpaR4Test {
}
@Test
public void testSortOnSearchParameterWhereAllResourcesHaveAValue() throws Exception {
public void testSortOnSearchParameterWhereAllResourcesHaveAValue() {
Patient pBA = new Patient();
pBA.setId("BA");
pBA.setActive(true);
@ -348,20 +354,63 @@ public class FhirResourceDaoR4SortTest extends BaseJpaR4Test {
SearchParameterMap map;
List<String> ids;
runInTransaction(() -> {
ourLog.info("Dates:\n * {}", myResourceIndexedSearchParamDateDao.findAll().stream().map(t -> t.toString()).collect(Collectors.joining("\n * ")));
});
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Observation.SP_SUBJECT, new ReferenceParam("Patient", "identifier", "PCA|PCA"));
map.setSort(new SortSpec("date").setOrder(SortOrderEnum.DESC));
myCaptureQueriesListener.clear();
ids = toUnqualifiedVersionlessIdValues(myObservationDao.search(map));
ourLog.info("IDS: {}", ids);
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertThat(ids.toString(), ids, contains("Observation/OBS2", "Observation/OBS1"));
runInTransaction(() -> ourLog.info("Dates:\n * {}", myResourceIndexedSearchParamDateDao.findAll().stream().map(t -> t.toString()).collect(Collectors.joining("\n * "))));
myTestDaoSearch.assertSearchFinds(
"chained search",
"Observation?subject.identifier=PCA|PCA&_sort=-date",
"OBS2", "OBS1"
);
}
/**
* Define a composition SP for document Bundles, and sort by it.
* The chain is referencing the Bundle contents.
* @see https://smilecdr.com/docs/fhir_storage_relational/chained_searches_and_sorts.html#document-and-message-search-parameters
*/
@Test
void testSortByCompositionSP() {
// given
SearchParameter searchParameter = new SearchParameter();
searchParameter.setId("bundle-composition-patient-birthdate");
searchParameter.setCode("composition.patient.birthdate");
searchParameter.setName("composition.patient.birthdate");
searchParameter.setUrl("http://example.org/SearchParameter/bundle-composition-patient-birthdate");
searchParameter.setStatus(Enumerations.PublicationStatus.ACTIVE);
searchParameter.addBase("Bundle");
searchParameter.setType(Enumerations.SearchParamType.DATE);
searchParameter.setExpression("Bundle.entry.resource.ofType(Patient).birthDate");
doUpdateResource(searchParameter);
mySearchParamRegistry.forceRefresh();
Patient pat1 = buildResource("Patient", withId("pat1"), withBirthdate("2001-03-17"));
doUpdateResource(pat1);
Bundle pat1Bundle = buildCompositionBundle(pat1);
String pat1BundleId = doCreateResource(pat1Bundle).getIdPart();
Patient pat2 = buildResource("Patient", withId("pat2"), withBirthdate("2000-01-01"));
doUpdateResource(pat2);
Bundle pat2Bundle = buildCompositionBundle(pat2);
String pat2BundleId = doCreateResource(pat2Bundle).getIdPart();
// then
myTestDaoSearch.assertSearchFinds("sort by contained date",
"Bundle?_sort=composition.patient.birthdate", List.of(pat2BundleId, pat1BundleId));
myTestDaoSearch.assertSearchFinds("reverse sort by contained date",
"Bundle?_sort=-composition.patient.birthdate", List.of(pat1BundleId, pat2BundleId));
}
private static Bundle buildCompositionBundle(Patient pat11) {
Bundle bundle = new Bundle();
bundle.setType(Bundle.BundleType.DOCUMENT);
Composition composition = new Composition();
composition.setType(new CodeableConcept().addCoding(new Coding().setCode("code").setSystem("http://example.org")));
bundle.addEntry().setResource(composition);
composition.getSubject().setReference(pat11.getIdElement().getValue());
bundle.addEntry().setResource(pat11);
return bundle;
}
}

View File

@ -42,6 +42,7 @@ import ca.uhn.fhir.jpa.binary.provider.BinaryAccessProvider;
import ca.uhn.fhir.jpa.bulk.export.api.IBulkDataExportJobSchedulingHelper;
import ca.uhn.fhir.jpa.dao.GZipUtil;
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
import ca.uhn.fhir.jpa.dao.TestDaoSearch;
import ca.uhn.fhir.jpa.dao.data.IBatch2JobInstanceRepository;
import ca.uhn.fhir.jpa.dao.data.IBatch2WorkChunkRepository;
import ca.uhn.fhir.jpa.dao.data.IForcedIdDao;
@ -552,6 +553,8 @@ public abstract class BaseJpaR4Test extends BaseJpaTest implements ITestDataBuil
private IInterceptorService myInterceptorService;
@Autowired(required = false)
private MdmStorageInterceptor myMdmStorageInterceptor;
@Autowired
protected TestDaoSearch myTestDaoSearch;
@RegisterExtension
private final PreventDanglingInterceptorsExtension myPreventDanglingInterceptorsExtension = new PreventDanglingInterceptorsExtension(()-> myInterceptorRegistry);

View File

@ -28,6 +28,7 @@ import ca.uhn.fhir.jpa.binstore.MemoryBinaryStorageSvcImpl;
import ca.uhn.fhir.jpa.config.PackageLoaderConfig;
import ca.uhn.fhir.jpa.config.r4.JpaR4Config;
import ca.uhn.fhir.jpa.config.util.HapiEntityManagerFactoryUtil;
import ca.uhn.fhir.jpa.dao.TestDaoSearch;
import ca.uhn.fhir.jpa.model.dialect.HapiFhirH2Dialect;
import ca.uhn.fhir.jpa.searchparam.config.NicknameServiceConfig;
import ca.uhn.fhir.jpa.util.CircularQueueCaptureQueriesListener;
@ -78,7 +79,8 @@ import static org.junit.jupiter.api.Assertions.fail;
TestHSearchAddInConfig.DefaultLuceneHeap.class,
JpaBatch2Config.class,
Batch2JobsConfig.class,
NicknameServiceConfig.class
NicknameServiceConfig.class,
TestDaoSearch.Config.class
})
public class TestR4Config {