Searching with more than one chained Bundle SearchParameter returns incorrect results (#5614)
* Failing test * fix * changelog
This commit is contained in:
parent
47867fc628
commit
4b1ac778cc
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
type: fix
|
||||||
|
issue: 5623
|
||||||
|
title: "Previously, searches that used more than one chained `Bundle` `SearchParameter` (i.e. `Composition`) were only
|
||||||
|
adding one condition to the underlying SQL query which resulted in incorrect search results. This has been fixed."
|
|
@ -2466,7 +2466,7 @@ public class QueryStack {
|
||||||
theRequestPartitionId,
|
theRequestPartitionId,
|
||||||
andPredicates,
|
andPredicates,
|
||||||
nextAnd)) {
|
nextAnd)) {
|
||||||
break;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
EmbeddedChainedSearchModeEnum embeddedChainedSearchModeEnum =
|
EmbeddedChainedSearchModeEnum embeddedChainedSearchModeEnum =
|
||||||
|
|
|
@ -11,14 +11,18 @@ import ca.uhn.fhir.jpa.test.BaseJpaR4Test;
|
||||||
import ca.uhn.fhir.jpa.util.SqlQuery;
|
import ca.uhn.fhir.jpa.util.SqlQuery;
|
||||||
import ca.uhn.fhir.parser.StrictErrorHandler;
|
import ca.uhn.fhir.parser.StrictErrorHandler;
|
||||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||||
|
import ca.uhn.fhir.rest.param.ReferenceParam;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
import org.hl7.fhir.r4.model.AuditEvent;
|
import org.hl7.fhir.r4.model.AuditEvent;
|
||||||
import org.hl7.fhir.r4.model.Bundle;
|
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.Coding;
|
||||||
|
import org.hl7.fhir.r4.model.Composition;
|
||||||
import org.hl7.fhir.r4.model.Device;
|
import org.hl7.fhir.r4.model.Device;
|
||||||
import org.hl7.fhir.r4.model.DomainResource;
|
import org.hl7.fhir.r4.model.DomainResource;
|
||||||
import org.hl7.fhir.r4.model.Encounter;
|
import org.hl7.fhir.r4.model.Encounter;
|
||||||
|
import org.hl7.fhir.r4.model.Enumerations;
|
||||||
import org.hl7.fhir.r4.model.IdType;
|
import org.hl7.fhir.r4.model.IdType;
|
||||||
import org.hl7.fhir.r4.model.Location;
|
import org.hl7.fhir.r4.model.Location;
|
||||||
import org.hl7.fhir.r4.model.MessageHeader;
|
import org.hl7.fhir.r4.model.MessageHeader;
|
||||||
|
@ -27,22 +31,26 @@ import org.hl7.fhir.r4.model.Organization;
|
||||||
import org.hl7.fhir.r4.model.Patient;
|
import org.hl7.fhir.r4.model.Patient;
|
||||||
import org.hl7.fhir.r4.model.Quantity;
|
import org.hl7.fhir.r4.model.Quantity;
|
||||||
import org.hl7.fhir.r4.model.Reference;
|
import org.hl7.fhir.r4.model.Reference;
|
||||||
|
import org.hl7.fhir.r4.model.SearchParameter;
|
||||||
import org.hl7.fhir.r4.model.StringType;
|
import org.hl7.fhir.r4.model.StringType;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Disabled;
|
import org.junit.jupiter.api.Disabled;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.CsvSource;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.sql.Date;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static org.apache.commons.lang3.StringUtils.countMatches;
|
import static org.apache.commons.lang3.StringUtils.countMatches;
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
import static org.hamcrest.Matchers.contains;
|
import static org.hamcrest.Matchers.contains;
|
||||||
import static org.hamcrest.Matchers.in;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
import static org.junit.jupiter.api.Assertions.fail;
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
|
||||||
|
|
||||||
|
@ -1573,6 +1581,76 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
|
||||||
countUnionStatementsInGeneratedQuery("/Observation?subject:Location.name=Smith", 1);
|
countUnionStatementsInGeneratedQuery("/Observation?subject:Location.name=Smith", 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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
|
||||||
|
})
|
||||||
|
public void testMultipleChainedBundleCompositionSearchParameters(String theSearchUrl, int theExpectedCount) {
|
||||||
|
createSearchParameter("bundle-composition-patient-birthdate",
|
||||||
|
"composition.patient.birthdate",
|
||||||
|
"Bundle",
|
||||||
|
"Bundle.entry.resource.ofType(Patient).birthDate",
|
||||||
|
Enumerations.SearchParamType.DATE
|
||||||
|
);
|
||||||
|
|
||||||
|
createSearchParameter("bundle-composition-patient-identifier",
|
||||||
|
"composition.patient.identifier",
|
||||||
|
"Bundle",
|
||||||
|
"Bundle.entry.resource.ofType(Patient).identifier",
|
||||||
|
Enumerations.SearchParamType.TOKEN
|
||||||
|
);
|
||||||
|
|
||||||
|
createDocumentBundleWithPatientDetails("1980-01-01", "system", "value-1");
|
||||||
|
|
||||||
|
SearchParameterMap params = myMatchUrlService.getResourceSearch(theSearchUrl).getSearchParameterMap().setLoadSynchronous(true);
|
||||||
|
assertSearchReturns(myBundleDao, params, theExpectedCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createSearchParameter(String theId, String theCode, String theBase, String theExpression, Enumerations.SearchParamType theType) {
|
||||||
|
SearchParameter searchParameter = new SearchParameter();
|
||||||
|
searchParameter.setId(theId);
|
||||||
|
searchParameter.setCode(theCode);
|
||||||
|
searchParameter.setName(theCode);
|
||||||
|
searchParameter.setUrl("http://example.org/SearchParameter/" + theId);
|
||||||
|
searchParameter.setStatus(Enumerations.PublicationStatus.ACTIVE);
|
||||||
|
searchParameter.addBase(theBase);
|
||||||
|
searchParameter.setType(theType);
|
||||||
|
searchParameter.setExpression(theExpression);
|
||||||
|
searchParameter = (SearchParameter) mySearchParameterDao.update(searchParameter, mySrd).getResource();
|
||||||
|
mySearchParamRegistry.forceRefresh();
|
||||||
|
assertNotNull(mySearchParamRegistry.getActiveSearchParam(theBase, searchParameter.getName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createDocumentBundleWithPatientDetails(String theBirthDate, String theIdentifierSystem, String theIdentifierValue) {
|
||||||
|
Patient patient = new Patient();
|
||||||
|
patient.setBirthDate(Date.valueOf(theBirthDate));
|
||||||
|
patient.addIdentifier().setSystem(theIdentifierSystem).setValue(theIdentifierValue);
|
||||||
|
patient = (Patient) myPatientDao.create(patient, mySrd).getResource();
|
||||||
|
assertSearchReturns(myPatientDao, SearchParameterMap.newSynchronous(), 1);
|
||||||
|
|
||||||
|
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(patient.getIdElement().getValue());
|
||||||
|
bundle.addEntry().setResource(patient);
|
||||||
|
myBundleDao.create(bundle, mySrd);
|
||||||
|
assertSearchReturns(myBundleDao, SearchParameterMap.newSynchronous(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertSearchReturns(IFhirResourceDao<?> theDao, SearchParameterMap theSearchParams, int theExpectedCount){
|
||||||
|
assertEquals(theExpectedCount, theDao.search(theSearchParams, mySrd).size());
|
||||||
|
}
|
||||||
|
|
||||||
private void countUnionStatementsInGeneratedQuery(String theUrl, int theExpectedNumberOfUnions) throws IOException {
|
private void countUnionStatementsInGeneratedQuery(String theUrl, int theExpectedNumberOfUnions) throws IOException {
|
||||||
myCaptureQueriesListener.clear();
|
myCaptureQueriesListener.clear();
|
||||||
searchAndReturnUnqualifiedVersionlessIdValues(theUrl);
|
searchAndReturnUnqualifiedVersionlessIdValues(theUrl);
|
||||||
|
|
Loading…
Reference in New Issue