Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
type: fix
issue: 5623
backport: 6.10.5
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."
Original file line number Diff line number Diff line change
Expand Up @@ -2466,7 +2466,7 @@ private Condition createPredicateSearchParameter(
theRequestPartitionId,
andPredicates,
nextAnd)) {
break;
continue;
}

EmbeddedChainedSearchModeEnum embeddedChainedSearchModeEnum =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,18 @@
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;
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.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;
import org.hl7.fhir.r4.model.Location;
import org.hl7.fhir.r4.model.MessageHeader;
Expand All @@ -27,22 +31,26 @@
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Quantity;
import org.hl7.fhir.r4.model.Reference;
import org.hl7.fhir.r4.model.SearchParameter;
import org.hl7.fhir.r4.model.StringType;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
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 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.in;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.fail;


Expand Down Expand Up @@ -1573,6 +1581,76 @@ public void testQueryStructure() throws Exception {
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 {
myCaptureQueriesListener.clear();
searchAndReturnUnqualifiedVersionlessIdValues(theUrl);
Expand Down