Merge remote-tracking branch 'refs/remotes/origin/master' into mb_20240925_partition_search

This commit is contained in:
Michael Buckley 2024-09-28 18:39:07 -04:00
commit d05079da22
21 changed files with 360 additions and 139 deletions

View File

@ -0,0 +1,5 @@
---
type: change
issue: 6261
title: "Upgrading to Jakarta had caused a problem with the version of java-simple-mail that was in use. This has been updated to be conformant
with the Jakarta Mail APIs. Thanks to Thomas Papke(@thopap) for the contribution!"

View File

@ -0,0 +1,5 @@
---
type: fix
issue: 6290
title: "Previously, a specific migration task was using the `TRIM()` function, which does not exist in MSSQL 2012. This was causing migrations targeting MSSQL 2012 to fail.
This has been corrected and replaced with usage of a combination of LTRIM() and RTRIM(). Thanks to Primož Delopst at Better for the contribution!"

View File

@ -0,0 +1,7 @@
---
type: fix
issue: 6317
title: "Previously, defining a unique combo Search Parameter with the DateTime component and submitting multiple
resources with the same dateTime element (e.g. Observation.effectiveDateTime) resulted in duplicate resource creation.
This has been fixed."

View File

@ -1972,7 +1972,7 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
* The loop allows us to create multiple combo index joins if there * The loop allows us to create multiple combo index joins if there
* are multiple AND expressions for the related parameters. * are multiple AND expressions for the related parameters.
*/ */
while (validateParamValuesAreValidForComboParam(theRequest, theParams, comboParamNames)) { while (validateParamValuesAreValidForComboParam(theRequest, theParams, comboParamNames, comboParam)) {
applyComboSearchParam(theQueryStack, theParams, theRequest, comboParamNames, comboParam); applyComboSearchParam(theQueryStack, theParams, theRequest, comboParamNames, comboParam);
} }
} }
@ -2068,7 +2068,10 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
* (e.g. <code>?date=gt2024-02-01</code>), etc. * (e.g. <code>?date=gt2024-02-01</code>), etc.
*/ */
private boolean validateParamValuesAreValidForComboParam( private boolean validateParamValuesAreValidForComboParam(
RequestDetails theRequest, @Nonnull SearchParameterMap theParams, List<String> theComboParamNames) { RequestDetails theRequest,
@Nonnull SearchParameterMap theParams,
List<String> theComboParamNames,
RuntimeSearchParam theComboParam) {
boolean paramValuesAreValidForCombo = true; boolean paramValuesAreValidForCombo = true;
List<List<IQueryParameterType>> paramOrValues = new ArrayList<>(theComboParamNames.size()); List<List<IQueryParameterType>> paramOrValues = new ArrayList<>(theComboParamNames.size());
@ -2129,6 +2132,19 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
break; break;
} }
} }
// Date params are not eligible for using composite unique index
// as index could contain date with different precision (e.g. DAY, SECOND)
if (nextParamDef.getParamType() == RestSearchParameterTypeEnum.DATE
&& theComboParam.getComboSearchParamType() == ComboSearchParamType.UNIQUE) {
ourLog.debug(
"Search with params {} is not a candidate for combo searching - "
+ "Unique combo search parameter '{}' has DATE type",
theComboParamNames,
nextParamName);
paramValuesAreValidForCombo = false;
break;
}
} }
if (CartesianProductUtil.calculateCartesianProductSize(paramOrValues) > 500) { if (CartesianProductUtil.calculateCartesianProductSize(paramOrValues) > 500) {

View File

@ -566,7 +566,8 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
for (BaseResourceIndexedSearchParam nextParam : paramsListForCompositePart) { for (BaseResourceIndexedSearchParam nextParam : paramsListForCompositePart) {
IQueryParameterType nextParamAsClientParam = nextParam.toQueryParameterType(); IQueryParameterType nextParamAsClientParam = nextParam.toQueryParameterType();
if (nextParamAsClientParam instanceof DateParam) { if (theParam.getComboSearchParamType() == ComboSearchParamType.NON_UNIQUE
&& nextParamAsClientParam instanceof DateParam) {
DateParam date = (DateParam) nextParamAsClientParam; DateParam date = (DateParam) nextParamAsClientParam;
if (date.getPrecision() != TemporalPrecisionEnum.DAY) { if (date.getPrecision() != TemporalPrecisionEnum.DAY) {
continue; continue;

View File

@ -70,6 +70,11 @@
<artifactId>jakarta.servlet-api</artifactId> <artifactId>jakarta.servlet-api</artifactId>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency>
<groupId>jakarta.mail</groupId>
<artifactId>jakarta.mail-api</artifactId>
<optional>true</optional>
</dependency>
<!-- test dependencies --> <!-- test dependencies -->
<dependency> <dependency>

View File

@ -28,8 +28,8 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import javax.mail.internet.InternetAddress; import jakarta.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage; import jakarta.mail.internet.MimeMessage;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;

View File

@ -17,8 +17,8 @@ import org.junit.jupiter.api.extension.RegisterExtension;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import javax.mail.internet.InternetAddress; import jakarta.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage; import jakarta.mail.internet.MimeMessage;
import java.util.Arrays; import java.util.Arrays;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;

View File

@ -26,8 +26,8 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.api.extension.RegisterExtension;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import javax.mail.internet.InternetAddress; import jakarta.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage; import jakarta.mail.internet.MimeMessage;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;

View File

@ -135,10 +135,10 @@ public abstract class BasePartitioningR4Test extends BaseJpaR4SystemTest {
addCreateDefaultPartition(); addCreateDefaultPartition();
addReadDefaultPartition(); // one for search param validation addReadDefaultPartition(); // one for search param validation
SearchParameter sp = new SearchParameter(); SearchParameter sp = new SearchParameter();
sp.setId("SearchParameter/patient-birthdate"); sp.setId("SearchParameter/patient-gender");
sp.setType(Enumerations.SearchParamType.DATE); sp.setType(Enumerations.SearchParamType.TOKEN);
sp.setCode("birthdate"); sp.setCode("gender");
sp.setExpression("Patient.birthDate"); sp.setExpression("Patient.gender");
sp.setStatus(Enumerations.PublicationStatus.ACTIVE); sp.setStatus(Enumerations.PublicationStatus.ACTIVE);
sp.addBase("Patient"); sp.addBase("Patient");
mySearchParameterDao.update(sp, mySrd); mySearchParameterDao.update(sp, mySrd);
@ -156,13 +156,13 @@ public abstract class BasePartitioningR4Test extends BaseJpaR4SystemTest {
addCreateDefaultPartition(); addCreateDefaultPartition();
sp = new SearchParameter(); sp = new SearchParameter();
sp.setId("SearchParameter/patient-birthdate-unique"); sp.setId("SearchParameter/patient-gender-family-unique");
sp.setType(Enumerations.SearchParamType.COMPOSITE); sp.setType(Enumerations.SearchParamType.COMPOSITE);
sp.setStatus(Enumerations.PublicationStatus.ACTIVE); sp.setStatus(Enumerations.PublicationStatus.ACTIVE);
sp.addBase("Patient"); sp.addBase("Patient");
sp.addComponent() sp.addComponent()
.setExpression("Patient") .setExpression("Patient")
.setDefinition("SearchParameter/patient-birthdate"); .setDefinition("SearchParameter/patient-gender");
sp.addComponent() sp.addComponent()
.setExpression("Patient") .setExpression("Patient")
.setDefinition("SearchParameter/patient-family"); .setDefinition("SearchParameter/patient-family");

View File

@ -13,10 +13,10 @@ import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.searchparam.util.JpaParamUtil; import ca.uhn.fhir.jpa.searchparam.util.JpaParamUtil;
import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.DateAndListParam;
import ca.uhn.fhir.rest.param.DateOrListParam;
import ca.uhn.fhir.rest.param.DateParam; import ca.uhn.fhir.rest.param.DateParam;
import ca.uhn.fhir.rest.param.ReferenceParam; import ca.uhn.fhir.rest.param.ReferenceParam;
import ca.uhn.fhir.rest.param.StringOrListParam;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.param.TokenAndListParam; import ca.uhn.fhir.rest.param.TokenAndListParam;
import ca.uhn.fhir.rest.param.TokenParam; import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException; import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
@ -33,6 +33,7 @@ import org.hl7.fhir.r4.model.DateType;
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.Enumerations;
import org.hl7.fhir.r4.model.Enumerations.PublicationStatus; import org.hl7.fhir.r4.model.Enumerations.PublicationStatus;
import org.hl7.fhir.r4.model.HumanName;
import org.hl7.fhir.r4.model.IdType; import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Observation; import org.hl7.fhir.r4.model.Observation;
import org.hl7.fhir.r4.model.Organization; import org.hl7.fhir.r4.model.Organization;
@ -115,6 +116,46 @@ public class FhirResourceDaoR4ComboUniqueParamTest extends BaseComboParamsR4Test
myMessages.clear(); myMessages.clear();
} }
private void createUniqueGenderFamilyComboSp() {
SearchParameter sp = new SearchParameter();
sp.setId("SearchParameter/patient-gender");
sp.setType(Enumerations.SearchParamType.TOKEN);
sp.setCode("gender");
sp.setExpression("Patient.gender");
sp.setStatus(PublicationStatus.ACTIVE);
sp.addBase("Patient");
mySearchParameterDao.update(sp, mySrd);
sp = new SearchParameter();
sp.setId("SearchParameter/patient-family");
sp.setType(Enumerations.SearchParamType.STRING);
sp.setCode("family");
sp.setExpression("Patient.name.family");
sp.setStatus(Enumerations.PublicationStatus.ACTIVE);
sp.addBase("Patient");
mySearchParameterDao.update(sp, mySrd);
sp = new SearchParameter();
sp.setId("SearchParameter/patient-gender-family");
sp.setType(Enumerations.SearchParamType.COMPOSITE);
sp.setStatus(PublicationStatus.ACTIVE);
sp.addBase("Patient");
sp.addComponent()
.setExpression("Patient")
.setDefinition("SearchParameter/patient-gender");
sp.addComponent()
.setExpression("Patient")
.setDefinition("SearchParameter/patient-family");
sp.addExtension()
.setUrl(HapiExtensions.EXT_SP_UNIQUE)
.setValue(new BooleanType(true));
mySearchParameterDao.update(sp, mySrd);
mySearchParamRegistry.forceRefresh();
myMessages.clear();
}
private void createUniqueIndexCoverageBeneficiary() { private void createUniqueIndexCoverageBeneficiary() {
SearchParameter sp = new SearchParameter(); SearchParameter sp = new SearchParameter();
sp.setId("SearchParameter/coverage-beneficiary"); sp.setId("SearchParameter/coverage-beneficiary");
@ -276,6 +317,45 @@ public class FhirResourceDaoR4ComboUniqueParamTest extends BaseComboParamsR4Test
mySearchParamRegistry.forceRefresh(); mySearchParamRegistry.forceRefresh();
} }
private void createUniqueObservationDateCode() {
SearchParameter sp = new SearchParameter();
sp.setId("SearchParameter/obs-effective");
sp.setType(Enumerations.SearchParamType.DATE);
sp.setCode("date");
sp.setExpression("Observation.effective");
sp.setStatus(PublicationStatus.ACTIVE);
sp.addBase("Observation");
mySearchParameterDao.update(sp, mySrd);
sp = new SearchParameter();
sp.setId("SearchParameter/obs-code");
sp.setType(Enumerations.SearchParamType.TOKEN);
sp.setCode("code");
sp.setExpression("Observation.code");
sp.setStatus(PublicationStatus.ACTIVE);
sp.addBase("Observation");
mySearchParameterDao.update(sp, mySrd);
sp = new SearchParameter();
sp.setId("SearchParameter/observation-date-code");
sp.setType(Enumerations.SearchParamType.COMPOSITE);
sp.setStatus(PublicationStatus.ACTIVE);
sp.addBase("Observation");
sp.setExpression("Observation.code");
sp.addComponent()
.setExpression("Observation")
.setDefinition("SearchParameter/obs-effective");
sp.addComponent()
.setExpression("Observation")
.setDefinition("SearchParameter/obs-code");
sp.addExtension()
.setUrl(HapiExtensions.EXT_SP_UNIQUE)
.setValue(new BooleanType(true));
mySearchParameterDao.update(sp, mySrd);
mySearchParamRegistry.forceRefresh();
}
private void createUniqueObservationSubjectDateCode() { private void createUniqueObservationSubjectDateCode() {
SearchParameter sp = new SearchParameter(); SearchParameter sp = new SearchParameter();
sp.setId("SearchParameter/obs-subject"); sp.setId("SearchParameter/obs-subject");
@ -471,11 +551,11 @@ public class FhirResourceDaoR4ComboUniqueParamTest extends BaseComboParamsR4Test
public void testDoubleMatchingOnAnd_Search_TwoAndOrValues() { public void testDoubleMatchingOnAnd_Search_TwoAndOrValues() {
myStorageSettings.setUniqueIndexesCheckedBeforeSave(false); myStorageSettings.setUniqueIndexesCheckedBeforeSave(false);
createUniqueBirthdateAndGenderSps(); createUniqueGenderFamilyComboSp();
Patient pt1 = new Patient(); Patient pt1 = new Patient();
pt1.setGender(Enumerations.AdministrativeGender.MALE); pt1.setGender(Enumerations.AdministrativeGender.MALE);
pt1.setBirthDateElement(new DateType("2011-01-01")); pt1.getName().add(new HumanName().setFamily("Family1"));
String id1 = myPatientDao.create(pt1, mySrd).getId().toUnqualifiedVersionless().getValue(); String id1 = myPatientDao.create(pt1, mySrd).getId().toUnqualifiedVersionless().getValue();
// Two OR values on the same resource - Currently composite SPs don't work for this // Two OR values on the same resource - Currently composite SPs don't work for this
@ -484,17 +564,22 @@ public class FhirResourceDaoR4ComboUniqueParamTest extends BaseComboParamsR4Test
sp.setLoadSynchronous(true); sp.setLoadSynchronous(true);
sp.add(Patient.SP_GENDER, sp.add(Patient.SP_GENDER,
new TokenAndListParam() new TokenAndListParam()
.addAnd(new TokenParam("http://hl7.org/fhir/administrative-gender","male"), new TokenParam( "http://hl7.org/fhir/administrative-gender","female")) .addAnd(new TokenParam("http://hl7.org/fhir/administrative-gender","male"),
new TokenParam( "http://hl7.org/fhir/administrative-gender","female"))
); );
sp.add(Patient.SP_BIRTHDATE, sp.add(Patient.SP_FAMILY,
new DateAndListParam() new StringOrListParam()
.addAnd(new DateParam("2011-01-01"), new DateParam( "2011-02-02")) .addOr(new StringParam("Family1")).addOr(new StringParam("Family2"))
); );
IBundleProvider outcome = myPatientDao.search(sp, mySrd); IBundleProvider outcome = myPatientDao.search(sp, mySrd);
myCaptureQueriesListener.logFirstSelectQueryForCurrentThread(); myCaptureQueriesListener.logFirstSelectQueryForCurrentThread();
assertThat(toUnqualifiedVersionlessIdValues(outcome)).containsExactlyInAnyOrder(id1); assertThat(toUnqualifiedVersionlessIdValues(outcome)).containsExactlyInAnyOrder(id1);
String unformattedSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false); String unformattedSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false);
assertEquals("SELECT t0.RES_ID FROM HFJ_IDX_CMP_STRING_UNIQ t0 WHERE (t0.IDX_STRING IN ('Patient?birthdate=2011-01-01&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cfemale','Patient?birthdate=2011-01-01&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale','Patient?birthdate=2011-02-02&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cfemale','Patient?birthdate=2011-02-02&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale') )", unformattedSql); assertEquals("SELECT t0.RES_ID FROM HFJ_IDX_CMP_STRING_UNIQ t0 WHERE (t0.IDX_STRING IN (" +
"'Patient?family=Family1&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cfemale'," +
"'Patient?family=Family1&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale'," +
"'Patient?family=Family2&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cfemale'," +
"'Patient?family=Family2&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale') )", unformattedSql);
} }
@ -1167,16 +1252,16 @@ public class FhirResourceDaoR4ComboUniqueParamTest extends BaseComboParamsR4Test
@Test @Test
public void testOrQuery() { public void testOrQuery() {
myStorageSettings.setAdvancedHSearchIndexing(false); myStorageSettings.setAdvancedHSearchIndexing(false);
createUniqueBirthdateAndGenderSps(); createUniqueGenderFamilyComboSp();
Patient pt1 = new Patient(); Patient pt1 = new Patient();
pt1.setGender(Enumerations.AdministrativeGender.MALE); pt1.setGender(Enumerations.AdministrativeGender.MALE);
pt1.setBirthDateElement(new DateType("2011-01-01")); pt1.getName().add(new HumanName().setFamily("Family1"));
IIdType id1 = myPatientDao.create(pt1, mySrd).getId().toUnqualifiedVersionless(); IIdType id1 = myPatientDao.create(pt1, mySrd).getId().toUnqualifiedVersionless();
Patient pt2 = new Patient(); Patient pt2 = new Patient();
pt2.setGender(Enumerations.AdministrativeGender.MALE); pt2.setGender(Enumerations.AdministrativeGender.MALE);
pt2.setBirthDateElement(new DateType("2011-01-02")); pt2.getName().add(new HumanName().setFamily("Family2"));
IIdType id2 = myPatientDao.create(pt2, mySrd).getId().toUnqualifiedVersionless(); IIdType id2 = myPatientDao.create(pt2, mySrd).getId().toUnqualifiedVersionless();
myCaptureQueriesListener.clear(); myCaptureQueriesListener.clear();
@ -1184,16 +1269,21 @@ public class FhirResourceDaoR4ComboUniqueParamTest extends BaseComboParamsR4Test
SearchParameterMap params = new SearchParameterMap(); SearchParameterMap params = new SearchParameterMap();
params.setLoadSynchronousUpTo(100); params.setLoadSynchronousUpTo(100);
params.add("gender", new TokenParam("http://hl7.org/fhir/administrative-gender", "male")); params.add("gender", new TokenParam("http://hl7.org/fhir/administrative-gender", "male"));
params.add("birthdate", new DateOrListParam().addOr(new DateParam("2011-01-01")).addOr(new DateParam("2011-01-02"))); params.add("family", new StringOrListParam()
.addOr(new StringParam("Family1")).addOr(new StringParam("Family2")));
myCaptureQueriesListener.clear(); myCaptureQueriesListener.clear();
IBundleProvider results = myPatientDao.search(params, mySrd); IBundleProvider results = myPatientDao.search(params, mySrd);
myCaptureQueriesListener.logFirstSelectQueryForCurrentThread(); myCaptureQueriesListener.logFirstSelectQueryForCurrentThread();
assertThat(toUnqualifiedVersionlessIdValues(results)).containsExactlyInAnyOrder(id1.getValue(), id2.getValue()); assertThat(toUnqualifiedVersionlessIdValues(results)).containsExactlyInAnyOrder(id1.getValue(), id2.getValue());
assertThat(myCaptureQueriesListener.getSelectQueries().get(0).getSql(true, false)) assertThat(myCaptureQueriesListener.getSelectQueries().get(0).getSql(true, false))
.contains("SELECT t0.RES_ID FROM HFJ_IDX_CMP_STRING_UNIQ t0 WHERE (t0.IDX_STRING IN ('Patient?birthdate=2011-01-01&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale','Patient?birthdate=2011-01-02&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale') )"); .contains("SELECT t0.RES_ID FROM HFJ_IDX_CMP_STRING_UNIQ t0 WHERE (t0.IDX_STRING IN " +
"('Patient?family=Family1&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale'," +
"'Patient?family=Family2&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale') )");
logCapturedMessages(); logCapturedMessages();
assertThat(myMessages.toString()).contains("Using UNIQUE index(es) for query for search: [Patient?birthdate=2011-01-01&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale, Patient?birthdate=2011-01-02&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale]"); assertThat(myMessages.toString()).contains("Using UNIQUE index(es) for query for search: " +
"[Patient?family=Family1&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale, " +
"Patient?family=Family2&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale]");
myMessages.clear(); myMessages.clear();
} }
@ -1201,16 +1291,16 @@ public class FhirResourceDaoR4ComboUniqueParamTest extends BaseComboParamsR4Test
@Test @Test
public void testSearchSynchronousUsingUniqueComposite() { public void testSearchSynchronousUsingUniqueComposite() {
myStorageSettings.setAdvancedHSearchIndexing(false); myStorageSettings.setAdvancedHSearchIndexing(false);
createUniqueBirthdateAndGenderSps(); createUniqueGenderFamilyComboSp();
Patient pt1 = new Patient(); Patient pt1 = new Patient();
pt1.setGender(Enumerations.AdministrativeGender.MALE); pt1.setGender(Enumerations.AdministrativeGender.MALE);
pt1.setBirthDateElement(new DateType("2011-01-01")); pt1.getName().add(new HumanName().setFamily("Family1"));
IIdType id1 = myPatientDao.create(pt1, mySrd).getId().toUnqualifiedVersionless(); IIdType id1 = myPatientDao.create(pt1, mySrd).getId().toUnqualifiedVersionless();
Patient pt2 = new Patient(); Patient pt2 = new Patient();
pt2.setGender(Enumerations.AdministrativeGender.MALE); pt2.setGender(Enumerations.AdministrativeGender.MALE);
pt2.setBirthDateElement(new DateType("2011-01-02")); pt2.getName().add(new HumanName().setFamily("Family2"));
myPatientDao.create(pt2, mySrd).getId().toUnqualifiedVersionless(); myPatientDao.create(pt2, mySrd).getId().toUnqualifiedVersionless();
myCaptureQueriesListener.clear(); myCaptureQueriesListener.clear();
@ -1218,13 +1308,14 @@ public class FhirResourceDaoR4ComboUniqueParamTest extends BaseComboParamsR4Test
SearchParameterMap params = new SearchParameterMap(); SearchParameterMap params = new SearchParameterMap();
params.setLoadSynchronousUpTo(100); params.setLoadSynchronousUpTo(100);
params.add("gender", new TokenParam("http://hl7.org/fhir/administrative-gender", "male")); params.add("gender", new TokenParam("http://hl7.org/fhir/administrative-gender", "male"));
params.add("birthdate", new DateParam("2011-01-01")); params.add("family", new StringParam("Family1"));
IBundleProvider results = myPatientDao.search(params, mySrd); IBundleProvider results = myPatientDao.search(params, mySrd);
myCaptureQueriesListener.logFirstSelectQueryForCurrentThread(); myCaptureQueriesListener.logFirstSelectQueryForCurrentThread();
assertThat(toUnqualifiedVersionlessIdValues(results)).containsExactlyInAnyOrder(id1.getValue()); assertThat(toUnqualifiedVersionlessIdValues(results)).containsExactlyInAnyOrder(id1.getValue());
logCapturedMessages(); logCapturedMessages();
assertThat(myMessages.toString()).contains("Using UNIQUE index(es) for query for search: Patient?birthdate=2011-01-01&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale"); assertThat(myMessages.toString()).contains("Using UNIQUE index(es) for query for search: " +
"Patient?family=Family1&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale");
myMessages.clear(); myMessages.clear();
} }
@ -1232,33 +1323,34 @@ public class FhirResourceDaoR4ComboUniqueParamTest extends BaseComboParamsR4Test
@Test @Test
public void testSearchUsingUniqueComposite() { public void testSearchUsingUniqueComposite() {
createUniqueBirthdateAndGenderSps(); createUniqueGenderFamilyComboSp();
Patient pt1 = new Patient(); Patient pt1 = new Patient();
pt1.setGender(Enumerations.AdministrativeGender.MALE); pt1.setGender(Enumerations.AdministrativeGender.MALE);
pt1.setBirthDateElement(new DateType("2011-01-01")); pt1.getName().add(new HumanName().setFamily("Family1"));
String id1 = myPatientDao.create(pt1, mySrd).getId().toUnqualifiedVersionless().getValue(); String id1 = myPatientDao.create(pt1, mySrd).getId().toUnqualifiedVersionless().getValue();
Patient pt2 = new Patient(); Patient pt2 = new Patient();
pt2.setGender(Enumerations.AdministrativeGender.MALE); pt2.setGender(Enumerations.AdministrativeGender.MALE);
pt2.setBirthDateElement(new DateType("2011-01-02")); pt2.getName().add(new HumanName().setFamily("Family2"));
myPatientDao.create(pt2, mySrd).getId().toUnqualifiedVersionless(); myPatientDao.create(pt2, mySrd).getId().toUnqualifiedVersionless();
myMessages.clear(); myMessages.clear();
SearchParameterMap params = new SearchParameterMap(); SearchParameterMap params = new SearchParameterMap();
params.add("gender", new TokenParam("http://hl7.org/fhir/administrative-gender", "male")); params.add("gender", new TokenParam("http://hl7.org/fhir/administrative-gender", "male"));
params.add("birthdate", new DateParam("2011-01-01")); params.add("family", new StringParam("Family1"));
IBundleProvider results = myPatientDao.search(params, mySrd); IBundleProvider results = myPatientDao.search(params, mySrd);
String searchId = results.getUuid(); String searchId = results.getUuid();
assertThat(toUnqualifiedVersionlessIdValues(results)).containsExactlyInAnyOrder(id1); assertThat(toUnqualifiedVersionlessIdValues(results)).containsExactlyInAnyOrder(id1);
logCapturedMessages(); logCapturedMessages();
assertThat(myMessages.toString()).contains("Using UNIQUE index(es) for query for search: Patient?birthdate=2011-01-01&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale"); assertThat(myMessages.toString()).contains("Using UNIQUE index(es) for query for search: " +
"Patient?family=Family1&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale");
myMessages.clear(); myMessages.clear();
// Other order // Other order
myMessages.clear(); myMessages.clear();
params = new SearchParameterMap(); params = new SearchParameterMap();
params.add("birthdate", new DateParam("2011-01-01")); params.add("family", new StringParam("Family1"));
params.add("gender", new TokenParam("http://hl7.org/fhir/administrative-gender", "male")); params.add("gender", new TokenParam("http://hl7.org/fhir/administrative-gender", "male"));
results = myPatientDao.search(params, mySrd); results = myPatientDao.search(params, mySrd);
assertEquals(searchId, results.getUuid()); assertEquals(searchId, results.getUuid());
@ -1272,16 +1364,17 @@ public class FhirResourceDaoR4ComboUniqueParamTest extends BaseComboParamsR4Test
myMessages.clear(); myMessages.clear();
params = new SearchParameterMap(); params = new SearchParameterMap();
params.add("gender", new TokenParam("http://hl7.org/fhir/administrative-gender", "male")); params.add("gender", new TokenParam("http://hl7.org/fhir/administrative-gender", "male"));
params.add("birthdate", new DateParam("2011-01-03")); params.add("family", new StringParam("Family3"));
results = myPatientDao.search(params, mySrd); results = myPatientDao.search(params, mySrd);
assertThat(toUnqualifiedVersionlessIdValues(results)).isEmpty(); assertThat(toUnqualifiedVersionlessIdValues(results)).isEmpty();
logCapturedMessages(); logCapturedMessages();
assertThat(myMessages.toString()).contains("Using UNIQUE index(es) for query for search: Patient?birthdate=2011-01-03&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale"); assertThat(myMessages.toString()).contains("Using UNIQUE index(es) for query for search: " +
"Patient?family=Family3&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale");
myMessages.clear(); myMessages.clear();
myMessages.clear(); myMessages.clear();
params = new SearchParameterMap(); params = new SearchParameterMap();
params.add("birthdate", new DateParam("2011-01-03")); params.add("family", new StringParam("Family3"));
results = myPatientDao.search(params, mySrd); results = myPatientDao.search(params, mySrd);
assertThat(toUnqualifiedVersionlessIdValues(results)).isEmpty(); assertThat(toUnqualifiedVersionlessIdValues(results)).isEmpty();
// STANDARD QUERY // STANDARD QUERY
@ -1666,7 +1759,7 @@ public class FhirResourceDaoR4ComboUniqueParamTest extends BaseComboParamsR4Test
} }
@Test @Test
public void testDuplicateUniqueValuesAreRejected() { public void testDuplicateUniqueValuesWithDateAreRejected() {
createUniqueBirthdateAndGenderSps(); createUniqueBirthdateAndGenderSps();
Patient pt1 = new Patient(); Patient pt1 = new Patient();
@ -1699,13 +1792,75 @@ public class FhirResourceDaoR4ComboUniqueParamTest extends BaseComboParamsR4Test
} }
@Test @Test
public void testReplaceOneWithAnother() { public void testDuplicateUniqueValuesWithDateTimeAreRejected() {
myStorageSettings.setAdvancedHSearchIndexing(false); createUniqueObservationDateCode();
Observation obs = new Observation();
obs.getCode().addCoding().setSystem("foo").setCode("bar");
obs.setEffective(new DateTimeType("2017-10-10T00:00:00"));
myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
try {
myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
fail();
} catch (ResourceVersionConflictException e) {
assertThat(e.getMessage())
.contains("new unique index created by SearchParameter/observation-date-code");
}
}
@Test
public void testUniqueComboSearchWithDateNotUsingUniqueIndex() {
createUniqueBirthdateAndGenderSps(); createUniqueBirthdateAndGenderSps();
Patient pt1 = new Patient(); Patient pt1 = new Patient();
pt1.setGender(Enumerations.AdministrativeGender.MALE); pt1.setGender(Enumerations.AdministrativeGender.MALE);
pt1.setBirthDateElement(new DateType("2011-01-01")); pt1.setBirthDateElement(new DateType("2011-01-01"));
String pId = myPatientDao.create(pt1, mySrd).getId().toUnqualifiedVersionless().getValue();
myCaptureQueriesListener.clear();
SearchParameterMap params = new SearchParameterMap();
params.setLoadSynchronousUpTo(100);
params.add("gender", new TokenParam("http://hl7.org/fhir/administrative-gender", "male"));
params.add("birthdate", new DateParam("2011-01-01"));
IBundleProvider results = myPatientDao.search(params, mySrd);
assertThat(toUnqualifiedVersionlessIdValues(results)).containsExactlyInAnyOrder(pId);
myCaptureQueriesListener.logFirstSelectQueryForCurrentThread();
String unformattedSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false);
assertThat(unformattedSql).doesNotContain("HFJ_IDX_CMP_STRING_UNIQ");
}
@Test
public void testUniqueComboSearchWithDateTimeNotUsingUniqueIndex() {
createUniqueObservationDateCode();
Observation obs = new Observation();
obs.getCode().addCoding().setSystem("foo").setCode("bar");
obs.setEffective(new DateTimeType("2017-10-10T00:00:00"));
String obsId = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless().getValue();
myCaptureQueriesListener.clear();
SearchParameterMap params = new SearchParameterMap();
params.setLoadSynchronousUpTo(100);
params.add("code", new TokenParam("foo", "bar"));
params.add("date", new DateParam("2017-10-10T00:00:00"));
IBundleProvider results = myObservationDao.search(params, mySrd);
assertThat(toUnqualifiedVersionlessIdValues(results)).containsExactlyInAnyOrder(obsId);
myCaptureQueriesListener.logFirstSelectQueryForCurrentThread();
String unformattedSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false);
assertThat(unformattedSql).doesNotContain("HFJ_IDX_CMP_STRING_UNIQ");
}
@Test
public void testReplaceOneWithAnother() {
myStorageSettings.setAdvancedHSearchIndexing(false);
createUniqueGenderFamilyComboSp();
Patient pt1 = new Patient();
pt1.setGender(Enumerations.AdministrativeGender.MALE);
pt1.getName().add(new HumanName().setFamily("Family1"));
IIdType id1 = myPatientDao.create(pt1, mySrd).getId().toUnqualified(); IIdType id1 = myPatientDao.create(pt1, mySrd).getId().toUnqualified();
assertNotNull(id1); assertNotNull(id1);
@ -1714,27 +1869,28 @@ public class FhirResourceDaoR4ComboUniqueParamTest extends BaseComboParamsR4Test
pt1 = new Patient(); pt1 = new Patient();
pt1.setId(id1); pt1.setId(id1);
pt1.setGender(Enumerations.AdministrativeGender.FEMALE); pt1.setGender(Enumerations.AdministrativeGender.FEMALE);
pt1.setBirthDateElement(new DateType("2011-01-01")); pt1.getName().add(new HumanName().setFamily("Family1"));
id1 = myPatientDao.update(pt1, mySrd).getId().toUnqualified(); id1 = myPatientDao.update(pt1, mySrd).getId().toUnqualified();
assertNotNull(id1); assertNotNull(id1);
assertEquals("2", id1.getVersionIdPart()); assertEquals("2", id1.getVersionIdPart());
Patient pt2 = new Patient(); Patient pt2 = new Patient();
pt2.setGender(Enumerations.AdministrativeGender.MALE); pt2.setGender(Enumerations.AdministrativeGender.MALE);
pt2.setBirthDateElement(new DateType("2011-01-01")); pt2.getName().add(new HumanName().setFamily("Family1"));
IIdType id2 = myPatientDao.create(pt2, mySrd).getId().toUnqualifiedVersionless(); IIdType id2 = myPatientDao.create(pt2, mySrd).getId().toUnqualifiedVersionless();
myMessages.clear(); myMessages.clear();
SearchParameterMap params = new SearchParameterMap(); SearchParameterMap params = new SearchParameterMap();
params.add("gender", new TokenParam("http://hl7.org/fhir/administrative-gender", "male")); params.add("gender", new TokenParam("http://hl7.org/fhir/administrative-gender", "male"));
params.add("birthdate", new DateParam("2011-01-01")); params.add("family", new StringParam("Family1"));
IBundleProvider results = myPatientDao.search(params, mySrd); IBundleProvider results = myPatientDao.search(params, mySrd);
String searchId = results.getUuid(); String searchId = results.getUuid();
assertThat(searchId).isNotBlank(); assertThat(searchId).isNotBlank();
assertThat(toUnqualifiedVersionlessIdValues(results)).containsExactlyInAnyOrder(id2.getValue()); assertThat(toUnqualifiedVersionlessIdValues(results)).containsExactlyInAnyOrder(id2.getValue());
logCapturedMessages(); logCapturedMessages();
assertThat(myMessages.toString()).contains("Using UNIQUE index(es) for query for search: Patient?birthdate=2011-01-01&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale"); assertThat(myMessages.toString()).contains("Using UNIQUE index(es) for query for search: " +
"Patient?family=Family1&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale");
myMessages.clear(); myMessages.clear();
} }

View File

@ -413,7 +413,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
p.getMeta().addTag("http://system", "code", "diisplay"); p.getMeta().addTag("http://system", "code", "diisplay");
p.addName().setFamily("FAM"); p.addName().setFamily("FAM");
p.addIdentifier().setSystem("system").setValue("value"); p.addIdentifier().setSystem("system").setValue("value");
p.setBirthDateElement(new DateType("2020-01-01")); p.setGender(Enumerations.AdministrativeGender.MALE);
p.getManagingOrganization().setReferenceElement(orgId); p.getManagingOrganization().setReferenceElement(orgId);
Long patientId = myPatientDao.create(p, mySrd).getId().getIdPartAsLong(); Long patientId = myPatientDao.create(p, mySrd).getId().getIdPartAsLong();
@ -502,7 +502,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
p.getMeta().addTag("http://system", "code", "diisplay"); p.getMeta().addTag("http://system", "code", "diisplay");
p.addName().setFamily("FAM"); p.addName().setFamily("FAM");
p.addIdentifier().setSystem("system").setValue("value"); p.addIdentifier().setSystem("system").setValue("value");
p.setBirthDate(new Date()); p.setGender(Enumerations.AdministrativeGender.MALE);
p.getManagingOrganization().setReferenceElement(orgId); p.getManagingOrganization().setReferenceElement(orgId);
Long patientId = myPatientDao.create(p, mySrd).getId().getIdPartAsLong(); Long patientId = myPatientDao.create(p, mySrd).getId().getIdPartAsLong();
@ -679,7 +679,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
p.getMeta().addTag("http://system", "code", "display"); p.getMeta().addTag("http://system", "code", "display");
p.addName().setFamily("FAM"); p.addName().setFamily("FAM");
p.addIdentifier().setSystem("system").setValue("value"); p.addIdentifier().setSystem("system").setValue("value");
p.setBirthDate(new Date()); p.setGender(Enumerations.AdministrativeGender.MALE);
p.getManagingOrganization().setReference(org.getId()); p.getManagingOrganization().setReference(org.getId());
input.addEntry() input.addEntry()
.setFullUrl(p.getId()) .setFullUrl(p.getId())
@ -2541,14 +2541,14 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
public void testSearch_UniqueParam_SearchAllPartitions() { public void testSearch_UniqueParam_SearchAllPartitions() {
createUniqueComboSp(); createUniqueComboSp();
IIdType id = createPatient(withPartition(1), withBirthdate("2020-01-01"), withFamily("FAM")); IIdType id = createPatient(withPartition(1), withGender("male"), withFamily("FAM"));
addReadAllPartitions(); addReadAllPartitions();
myCaptureQueriesListener.clear(); myCaptureQueriesListener.clear();
SearchParameterMap map = new SearchParameterMap(); SearchParameterMap map = new SearchParameterMap();
map.add(Patient.SP_FAMILY, new StringParam("FAM")); map.add(Patient.SP_FAMILY, new StringParam("FAM"));
map.add(Patient.SP_BIRTHDATE, new DateParam("2020-01-01")); map.add(Patient.SP_GENDER, new TokenParam(null, "male"));
map.setLoadSynchronous(true); map.setLoadSynchronous(true);
IBundleProvider results = myPatientDao.search(map, mySrd); IBundleProvider results = myPatientDao.search(map, mySrd);
List<IIdType> ids = toUnqualifiedVersionlessIds(results); List<IIdType> ids = toUnqualifiedVersionlessIds(results);
@ -2558,7 +2558,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true); String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql); ourLog.info("Search SQL:\n{}", searchSql);
assertThat(searchSql).doesNotContain("PARTITION_ID"); assertThat(searchSql).doesNotContain("PARTITION_ID");
assertThat(searchSql).containsOnlyOnce("IDX_STRING = 'Patient?birthdate=2020-01-01&family=FAM'"); assertThat(searchSql).containsOnlyOnce("IDX_STRING = 'Patient?family=FAM&gender=male'");
} }
@ -2566,13 +2566,13 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
public void testSearch_UniqueParam_SearchOnePartition() { public void testSearch_UniqueParam_SearchOnePartition() {
createUniqueComboSp(); createUniqueComboSp();
IIdType id = createPatient(withPartition(1), withBirthdate("2020-01-01"), withFamily("FAM")); IIdType id = createPatient(withPartition(1), withGender("male"), withFamily("FAM"));
addReadPartition(1); addReadPartition(1);
myCaptureQueriesListener.clear(); myCaptureQueriesListener.clear();
SearchParameterMap map = new SearchParameterMap(); SearchParameterMap map = new SearchParameterMap();
map.add(Patient.SP_FAMILY, new StringParam("FAM")); map.add(Patient.SP_FAMILY, new StringParam("FAM"));
map.add(Patient.SP_BIRTHDATE, new DateParam("2020-01-01")); map.add(Patient.SP_GENDER, new TokenParam(null, "male"));
map.setLoadSynchronous(true); map.setLoadSynchronous(true);
IBundleProvider results = myPatientDao.search(map, mySrd); IBundleProvider results = myPatientDao.search(map, mySrd);
List<IIdType> ids = toUnqualifiedVersionlessIds(results); List<IIdType> ids = toUnqualifiedVersionlessIds(results);
@ -2582,13 +2582,13 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true); String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql); ourLog.info("Search SQL:\n{}", searchSql);
assertThat(searchSql).containsOnlyOnce( "PARTITION_ID = '1'"); assertThat(searchSql).containsOnlyOnce( "PARTITION_ID = '1'");
assertThat(searchSql).containsOnlyOnce("IDX_STRING = 'Patient?birthdate=2020-01-01&family=FAM'"); assertThat(searchSql).containsOnlyOnce("IDX_STRING = 'Patient?family=FAM&gender=male'");
// Same query, different partition // Same query, different partition
addReadPartition(2); addReadPartition(2);
myCaptureQueriesListener.clear(); myCaptureQueriesListener.clear();
map = new SearchParameterMap(); map = new SearchParameterMap();
map.add(Patient.SP_BIRTHDATE, new DateParam("2020-01-01")); map.add(Patient.SP_GENDER, new TokenParam(null, "male"));
map.setLoadSynchronous(true); map.setLoadSynchronous(true);
results = myPatientDao.search(map, mySrd); results = myPatientDao.search(map, mySrd);
ids = toUnqualifiedVersionlessIds(results); ids = toUnqualifiedVersionlessIds(results);
@ -2661,7 +2661,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
public void testSearch_RefParam_TargetPid_SearchOnePartition() { public void testSearch_RefParam_TargetPid_SearchOnePartition() {
createUniqueComboSp(); createUniqueComboSp();
IIdType patientId = createPatient(withPartition(myPartitionId), withBirthdate("2020-01-01")); IIdType patientId = createPatient(withPartition(myPartitionId), withGender("male"));
IIdType observationId = createObservation(withPartition(myPartitionId), withSubject(patientId)); IIdType observationId = createObservation(withPartition(myPartitionId), withSubject(patientId));
addReadPartition(myPartitionId); addReadPartition(myPartitionId);
@ -2698,7 +2698,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
public void testSearch_RefParam_TargetPid_SearchDefaultPartition() { public void testSearch_RefParam_TargetPid_SearchDefaultPartition() {
createUniqueComboSp(); createUniqueComboSp();
IIdType patientId = createPatient(withPartition(null), withBirthdate("2020-01-01")); IIdType patientId = createPatient(withPartition(null), withGender("male"));
IIdType observationId = createObservation(withPartition(null), withSubject(patientId)); IIdType observationId = createObservation(withPartition(null), withSubject(patientId));
addReadDefaultPartition(); addReadDefaultPartition();
@ -2735,7 +2735,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
public void testSearch_RefParam_TargetForcedId_SearchOnePartition() { public void testSearch_RefParam_TargetForcedId_SearchOnePartition() {
createUniqueComboSp(); createUniqueComboSp();
IIdType patientId = createPatient(withPartition(myPartitionId), withId("ONE"), withBirthdate("2020-01-01")); IIdType patientId = createPatient(withPartition(myPartitionId), withId("ONE"), withGender("male"));
IIdType observationId = createObservation(withPartition(myPartitionId), withSubject(patientId)); IIdType observationId = createObservation(withPartition(myPartitionId), withSubject(patientId));
addReadPartition(myPartitionId); addReadPartition(myPartitionId);
@ -2805,7 +2805,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
public void testSearch_RefParam_TargetForcedId_SearchDefaultPartition() { public void testSearch_RefParam_TargetForcedId_SearchDefaultPartition() {
createUniqueComboSp(); createUniqueComboSp();
IIdType patientId = createPatient(withPartition(null), withId("ONE"), withBirthdate("2020-01-01")); IIdType patientId = createPatient(withPartition(null), withId("ONE"), withGender("male"));
IIdType observationId = createObservation(withPartition(null), withSubject(patientId)); IIdType observationId = createObservation(withPartition(null), withSubject(patientId));
addReadDefaultPartition(); addReadDefaultPartition();

View File

@ -20,6 +20,7 @@
package ca.uhn.fhir.jpa.embedded; package ca.uhn.fhir.jpa.embedded;
import ca.uhn.fhir.jpa.migrate.DriverTypeEnum; import ca.uhn.fhir.jpa.migrate.DriverTypeEnum;
import ca.uhn.fhir.jpa.util.DatabaseSupportUtil;
import ca.uhn.fhir.test.utilities.docker.DockerRequiredCondition; import ca.uhn.fhir.test.utilities.docker.DockerRequiredCondition;
import ca.uhn.fhir.util.VersionEnum; import ca.uhn.fhir.util.VersionEnum;
import org.junit.jupiter.api.extension.AfterAllCallback; import org.junit.jupiter.api.extension.AfterAllCallback;
@ -54,7 +55,7 @@ public class HapiEmbeddedDatabasesExtension implements AfterAllCallback {
myEmbeddedDatabases.add(new H2EmbeddedDatabase()); myEmbeddedDatabases.add(new H2EmbeddedDatabase());
myEmbeddedDatabases.add(new PostgresEmbeddedDatabase()); myEmbeddedDatabases.add(new PostgresEmbeddedDatabase());
myEmbeddedDatabases.add(new MsSqlEmbeddedDatabase()); myEmbeddedDatabases.add(new MsSqlEmbeddedDatabase());
if (OracleCondition.canUseOracle()) { if (DatabaseSupportUtil.canUseOracle()) {
myEmbeddedDatabases.add(new OracleEmbeddedDatabase()); myEmbeddedDatabases.add(new OracleEmbeddedDatabase());
} else { } else {
String message = String message =
@ -136,7 +137,7 @@ public class HapiEmbeddedDatabasesExtension implements AfterAllCallback {
arguments.add(Arguments.of(DriverTypeEnum.POSTGRES_9_4)); arguments.add(Arguments.of(DriverTypeEnum.POSTGRES_9_4));
arguments.add(Arguments.of(DriverTypeEnum.MSSQL_2012)); arguments.add(Arguments.of(DriverTypeEnum.MSSQL_2012));
if (OracleCondition.canUseOracle()) { if (DatabaseSupportUtil.canUseOracle()) {
arguments.add(Arguments.of(DriverTypeEnum.ORACLE_12C)); arguments.add(Arguments.of(DriverTypeEnum.ORACLE_12C));
} }

View File

@ -20,6 +20,7 @@
package ca.uhn.fhir.jpa.embedded; package ca.uhn.fhir.jpa.embedded;
import ca.uhn.fhir.jpa.migrate.DriverTypeEnum; import ca.uhn.fhir.jpa.migrate.DriverTypeEnum;
import ca.uhn.fhir.jpa.util.DatabaseSupportUtil;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.testcontainers.containers.MSSQLServerContainer; import org.testcontainers.containers.MSSQLServerContainer;
@ -43,9 +44,19 @@ public class MsSqlEmbeddedDatabase extends JpaEmbeddedDatabase {
private final MSSQLServerContainer myContainer; private final MSSQLServerContainer myContainer;
public MsSqlEmbeddedDatabase() { public MsSqlEmbeddedDatabase() {
// azure-sql-edge docker image does not support kernel 6.7+
// as a result, mssql container fails to start most of the time
// mssql/server:2019 image support kernel 6.7+, so use it for amd64 architecture
// See: https://github.com/microsoft/mssql-docker/issues/868
if (DatabaseSupportUtil.canUseMsSql2019()) {
myContainer = new MSSQLServerContainer("mcr.microsoft.com/mssql/server:2019-latest").acceptLicense();
} else {
DockerImageName msSqlImage = DockerImageName.parse("mcr.microsoft.com/azure-sql-edge:latest") DockerImageName msSqlImage = DockerImageName.parse("mcr.microsoft.com/azure-sql-edge:latest")
.asCompatibleSubstituteFor("mcr.microsoft.com/mssql/server"); .asCompatibleSubstituteFor("mcr.microsoft.com/mssql/server");
myContainer = new MSSQLServerContainer(msSqlImage).acceptLicense(); myContainer = new MSSQLServerContainer(msSqlImage).acceptLicense();
}
myContainer.start(); myContainer.start();
super.initialize( super.initialize(
DriverTypeEnum.MSSQL_2012, DriverTypeEnum.MSSQL_2012,

View File

@ -19,8 +19,7 @@
*/ */
package ca.uhn.fhir.jpa.embedded; package ca.uhn.fhir.jpa.embedded;
import org.apache.commons.lang3.StringUtils; import ca.uhn.fhir.jpa.util.DatabaseSupportUtil;
import org.apache.commons.lang3.SystemUtils;
import org.junit.jupiter.api.extension.ConditionEvaluationResult; import org.junit.jupiter.api.extension.ConditionEvaluationResult;
import org.junit.jupiter.api.extension.ExecutionCondition; import org.junit.jupiter.api.extension.ExecutionCondition;
import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ExtensionContext;
@ -33,25 +32,8 @@ public class OracleCondition implements ExecutionCondition {
@Override @Override
public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext theExtensionContext) { public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext theExtensionContext) {
return canUseOracle() return DatabaseSupportUtil.canUseOracle()
? ConditionEvaluationResult.enabled(ENABLED_MSG) ? ConditionEvaluationResult.enabled(ENABLED_MSG)
: ConditionEvaluationResult.disabled(DISABLED_MSG); : ConditionEvaluationResult.disabled(DISABLED_MSG);
} }
public static boolean canUseOracle() {
if (!isMac()) {
return true;
}
return isColimaConfigured();
}
private static boolean isMac() {
return SystemUtils.IS_OS_MAC || SystemUtils.IS_OS_MAC_OSX;
}
private static boolean isColimaConfigured() {
return StringUtils.isNotBlank(System.getenv("TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE"))
&& StringUtils.isNotBlank(System.getenv("DOCKER_HOST"))
&& System.getenv("DOCKER_HOST").contains("colima");
}
} }

View File

@ -0,0 +1,34 @@
package ca.uhn.fhir.jpa.util;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.SystemUtils;
public final class DatabaseSupportUtil {
private DatabaseSupportUtil() {}
public static boolean canUseMsSql2019() {
return isSupportAmd64Architecture();
}
public static boolean canUseOracle() {
return isSupportAmd64Architecture();
}
private static boolean isSupportAmd64Architecture() {
if (!isMac()) {
return true;
}
return isColimaConfigured();
}
private static boolean isMac() {
return SystemUtils.IS_OS_MAC || SystemUtils.IS_OS_MAC_OSX;
}
private static boolean isColimaConfigured() {
return StringUtils.isNotBlank(System.getenv("TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE"))
&& StringUtils.isNotBlank(System.getenv("DOCKER_HOST"))
&& System.getenv("DOCKER_HOST").contains("colima");
}
}

View File

@ -79,25 +79,11 @@
<dependency> <dependency>
<groupId>org.simplejavamail</groupId> <groupId>org.simplejavamail</groupId>
<artifactId>simple-java-mail</artifactId> <artifactId>simple-java-mail</artifactId>
<!-- Excluded in favor of jakarta.activation:jakarta.activation-api -->
<exclusions>
<exclusion>
<groupId>com.sun.activation</groupId>
<artifactId>jakarta.activation</artifactId>
</exclusion>
</exclusions>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.icegreen</groupId> <groupId>com.icegreen</groupId>
<artifactId>greenmail</artifactId> <artifactId>greenmail</artifactId>
<scope>compile</scope> <scope>compile</scope>
<!-- Excluded in favor of jakarta.activation:jakarta.activation-api -->
<exclusions>
<exclusion>
<groupId>com.sun.activation</groupId>
<artifactId>jakarta.activation</artifactId>
</exclusion>
</exclusions>
</dependency> </dependency>
</dependencies> </dependencies>

View File

@ -21,9 +21,9 @@ package ca.uhn.fhir.rest.server.mail;
import jakarta.annotation.Nonnull; import jakarta.annotation.Nonnull;
import org.simplejavamail.api.email.Email; import org.simplejavamail.api.email.Email;
import org.simplejavamail.api.mailer.AsyncResponse;
import java.util.List; import java.util.List;
import java.util.function.Consumer;
public interface IMailSvc { public interface IMailSvc {
void sendMail(@Nonnull List<Email> theEmails); void sendMail(@Nonnull List<Email> theEmails);
@ -31,7 +31,5 @@ public interface IMailSvc {
void sendMail(@Nonnull Email theEmail); void sendMail(@Nonnull Email theEmail);
void sendMail( void sendMail(
@Nonnull Email theEmail, @Nonnull Email theEmail, @Nonnull Runnable theOnSuccess, @Nonnull Consumer<Throwable> theErrorHandler);
@Nonnull Runnable theOnSuccess,
@Nonnull AsyncResponse.ExceptionConsumer theErrorHandler);
} }

View File

@ -20,12 +20,9 @@
package ca.uhn.fhir.rest.server.mail; package ca.uhn.fhir.rest.server.mail;
import jakarta.annotation.Nonnull; import jakarta.annotation.Nonnull;
import org.apache.commons.lang3.Validate;
import org.simplejavamail.MailException; import org.simplejavamail.MailException;
import org.simplejavamail.api.email.Email; import org.simplejavamail.api.email.Email;
import org.simplejavamail.api.email.Recipient; import org.simplejavamail.api.email.Recipient;
import org.simplejavamail.api.mailer.AsyncResponse;
import org.simplejavamail.api.mailer.AsyncResponse.ExceptionConsumer;
import org.simplejavamail.api.mailer.Mailer; import org.simplejavamail.api.mailer.Mailer;
import org.simplejavamail.api.mailer.config.TransportStrategy; import org.simplejavamail.api.mailer.config.TransportStrategy;
import org.simplejavamail.mailer.MailerBuilder; import org.simplejavamail.mailer.MailerBuilder;
@ -33,6 +30,8 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class MailSvc implements IMailSvc { public class MailSvc implements IMailSvc {
@ -42,14 +41,14 @@ public class MailSvc implements IMailSvc {
private final Mailer myMailer; private final Mailer myMailer;
public MailSvc(@Nonnull MailConfig theMailConfig) { public MailSvc(@Nonnull MailConfig theMailConfig) {
Validate.notNull(theMailConfig); Objects.requireNonNull(theMailConfig);
myMailConfig = theMailConfig; myMailConfig = theMailConfig;
myMailer = makeMailer(myMailConfig); myMailer = makeMailer(myMailConfig);
} }
@Override @Override
public void sendMail(@Nonnull List<Email> theEmails) { public void sendMail(@Nonnull List<Email> theEmails) {
Validate.notNull(theEmails); Objects.requireNonNull(theEmails);
theEmails.forEach(theEmail -> send(theEmail, new OnSuccess(theEmail), new ErrorHandler(theEmail))); theEmails.forEach(theEmail -> send(theEmail, new OnSuccess(theEmail), new ErrorHandler(theEmail)));
} }
@ -60,21 +59,23 @@ public class MailSvc implements IMailSvc {
@Override @Override
public void sendMail( public void sendMail(
@Nonnull Email theEmail, @Nonnull Runnable theOnSuccess, @Nonnull ExceptionConsumer theErrorHandler) { @Nonnull Email theEmail, @Nonnull Runnable theOnSuccess, @Nonnull Consumer<Throwable> theErrorHandler) {
send(theEmail, theOnSuccess, theErrorHandler); send(theEmail, theOnSuccess, theErrorHandler);
} }
private void send( private void send(
@Nonnull Email theEmail, @Nonnull Runnable theOnSuccess, @Nonnull ExceptionConsumer theErrorHandler) { @Nonnull Email theEmail, @Nonnull Runnable theOnSuccess, @Nonnull Consumer<Throwable> theErrorHandler) {
Validate.notNull(theEmail); Objects.requireNonNull(theEmail);
Validate.notNull(theOnSuccess); Objects.requireNonNull(theOnSuccess);
Validate.notNull(theErrorHandler); Objects.requireNonNull(theErrorHandler);
try { try {
final AsyncResponse asyncResponse = myMailer.sendMail(theEmail, true); myMailer.sendMail(theEmail, true).whenComplete((result, ex) -> {
if (asyncResponse != null) { if (ex != null) {
asyncResponse.onSuccess(theOnSuccess); theErrorHandler.accept(ex);
asyncResponse.onException(theErrorHandler); } else {
theOnSuccess.run();
} }
});
} catch (MailException e) { } catch (MailException e) {
theErrorHandler.accept(e); theErrorHandler.accept(e);
} }
@ -117,7 +118,7 @@ public class MailSvc implements IMailSvc {
} }
} }
private class ErrorHandler implements ExceptionConsumer { private class ErrorHandler implements Consumer<Throwable> {
private final Email myEmail; private final Email myEmail;
private ErrorHandler(@Nonnull Email theEmail) { private ErrorHandler(@Nonnull Email theEmail) {
@ -125,7 +126,7 @@ public class MailSvc implements IMailSvc {
} }
@Override @Override
public void accept(Exception t) { public void accept(Throwable t) {
ourLog.error("Email not sent" + makeMessage(myEmail), t); ourLog.error("Email not sent" + makeMessage(myEmail), t);
} }
} }

View File

@ -4,6 +4,7 @@ import com.icegreen.greenmail.junit5.GreenMailExtension;
import com.icegreen.greenmail.util.GreenMailUtil; import com.icegreen.greenmail.util.GreenMailUtil;
import com.icegreen.greenmail.util.ServerSetupTest; import com.icegreen.greenmail.util.ServerSetupTest;
import jakarta.annotation.Nonnull; import jakarta.annotation.Nonnull;
import jakarta.mail.internet.MimeMessage;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.api.extension.RegisterExtension;
@ -11,7 +12,6 @@ import org.simplejavamail.MailException;
import org.simplejavamail.api.email.Email; import org.simplejavamail.api.email.Email;
import org.simplejavamail.email.EmailBuilder; import org.simplejavamail.email.EmailBuilder;
import javax.mail.internet.MimeMessage;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
@ -86,13 +86,14 @@ public class MailSvcIT {
@Test @Test
public void testSendMailWithInvalidToAddressExpectErrorHandler() { public void testSendMailWithInvalidToAddressExpectErrorHandler() {
// setup // setup
final Email email = withEmail("xyz"); String invalidEmailAdress = "xyz";
final Email email = withEmail(invalidEmailAdress);
// execute // execute
fixture.sendMail(email, fixture.sendMail(email,
() -> fail("Should not execute on Success"), () -> fail("Should not execute on Success"),
(e) -> { (e) -> {
assertTrue(e instanceof MailException); assertTrue(e instanceof MailException);
assertEquals("Invalid TO address: " + email, e.getMessage()); assertEquals("Invalid TO address: " + invalidEmailAdress, e.getMessage());
}); });
// validate // validate
assertTrue(ourGreenMail.waitForIncomingEmail(1000, 0)); assertTrue(ourGreenMail.waitForIncomingEmail(1000, 0));

34
pom.xml
View File

@ -869,6 +869,7 @@
<developer> <developer>
<id>delopst</id> <id>delopst</id>
<name>Primož Delopst</name> <name>Primož Delopst</name>
<organization>Better</organization>
</developer> </developer>
<developer> <developer>
<id>Zach Smith</id> <id>Zach Smith</id>
@ -1160,27 +1161,38 @@
<dependency> <dependency>
<groupId>org.simplejavamail</groupId> <groupId>org.simplejavamail</groupId>
<artifactId>simple-java-mail</artifactId> <artifactId>simple-java-mail</artifactId>
<version>6.6.1</version> <version>8.11.2</version>
<exclusions> <exclusions>
<exclusion> <exclusion>
<groupId>com.sun.activation</groupId> <groupId>com.github.bbottema</groupId>
<artifactId>jakarta.activation-api</artifactId> <artifactId>jetbrains-runtime-annotations</artifactId>
</exclusion> </exclusion>
<exclusion> <exclusion>
<groupId>com.sun.activation</groupId> <groupId>jakarta.mail</groupId>
<artifactId>jakarta.activation</artifactId> <artifactId>jakarta.mail-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>jakarta.mail</groupId>
<artifactId>jakarta.mail-api</artifactId>
<version>2.1.3</version>
</dependency>
<dependency>
<groupId>com.icegreen</groupId>
<artifactId>greenmail</artifactId>
<version>2.1.0-rc-1</version>
<exclusions>
<exclusion>
<groupId>jakarta.mail</groupId>
<artifactId>jakarta.mail-api</artifactId>
</exclusion> </exclusion>
</exclusions> </exclusions>
</dependency> </dependency>
<dependency>
<groupId>com.icegreen</groupId>
<artifactId>greenmail</artifactId>
<version>1.6.4</version>
</dependency>
<dependency> <dependency>
<groupId>com.icegreen</groupId> <groupId>com.icegreen</groupId>
<artifactId>greenmail-junit5</artifactId> <artifactId>greenmail-junit5</artifactId>
<version>1.6.4</version> <version>2.1.0-rc-1</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<!-- mail end --> <!-- mail end -->