Compare commits
10 Commits
75b894e9b4
...
d05079da22
Author | SHA1 | Date |
---|---|---|
Michael Buckley | d05079da22 | |
Michael Buckley | 4df01b1945 | |
Michael Buckley | afa1c2f0b6 | |
Michael Buckley | dad7094362 | |
volodymyr-korzh | 6bb72e445d | |
Michael Buckley | 8e5784f934 | |
volodymyr-korzh | fb7571185a | |
Tadgh | 919e2d2405 | |
Thomas Papke | 3f6d1eb29b | |
Tadgh | 377e44b6ca |
|
@ -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!"
|
|
@ -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!"
|
|
@ -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."
|
||||
|
|
@ -1972,7 +1972,7 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
|
|||
* The loop allows us to create multiple combo index joins if there
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
|
@ -2068,7 +2068,10 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
|
|||
* (e.g. <code>?date=gt2024-02-01</code>), etc.
|
||||
*/
|
||||
private boolean validateParamValuesAreValidForComboParam(
|
||||
RequestDetails theRequest, @Nonnull SearchParameterMap theParams, List<String> theComboParamNames) {
|
||||
RequestDetails theRequest,
|
||||
@Nonnull SearchParameterMap theParams,
|
||||
List<String> theComboParamNames,
|
||||
RuntimeSearchParam theComboParam) {
|
||||
boolean paramValuesAreValidForCombo = true;
|
||||
List<List<IQueryParameterType>> paramOrValues = new ArrayList<>(theComboParamNames.size());
|
||||
|
||||
|
@ -2129,6 +2132,19 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
|
|||
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) {
|
||||
|
|
|
@ -366,7 +366,7 @@ public class SearchQueryBuilder {
|
|||
public ComboCondition createOnCondition(DbColumn[] theSourceColumn, DbColumn[] theTargetColumn) {
|
||||
ComboCondition onCondition = ComboCondition.and();
|
||||
for (int i = 0; i < theSourceColumn.length; i += 1) {
|
||||
onCondition.addCondition(BinaryCondition.equalTo(theSourceColumn[0], theTargetColumn[0]));
|
||||
onCondition.addCondition(BinaryCondition.equalTo(theSourceColumn[i], theTargetColumn[i]));
|
||||
}
|
||||
return onCondition;
|
||||
}
|
||||
|
|
|
@ -144,21 +144,7 @@ public class SearchQueryExecutor implements ISearchQueryExecutor {
|
|||
if (myResultSet == null || !myResultSet.hasNext()) {
|
||||
myNext = NO_MORE;
|
||||
} else {
|
||||
Object nextRow = Objects.requireNonNull(myResultSet.next());
|
||||
// We should typically get two columns back, the first is the partition ID and the second
|
||||
// is the resource ID. But if we're doing a count query, we'll get a single column in an array
|
||||
// or maybe even just a single non array value depending on how the platform handles it.
|
||||
if (nextRow instanceof Number) {
|
||||
myNext = ((Number) nextRow).longValue();
|
||||
} else {
|
||||
Object[] nextRowAsArray = (Object[]) nextRow;
|
||||
if (nextRowAsArray.length == 1) {
|
||||
myNext = (Long) nextRowAsArray[0];
|
||||
} else {
|
||||
Integer nextPartitionId = (Integer) nextRowAsArray[0];
|
||||
myNext = (Long) nextRowAsArray[1];
|
||||
}
|
||||
}
|
||||
myNext = getNextPid(myResultSet);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
|
@ -169,6 +155,40 @@ public class SearchQueryExecutor implements ISearchQueryExecutor {
|
|||
}
|
||||
}
|
||||
|
||||
private long getNextPid(ScrollableResultsIterator<Object> theResultSet) {
|
||||
Object nextRow = Objects.requireNonNull(theResultSet.next());
|
||||
// We should typically get two columns back, the first is the partition ID and the second
|
||||
// is the resource ID. But if we're doing a count query, we'll get a single column in an array
|
||||
// or maybe even just a single non array value depending on how the platform handles it.
|
||||
if (nextRow instanceof Number) {
|
||||
return ((Number) nextRow).longValue();
|
||||
} else {
|
||||
Object[] nextRowAsArray = (Object[]) nextRow;
|
||||
if (nextRowAsArray.length == 1) {
|
||||
return (Long) nextRowAsArray[0];
|
||||
} else {
|
||||
int i;
|
||||
// TODO MB add a strategy object to GeneratedSql to describe the result set.
|
||||
// or make SQE generic
|
||||
// Comment to reviewer: this will be cleaner with the next
|
||||
// merge from ja_20240718_pk_schema_selector
|
||||
|
||||
// We have some cases to distinguish:
|
||||
// - res_id
|
||||
// - count
|
||||
// - partition_id, res_id
|
||||
// - res_id, coord-dist
|
||||
// - partition_id, res_id, coord-dist
|
||||
// Assume res_id is first Long in row, and is in first two columns
|
||||
if (nextRowAsArray[0] instanceof Long) {
|
||||
return (long) nextRowAsArray[0];
|
||||
} else {
|
||||
return (long) nextRowAsArray[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static SearchQueryExecutor emptyExecutor() {
|
||||
return NO_VALUE_EXECUTOR;
|
||||
}
|
||||
|
|
|
@ -566,7 +566,8 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
|
|||
for (BaseResourceIndexedSearchParam nextParam : paramsListForCompositePart) {
|
||||
IQueryParameterType nextParamAsClientParam = nextParam.toQueryParameterType();
|
||||
|
||||
if (nextParamAsClientParam instanceof DateParam) {
|
||||
if (theParam.getComboSearchParamType() == ComboSearchParamType.NON_UNIQUE
|
||||
&& nextParamAsClientParam instanceof DateParam) {
|
||||
DateParam date = (DateParam) nextParamAsClientParam;
|
||||
if (date.getPrecision() != TemporalPrecisionEnum.DAY) {
|
||||
continue;
|
||||
|
|
|
@ -70,6 +70,11 @@
|
|||
<artifactId>jakarta.servlet-api</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jakarta.mail</groupId>
|
||||
<artifactId>jakarta.mail-api</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- test dependencies -->
|
||||
<dependency>
|
||||
|
|
|
@ -28,8 +28,8 @@ import org.slf4j.Logger;
|
|||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import javax.mail.internet.InternetAddress;
|
||||
import javax.mail.internet.MimeMessage;
|
||||
import jakarta.mail.internet.InternetAddress;
|
||||
import jakarta.mail.internet.MimeMessage;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
|
|
@ -17,8 +17,8 @@ import org.junit.jupiter.api.extension.RegisterExtension;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.mail.internet.InternetAddress;
|
||||
import javax.mail.internet.MimeMessage;
|
||||
import jakarta.mail.internet.InternetAddress;
|
||||
import jakarta.mail.internet.MimeMessage;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
|
|
@ -26,8 +26,8 @@ import org.junit.jupiter.api.Test;
|
|||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import javax.mail.internet.InternetAddress;
|
||||
import javax.mail.internet.MimeMessage;
|
||||
import jakarta.mail.internet.InternetAddress;
|
||||
import jakarta.mail.internet.MimeMessage;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
|
|
@ -135,10 +135,10 @@ public abstract class BasePartitioningR4Test extends BaseJpaR4SystemTest {
|
|||
addCreateDefaultPartition();
|
||||
addReadDefaultPartition(); // one for search param validation
|
||||
SearchParameter sp = new SearchParameter();
|
||||
sp.setId("SearchParameter/patient-birthdate");
|
||||
sp.setType(Enumerations.SearchParamType.DATE);
|
||||
sp.setCode("birthdate");
|
||||
sp.setExpression("Patient.birthDate");
|
||||
sp.setId("SearchParameter/patient-gender");
|
||||
sp.setType(Enumerations.SearchParamType.TOKEN);
|
||||
sp.setCode("gender");
|
||||
sp.setExpression("Patient.gender");
|
||||
sp.setStatus(Enumerations.PublicationStatus.ACTIVE);
|
||||
sp.addBase("Patient");
|
||||
mySearchParameterDao.update(sp, mySrd);
|
||||
|
@ -156,13 +156,13 @@ public abstract class BasePartitioningR4Test extends BaseJpaR4SystemTest {
|
|||
|
||||
addCreateDefaultPartition();
|
||||
sp = new SearchParameter();
|
||||
sp.setId("SearchParameter/patient-birthdate-unique");
|
||||
sp.setId("SearchParameter/patient-gender-family-unique");
|
||||
sp.setType(Enumerations.SearchParamType.COMPOSITE);
|
||||
sp.setStatus(Enumerations.PublicationStatus.ACTIVE);
|
||||
sp.addBase("Patient");
|
||||
sp.addComponent()
|
||||
.setExpression("Patient")
|
||||
.setDefinition("SearchParameter/patient-birthdate");
|
||||
.setDefinition("SearchParameter/patient-gender");
|
||||
sp.addComponent()
|
||||
.setExpression("Patient")
|
||||
.setDefinition("SearchParameter/patient-family");
|
||||
|
|
|
@ -291,7 +291,7 @@ public class FhirResourceDaoR4ComboNonUniqueParamTest extends BaseComboParamsR4T
|
|||
assertThat(actual).containsExactlyInAnyOrder(id1.toUnqualifiedVersionless().getValue());
|
||||
|
||||
String sql = myCaptureQueriesListener.getSelectQueries().get(0).getSql(true, false);
|
||||
String expected = "SELECT t1.RES_ID FROM HFJ_RESOURCE t1 INNER JOIN HFJ_IDX_CMB_TOK_NU t0 ON (((t1.PARTITION_ID = t0.PARTITION_ID) OR ((t1.PARTITION_ID IS NULL) AND (t0.PARTITION_ID IS NULL))) AND (t1.RES_ID = t0.RES_ID)) INNER JOIN HFJ_SPIDX_DATE t2 ON (((t1.PARTITION_ID = t2.PARTITION_ID) OR ((t1.PARTITION_ID IS NULL) AND (t2.PARTITION_ID IS NULL))) AND (t1.RES_ID = t2.RES_ID)) WHERE ((t0.HASH_COMPLETE = '-2634469377090377342') AND ((t2.HASH_IDENTITY = '5247847184787287691') AND ((t2.SP_VALUE_LOW_DATE_ORDINAL >= '20210202') AND (t2.SP_VALUE_HIGH_DATE_ORDINAL <= '20210202'))))";
|
||||
String expected = "SELECT t1.RES_ID FROM HFJ_RESOURCE t1 INNER JOIN HFJ_IDX_CMB_TOK_NU t0 ON (t1.RES_ID = t0.RES_ID) INNER JOIN HFJ_SPIDX_DATE t2 ON (t1.RES_ID = t2.RES_ID) WHERE ((t0.HASH_COMPLETE = '-2634469377090377342') AND ((t2.HASH_IDENTITY = '5247847184787287691') AND ((t2.SP_VALUE_LOW_DATE_ORDINAL >= '20210202') AND (t2.SP_VALUE_HIGH_DATE_ORDINAL <= '20210202'))))";
|
||||
assertEquals(expected, sql);
|
||||
|
||||
logCapturedMessages();
|
||||
|
@ -323,7 +323,7 @@ public class FhirResourceDaoR4ComboNonUniqueParamTest extends BaseComboParamsR4T
|
|||
assertThat(actual).containsExactlyInAnyOrder(id1.toUnqualifiedVersionless().getValue());
|
||||
|
||||
String sql = myCaptureQueriesListener.getSelectQueries().get(0).getSql(true, false);
|
||||
String expected = "SELECT t0.RES_ID FROM HFJ_IDX_CMB_TOK_NU t0 INNER JOIN HFJ_SPIDX_STRING t1 ON (((t0.PARTITION_ID = t1.PARTITION_ID) OR ((t0.PARTITION_ID IS NULL) AND (t1.PARTITION_ID IS NULL))) AND (t0.RES_ID = t1.RES_ID)) WHERE ((t0.HASH_COMPLETE = '7545664593829342272') AND ((t1.HASH_NORM_PREFIX = '6206712800146298788') AND (t1.SP_VALUE_NORMALIZED LIKE 'JAY%')))";
|
||||
String expected = "SELECT t0.RES_ID FROM HFJ_IDX_CMB_TOK_NU t0 INNER JOIN HFJ_SPIDX_STRING t1 ON (t0.RES_ID = t1.RES_ID) WHERE ((t0.HASH_COMPLETE = '7545664593829342272') AND ((t1.HASH_NORM_PREFIX = '6206712800146298788') AND (t1.SP_VALUE_NORMALIZED LIKE 'JAY%')))";
|
||||
assertEquals(expected, sql);
|
||||
|
||||
logCapturedMessages();
|
||||
|
@ -461,7 +461,7 @@ public class FhirResourceDaoR4ComboNonUniqueParamTest extends BaseComboParamsR4T
|
|||
myCaptureQueriesListener.logSelectQueries();
|
||||
assertThat(actual).contains("Patient/A");
|
||||
|
||||
String expected = "SELECT t0.RES_ID FROM HFJ_IDX_CMB_TOK_NU t0 INNER JOIN HFJ_IDX_CMB_TOK_NU t1 ON (((t0.PARTITION_ID = t1.PARTITION_ID) OR ((t0.PARTITION_ID IS NULL) AND (t1.PARTITION_ID IS NULL))) AND (t0.RES_ID = t1.RES_ID)) WHERE ((t0.HASH_COMPLETE = '822090206952728926') AND (t1.HASH_COMPLETE = '-8088946700286918311'))";
|
||||
String expected = "SELECT t0.RES_ID FROM HFJ_IDX_CMB_TOK_NU t0 INNER JOIN HFJ_IDX_CMB_TOK_NU t1 ON (t0.RES_ID = t1.RES_ID) WHERE ((t0.HASH_COMPLETE = '822090206952728926') AND (t1.HASH_COMPLETE = '-8088946700286918311'))";
|
||||
assertEquals(expected, myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false));
|
||||
|
||||
}
|
||||
|
@ -497,7 +497,7 @@ public class FhirResourceDaoR4ComboNonUniqueParamTest extends BaseComboParamsR4T
|
|||
myCaptureQueriesListener.logSelectQueries();
|
||||
assertThat(actual).contains("Patient/A");
|
||||
|
||||
String expected = "SELECT t0.RES_ID FROM HFJ_IDX_CMB_TOK_NU t0 INNER JOIN HFJ_SPIDX_STRING t1 ON (((t0.PARTITION_ID = t1.PARTITION_ID) OR ((t0.PARTITION_ID IS NULL) AND (t1.PARTITION_ID IS NULL))) AND (t0.RES_ID = t1.RES_ID)) WHERE ((t0.HASH_COMPLETE = '822090206952728926') AND ((t1.HASH_NORM_PREFIX = '-3664262414674370905') AND (t1.SP_VALUE_NORMALIZED LIKE 'JONES%')))";
|
||||
String expected = "SELECT t0.RES_ID FROM HFJ_IDX_CMB_TOK_NU t0 INNER JOIN HFJ_SPIDX_STRING t1 ON (t0.RES_ID = t1.RES_ID) WHERE ((t0.HASH_COMPLETE = '822090206952728926') AND ((t1.HASH_NORM_PREFIX = '-3664262414674370905') AND (t1.SP_VALUE_NORMALIZED LIKE 'JONES%')))";
|
||||
assertEquals(expected, myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false));
|
||||
|
||||
}
|
||||
|
|
|
@ -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.util.JpaParamUtil;
|
||||
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.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.TokenParam;
|
||||
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.Enumerations;
|
||||
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.Observation;
|
||||
import org.hl7.fhir.r4.model.Organization;
|
||||
|
@ -115,6 +116,46 @@ public class FhirResourceDaoR4ComboUniqueParamTest extends BaseComboParamsR4Test
|
|||
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() {
|
||||
SearchParameter sp = new SearchParameter();
|
||||
sp.setId("SearchParameter/coverage-beneficiary");
|
||||
|
@ -276,6 +317,45 @@ public class FhirResourceDaoR4ComboUniqueParamTest extends BaseComboParamsR4Test
|
|||
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() {
|
||||
SearchParameter sp = new SearchParameter();
|
||||
sp.setId("SearchParameter/obs-subject");
|
||||
|
@ -471,11 +551,11 @@ public class FhirResourceDaoR4ComboUniqueParamTest extends BaseComboParamsR4Test
|
|||
public void testDoubleMatchingOnAnd_Search_TwoAndOrValues() {
|
||||
myStorageSettings.setUniqueIndexesCheckedBeforeSave(false);
|
||||
|
||||
createUniqueBirthdateAndGenderSps();
|
||||
createUniqueGenderFamilyComboSp();
|
||||
|
||||
Patient pt1 = new Patient();
|
||||
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();
|
||||
|
||||
// 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.add(Patient.SP_GENDER,
|
||||
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,
|
||||
new DateAndListParam()
|
||||
.addAnd(new DateParam("2011-01-01"), new DateParam( "2011-02-02"))
|
||||
sp.add(Patient.SP_FAMILY,
|
||||
new StringOrListParam()
|
||||
.addOr(new StringParam("Family1")).addOr(new StringParam("Family2"))
|
||||
);
|
||||
IBundleProvider outcome = myPatientDao.search(sp, mySrd);
|
||||
myCaptureQueriesListener.logFirstSelectQueryForCurrentThread();
|
||||
assertThat(toUnqualifiedVersionlessIdValues(outcome)).containsExactlyInAnyOrder(id1);
|
||||
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
|
||||
public void testOrQuery() {
|
||||
myStorageSettings.setAdvancedHSearchIndexing(false);
|
||||
createUniqueBirthdateAndGenderSps();
|
||||
createUniqueGenderFamilyComboSp();
|
||||
|
||||
Patient pt1 = new Patient();
|
||||
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();
|
||||
|
||||
Patient pt2 = new Patient();
|
||||
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();
|
||||
|
||||
myCaptureQueriesListener.clear();
|
||||
|
@ -1184,16 +1269,21 @@ public class FhirResourceDaoR4ComboUniqueParamTest extends BaseComboParamsR4Test
|
|||
SearchParameterMap params = new SearchParameterMap();
|
||||
params.setLoadSynchronousUpTo(100);
|
||||
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();
|
||||
IBundleProvider results = myPatientDao.search(params, mySrd);
|
||||
myCaptureQueriesListener.logFirstSelectQueryForCurrentThread();
|
||||
assertThat(toUnqualifiedVersionlessIdValues(results)).containsExactlyInAnyOrder(id1.getValue(), id2.getValue());
|
||||
|
||||
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();
|
||||
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();
|
||||
|
||||
}
|
||||
|
@ -1201,16 +1291,16 @@ public class FhirResourceDaoR4ComboUniqueParamTest extends BaseComboParamsR4Test
|
|||
@Test
|
||||
public void testSearchSynchronousUsingUniqueComposite() {
|
||||
myStorageSettings.setAdvancedHSearchIndexing(false);
|
||||
createUniqueBirthdateAndGenderSps();
|
||||
createUniqueGenderFamilyComboSp();
|
||||
|
||||
Patient pt1 = new Patient();
|
||||
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();
|
||||
|
||||
Patient pt2 = new Patient();
|
||||
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();
|
||||
|
||||
myCaptureQueriesListener.clear();
|
||||
|
@ -1218,13 +1308,14 @@ public class FhirResourceDaoR4ComboUniqueParamTest extends BaseComboParamsR4Test
|
|||
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"));
|
||||
params.add("family", new StringParam("Family1"));
|
||||
IBundleProvider results = myPatientDao.search(params, mySrd);
|
||||
myCaptureQueriesListener.logFirstSelectQueryForCurrentThread();
|
||||
assertThat(toUnqualifiedVersionlessIdValues(results)).containsExactlyInAnyOrder(id1.getValue());
|
||||
|
||||
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();
|
||||
|
||||
}
|
||||
|
@ -1232,33 +1323,34 @@ public class FhirResourceDaoR4ComboUniqueParamTest extends BaseComboParamsR4Test
|
|||
|
||||
@Test
|
||||
public void testSearchUsingUniqueComposite() {
|
||||
createUniqueBirthdateAndGenderSps();
|
||||
createUniqueGenderFamilyComboSp();
|
||||
|
||||
Patient pt1 = new Patient();
|
||||
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();
|
||||
|
||||
Patient pt2 = new Patient();
|
||||
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();
|
||||
|
||||
myMessages.clear();
|
||||
SearchParameterMap params = new SearchParameterMap();
|
||||
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);
|
||||
String searchId = results.getUuid();
|
||||
assertThat(toUnqualifiedVersionlessIdValues(results)).containsExactlyInAnyOrder(id1);
|
||||
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();
|
||||
|
||||
// Other order
|
||||
myMessages.clear();
|
||||
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"));
|
||||
results = myPatientDao.search(params, mySrd);
|
||||
assertEquals(searchId, results.getUuid());
|
||||
|
@ -1272,16 +1364,17 @@ public class FhirResourceDaoR4ComboUniqueParamTest extends BaseComboParamsR4Test
|
|||
myMessages.clear();
|
||||
params = new SearchParameterMap();
|
||||
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);
|
||||
assertThat(toUnqualifiedVersionlessIdValues(results)).isEmpty();
|
||||
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();
|
||||
params = new SearchParameterMap();
|
||||
params.add("birthdate", new DateParam("2011-01-03"));
|
||||
params.add("family", new StringParam("Family3"));
|
||||
results = myPatientDao.search(params, mySrd);
|
||||
assertThat(toUnqualifiedVersionlessIdValues(results)).isEmpty();
|
||||
// STANDARD QUERY
|
||||
|
@ -1666,7 +1759,7 @@ public class FhirResourceDaoR4ComboUniqueParamTest extends BaseComboParamsR4Test
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testDuplicateUniqueValuesAreRejected() {
|
||||
public void testDuplicateUniqueValuesWithDateAreRejected() {
|
||||
createUniqueBirthdateAndGenderSps();
|
||||
|
||||
Patient pt1 = new Patient();
|
||||
|
@ -1699,13 +1792,75 @@ public class FhirResourceDaoR4ComboUniqueParamTest extends BaseComboParamsR4Test
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testReplaceOneWithAnother() {
|
||||
myStorageSettings.setAdvancedHSearchIndexing(false);
|
||||
public void testDuplicateUniqueValuesWithDateTimeAreRejected() {
|
||||
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();
|
||||
|
||||
Patient pt1 = new Patient();
|
||||
pt1.setGender(Enumerations.AdministrativeGender.MALE);
|
||||
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();
|
||||
assertNotNull(id1);
|
||||
|
||||
|
@ -1714,27 +1869,28 @@ public class FhirResourceDaoR4ComboUniqueParamTest extends BaseComboParamsR4Test
|
|||
pt1 = new Patient();
|
||||
pt1.setId(id1);
|
||||
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();
|
||||
assertNotNull(id1);
|
||||
assertEquals("2", id1.getVersionIdPart());
|
||||
|
||||
Patient pt2 = new Patient();
|
||||
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();
|
||||
|
||||
myMessages.clear();
|
||||
SearchParameterMap params = new SearchParameterMap();
|
||||
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);
|
||||
String searchId = results.getUuid();
|
||||
assertThat(searchId).isNotBlank();
|
||||
assertThat(toUnqualifiedVersionlessIdValues(results)).containsExactlyInAnyOrder(id2.getValue());
|
||||
|
||||
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();
|
||||
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package ca.uhn.fhir.jpa.dao.r4;
|
||||
|
||||
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
|
||||
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
|
||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||
import ca.uhn.fhir.jpa.test.BaseJpaR4Test;
|
||||
import ca.uhn.fhir.rest.api.Constants;
|
||||
|
@ -15,13 +16,17 @@ import org.hl7.fhir.r4.model.SearchParameter;
|
|||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.CsvSource;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
public class FhirResourceDaoR4SearchSqlTest extends BaseJpaR4Test {
|
||||
|
||||
|
@ -37,6 +42,9 @@ public class FhirResourceDaoR4SearchSqlTest extends BaseJpaR4Test {
|
|||
@AfterEach
|
||||
public void after() {
|
||||
myStorageSettings.setTagStorageMode(JpaStorageSettings.DEFAULT_TAG_STORAGE_MODE);
|
||||
myPartitionSettings.setDefaultPartitionId(new PartitionSettings().getDefaultPartitionId());
|
||||
myPartitionSettings.setPartitionIdsInPrimaryKeys(new PartitionSettings().isPartitionIdsInPrimaryKeys());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -54,18 +62,50 @@ public class FhirResourceDaoR4SearchSqlTest extends BaseJpaR4Test {
|
|||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSortJoinIncludesPartitionId() {
|
||||
|
||||
@ParameterizedTest
|
||||
@CsvSource(textBlock = """
|
||||
single param - no hfj_resource, Patient?name=smith , 'SELECT t0.RES_ID FROM HFJ_SPIDX_STRING t0 WHERE ((t0.HASH_NORM_PREFIX = ?) AND (t0.SP_VALUE_NORMALIZED LIKE ?))'
|
||||
single join, Patient?name=smith&active=true ,'SELECT t1.RES_ID FROM HFJ_RESOURCE t1 INNER JOIN HFJ_SPIDX_STRING t0 ON (t1.RES_ID = t0.RES_ID) INNER JOIN HFJ_SPIDX_TOKEN t2 ON (t1.RES_ID = t2.RES_ID) WHERE (((t0.HASH_NORM_PREFIX = ?) AND (t0.SP_VALUE_NORMALIZED LIKE ?)) AND (t2.HASH_VALUE = ?))'
|
||||
not, Encounter?class:not=not-there ,'SELECT t0.RES_ID FROM HFJ_RESOURCE t0 WHERE (((t0.RES_TYPE = ?) AND (t0.RES_DELETED_AT IS NULL)) AND ((t0.RES_ID) NOT IN (SELECT t0.RES_ID FROM HFJ_SPIDX_TOKEN t0 WHERE (t0.HASH_VALUE = ?)) ))'
|
||||
not in chain, Observation?encounter.class:not=not-there ,'SELECT t0.SRC_RESOURCE_ID FROM HFJ_RES_LINK t0 WHERE ((t0.SRC_PATH = ?) AND ((t0.TARGET_RESOURCE_ID) NOT IN (SELECT t0.RES_ID FROM HFJ_SPIDX_TOKEN t0 WHERE (t0.HASH_VALUE = ?)) ))'
|
||||
bare sort, Patient?_sort=name ,'SELECT t0.RES_ID FROM HFJ_RESOURCE t0 LEFT OUTER JOIN HFJ_SPIDX_STRING t1 ON ((t0.RES_ID = t1.RES_ID) AND (t1.HASH_IDENTITY = ?)) WHERE ((t0.RES_TYPE = ?) AND (t0.RES_DELETED_AT IS NULL)) ORDER BY t1.SP_VALUE_NORMALIZED ASC NULLS LAST'
|
||||
""")
|
||||
public void testSqlGeneration(String theComment, String theFhirRestQuery, String theExpectedSql) {
|
||||
// setup
|
||||
myCaptureQueriesListener.clear();
|
||||
SearchParameterMap map = SearchParameterMap.newSynchronous(Patient.SP_ACTIVE, new TokenParam("true"));
|
||||
map.setSort(new SortSpec(Patient.SP_NAME));
|
||||
myPatientDao.search(map);
|
||||
|
||||
// execute
|
||||
myTestDaoSearch.searchForIds(theFhirRestQuery);
|
||||
|
||||
// verify
|
||||
assertEquals(1, myCaptureQueriesListener.countSelectQueries());
|
||||
String sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(false, false);
|
||||
assertEquals("SELECT t1.RES_ID FROM HFJ_RESOURCE t1 INNER JOIN HFJ_SPIDX_TOKEN t0 ON (t1.RES_ID = t0.RES_ID) LEFT OUTER JOIN HFJ_SPIDX_STRING t2 ON ((t1.RES_ID = t2.RES_ID) AND (t2.HASH_IDENTITY = ?)) WHERE (t0.HASH_VALUE = ?) ORDER BY t2.SP_VALUE_NORMALIZED ASC NULLS LAST", sql);
|
||||
|
||||
assertEquals(theExpectedSql, sql, theComment);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@CsvSource(textBlock = """
|
||||
single- no hfj_resource,Patient?name=smith ,'SELECT t0.PARTITION_ID,t0.RES_ID FROM HFJ_SPIDX_STRING t0 WHERE ((t0.HASH_NORM_PREFIX = ?) AND (t0.SP_VALUE_NORMALIZED LIKE ?))'
|
||||
single join, Patient?name=smith&active=true ,'SELECT t1.PARTITION_ID,t1.RES_ID FROM HFJ_RESOURCE t1 INNER JOIN HFJ_SPIDX_STRING t0 ON ((t1.PARTITION_ID = t0.PARTITION_ID) AND (t1.RES_ID = t0.RES_ID)) INNER JOIN HFJ_SPIDX_TOKEN t2 ON ((t1.PARTITION_ID = t2.PARTITION_ID) AND (t1.RES_ID = t2.RES_ID)) WHERE (((t0.HASH_NORM_PREFIX = ?) AND (t0.SP_VALUE_NORMALIZED LIKE ?)) AND (t2.HASH_VALUE = ?))'
|
||||
not, Encounter?class:not=not-there ,'SELECT t0.PARTITION_ID,t0.RES_ID FROM HFJ_RESOURCE t0 WHERE (((t0.RES_TYPE = ?) AND (t0.RES_DELETED_AT IS NULL)) AND ((t0.PARTITION_ID,t0.RES_ID) NOT IN (SELECT t0.PARTITION_ID,t0.RES_ID FROM HFJ_SPIDX_TOKEN t0 WHERE (t0.HASH_VALUE = ?)) ))'
|
||||
not in chain, Observation?encounter.class:not=not-there ,'SELECT t0.PARTITION_ID,t0.SRC_RESOURCE_ID FROM HFJ_RES_LINK t0 WHERE ((t0.SRC_PATH = ?) AND ((t0.TARGET_RES_PARTITION_ID,t0.TARGET_RESOURCE_ID) NOT IN (SELECT t0.PARTITION_ID,t0.RES_ID FROM HFJ_SPIDX_TOKEN t0 WHERE (t0.HASH_VALUE = ?)) ))'
|
||||
bare sort, Patient?_sort=name ,'SELECT t0.PARTITION_ID,t0.RES_ID FROM HFJ_RESOURCE t0 LEFT OUTER JOIN HFJ_SPIDX_STRING t1 ON ((t0.PARTITION_ID = t1.PARTITION_ID) AND (t0.RES_ID = t1.RES_ID) AND (t1.HASH_IDENTITY = ?)) WHERE ((t0.RES_TYPE = ?) AND (t0.RES_DELETED_AT IS NULL)) ORDER BY t1.SP_VALUE_NORMALIZED ASC NULLS LAST'
|
||||
""")
|
||||
public void testSqlGenerationWithPartitionJoins(String theComment, String theFhirRestQuery, String theExpectedSql) {
|
||||
// setup
|
||||
myPartitionSettings.setDefaultPartitionId(0);
|
||||
myPartitionSettings.setPartitionIdsInPrimaryKeys(true);
|
||||
myCaptureQueriesListener.clear();
|
||||
|
||||
// execute
|
||||
myTestDaoSearch.searchForIds(theFhirRestQuery);
|
||||
|
||||
// verify
|
||||
assertEquals(1, myCaptureQueriesListener.countSelectQueries());
|
||||
String sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(false, false);
|
||||
assertEquals(theExpectedSql, sql, theComment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Two regular search params - Should use HFJ_RESOURCE as root
|
||||
*/
|
||||
|
|
|
@ -413,7 +413,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
|
|||
p.getMeta().addTag("http://system", "code", "diisplay");
|
||||
p.addName().setFamily("FAM");
|
||||
p.addIdentifier().setSystem("system").setValue("value");
|
||||
p.setBirthDateElement(new DateType("2020-01-01"));
|
||||
p.setGender(Enumerations.AdministrativeGender.MALE);
|
||||
p.getManagingOrganization().setReferenceElement(orgId);
|
||||
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.addName().setFamily("FAM");
|
||||
p.addIdentifier().setSystem("system").setValue("value");
|
||||
p.setBirthDate(new Date());
|
||||
p.setGender(Enumerations.AdministrativeGender.MALE);
|
||||
p.getManagingOrganization().setReferenceElement(orgId);
|
||||
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.addName().setFamily("FAM");
|
||||
p.addIdentifier().setSystem("system").setValue("value");
|
||||
p.setBirthDate(new Date());
|
||||
p.setGender(Enumerations.AdministrativeGender.MALE);
|
||||
p.getManagingOrganization().setReference(org.getId());
|
||||
input.addEntry()
|
||||
.setFullUrl(p.getId())
|
||||
|
@ -2541,14 +2541,14 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
|
|||
public void testSearch_UniqueParam_SearchAllPartitions() {
|
||||
createUniqueComboSp();
|
||||
|
||||
IIdType id = createPatient(withPartition(1), withBirthdate("2020-01-01"), withFamily("FAM"));
|
||||
IIdType id = createPatient(withPartition(1), withGender("male"), withFamily("FAM"));
|
||||
|
||||
addReadAllPartitions();
|
||||
|
||||
myCaptureQueriesListener.clear();
|
||||
SearchParameterMap map = new SearchParameterMap();
|
||||
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);
|
||||
IBundleProvider results = myPatientDao.search(map, mySrd);
|
||||
List<IIdType> ids = toUnqualifiedVersionlessIds(results);
|
||||
|
@ -2558,7 +2558,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
|
|||
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
|
||||
ourLog.info("Search SQL:\n{}", searchSql);
|
||||
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() {
|
||||
createUniqueComboSp();
|
||||
|
||||
IIdType id = createPatient(withPartition(1), withBirthdate("2020-01-01"), withFamily("FAM"));
|
||||
IIdType id = createPatient(withPartition(1), withGender("male"), withFamily("FAM"));
|
||||
|
||||
addReadPartition(1);
|
||||
myCaptureQueriesListener.clear();
|
||||
SearchParameterMap map = new SearchParameterMap();
|
||||
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);
|
||||
IBundleProvider results = myPatientDao.search(map, mySrd);
|
||||
List<IIdType> ids = toUnqualifiedVersionlessIds(results);
|
||||
|
@ -2582,13 +2582,13 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
|
|||
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
|
||||
ourLog.info("Search SQL:\n{}", searchSql);
|
||||
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
|
||||
addReadPartition(2);
|
||||
myCaptureQueriesListener.clear();
|
||||
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);
|
||||
results = myPatientDao.search(map, mySrd);
|
||||
ids = toUnqualifiedVersionlessIds(results);
|
||||
|
@ -2661,7 +2661,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
|
|||
public void testSearch_RefParam_TargetPid_SearchOnePartition() {
|
||||
createUniqueComboSp();
|
||||
|
||||
IIdType patientId = createPatient(withPartition(myPartitionId), withBirthdate("2020-01-01"));
|
||||
IIdType patientId = createPatient(withPartition(myPartitionId), withGender("male"));
|
||||
IIdType observationId = createObservation(withPartition(myPartitionId), withSubject(patientId));
|
||||
|
||||
addReadPartition(myPartitionId);
|
||||
|
@ -2698,7 +2698,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
|
|||
public void testSearch_RefParam_TargetPid_SearchDefaultPartition() {
|
||||
createUniqueComboSp();
|
||||
|
||||
IIdType patientId = createPatient(withPartition(null), withBirthdate("2020-01-01"));
|
||||
IIdType patientId = createPatient(withPartition(null), withGender("male"));
|
||||
IIdType observationId = createObservation(withPartition(null), withSubject(patientId));
|
||||
|
||||
addReadDefaultPartition();
|
||||
|
@ -2735,7 +2735,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
|
|||
public void testSearch_RefParam_TargetForcedId_SearchOnePartition() {
|
||||
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));
|
||||
|
||||
addReadPartition(myPartitionId);
|
||||
|
@ -2805,7 +2805,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
|
|||
public void testSearch_RefParam_TargetForcedId_SearchDefaultPartition() {
|
||||
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));
|
||||
|
||||
addReadDefaultPartition();
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
package ca.uhn.fhir.jpa.embedded;
|
||||
|
||||
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.util.VersionEnum;
|
||||
import org.junit.jupiter.api.extension.AfterAllCallback;
|
||||
|
@ -54,7 +55,7 @@ public class HapiEmbeddedDatabasesExtension implements AfterAllCallback {
|
|||
myEmbeddedDatabases.add(new H2EmbeddedDatabase());
|
||||
myEmbeddedDatabases.add(new PostgresEmbeddedDatabase());
|
||||
myEmbeddedDatabases.add(new MsSqlEmbeddedDatabase());
|
||||
if (OracleCondition.canUseOracle()) {
|
||||
if (DatabaseSupportUtil.canUseOracle()) {
|
||||
myEmbeddedDatabases.add(new OracleEmbeddedDatabase());
|
||||
} else {
|
||||
String message =
|
||||
|
@ -136,7 +137,7 @@ public class HapiEmbeddedDatabasesExtension implements AfterAllCallback {
|
|||
arguments.add(Arguments.of(DriverTypeEnum.POSTGRES_9_4));
|
||||
arguments.add(Arguments.of(DriverTypeEnum.MSSQL_2012));
|
||||
|
||||
if (OracleCondition.canUseOracle()) {
|
||||
if (DatabaseSupportUtil.canUseOracle()) {
|
||||
arguments.add(Arguments.of(DriverTypeEnum.ORACLE_12C));
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
package ca.uhn.fhir.jpa.embedded;
|
||||
|
||||
import ca.uhn.fhir.jpa.migrate.DriverTypeEnum;
|
||||
import ca.uhn.fhir.jpa.util.DatabaseSupportUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.testcontainers.containers.MSSQLServerContainer;
|
||||
|
@ -43,9 +44,19 @@ public class MsSqlEmbeddedDatabase extends JpaEmbeddedDatabase {
|
|||
private final MSSQLServerContainer myContainer;
|
||||
|
||||
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")
|
||||
.asCompatibleSubstituteFor("mcr.microsoft.com/mssql/server");
|
||||
myContainer = new MSSQLServerContainer(msSqlImage).acceptLicense();
|
||||
}
|
||||
|
||||
myContainer.start();
|
||||
super.initialize(
|
||||
DriverTypeEnum.MSSQL_2012,
|
||||
|
|
|
@ -19,8 +19,7 @@
|
|||
*/
|
||||
package ca.uhn.fhir.jpa.embedded;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import ca.uhn.fhir.jpa.util.DatabaseSupportUtil;
|
||||
import org.junit.jupiter.api.extension.ConditionEvaluationResult;
|
||||
import org.junit.jupiter.api.extension.ExecutionCondition;
|
||||
import org.junit.jupiter.api.extension.ExtensionContext;
|
||||
|
@ -33,25 +32,8 @@ public class OracleCondition implements ExecutionCondition {
|
|||
|
||||
@Override
|
||||
public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext theExtensionContext) {
|
||||
return canUseOracle()
|
||||
return DatabaseSupportUtil.canUseOracle()
|
||||
? ConditionEvaluationResult.enabled(ENABLED_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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -79,25 +79,11 @@
|
|||
<dependency>
|
||||
<groupId>org.simplejavamail</groupId>
|
||||
<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>
|
||||
<groupId>com.icegreen</groupId>
|
||||
<artifactId>greenmail</artifactId>
|
||||
<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>
|
||||
</dependencies>
|
||||
|
||||
|
|
|
@ -21,9 +21,9 @@ package ca.uhn.fhir.rest.server.mail;
|
|||
|
||||
import jakarta.annotation.Nonnull;
|
||||
import org.simplejavamail.api.email.Email;
|
||||
import org.simplejavamail.api.mailer.AsyncResponse;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public interface IMailSvc {
|
||||
void sendMail(@Nonnull List<Email> theEmails);
|
||||
|
@ -31,7 +31,5 @@ public interface IMailSvc {
|
|||
void sendMail(@Nonnull Email theEmail);
|
||||
|
||||
void sendMail(
|
||||
@Nonnull Email theEmail,
|
||||
@Nonnull Runnable theOnSuccess,
|
||||
@Nonnull AsyncResponse.ExceptionConsumer theErrorHandler);
|
||||
@Nonnull Email theEmail, @Nonnull Runnable theOnSuccess, @Nonnull Consumer<Throwable> theErrorHandler);
|
||||
}
|
||||
|
|
|
@ -20,12 +20,9 @@
|
|||
package ca.uhn.fhir.rest.server.mail;
|
||||
|
||||
import jakarta.annotation.Nonnull;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.simplejavamail.MailException;
|
||||
import org.simplejavamail.api.email.Email;
|
||||
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.config.TransportStrategy;
|
||||
import org.simplejavamail.mailer.MailerBuilder;
|
||||
|
@ -33,6 +30,8 @@ import org.slf4j.Logger;
|
|||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class MailSvc implements IMailSvc {
|
||||
|
@ -42,14 +41,14 @@ public class MailSvc implements IMailSvc {
|
|||
private final Mailer myMailer;
|
||||
|
||||
public MailSvc(@Nonnull MailConfig theMailConfig) {
|
||||
Validate.notNull(theMailConfig);
|
||||
Objects.requireNonNull(theMailConfig);
|
||||
myMailConfig = theMailConfig;
|
||||
myMailer = makeMailer(myMailConfig);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMail(@Nonnull List<Email> theEmails) {
|
||||
Validate.notNull(theEmails);
|
||||
Objects.requireNonNull(theEmails);
|
||||
theEmails.forEach(theEmail -> send(theEmail, new OnSuccess(theEmail), new ErrorHandler(theEmail)));
|
||||
}
|
||||
|
||||
|
@ -60,21 +59,23 @@ public class MailSvc implements IMailSvc {
|
|||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
|
||||
private void send(
|
||||
@Nonnull Email theEmail, @Nonnull Runnable theOnSuccess, @Nonnull ExceptionConsumer theErrorHandler) {
|
||||
Validate.notNull(theEmail);
|
||||
Validate.notNull(theOnSuccess);
|
||||
Validate.notNull(theErrorHandler);
|
||||
@Nonnull Email theEmail, @Nonnull Runnable theOnSuccess, @Nonnull Consumer<Throwable> theErrorHandler) {
|
||||
Objects.requireNonNull(theEmail);
|
||||
Objects.requireNonNull(theOnSuccess);
|
||||
Objects.requireNonNull(theErrorHandler);
|
||||
try {
|
||||
final AsyncResponse asyncResponse = myMailer.sendMail(theEmail, true);
|
||||
if (asyncResponse != null) {
|
||||
asyncResponse.onSuccess(theOnSuccess);
|
||||
asyncResponse.onException(theErrorHandler);
|
||||
myMailer.sendMail(theEmail, true).whenComplete((result, ex) -> {
|
||||
if (ex != null) {
|
||||
theErrorHandler.accept(ex);
|
||||
} else {
|
||||
theOnSuccess.run();
|
||||
}
|
||||
});
|
||||
} catch (MailException 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 ErrorHandler(@Nonnull Email theEmail) {
|
||||
|
@ -125,7 +126,7 @@ public class MailSvc implements IMailSvc {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void accept(Exception t) {
|
||||
public void accept(Throwable t) {
|
||||
ourLog.error("Email not sent" + makeMessage(myEmail), t);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import com.icegreen.greenmail.junit5.GreenMailExtension;
|
|||
import com.icegreen.greenmail.util.GreenMailUtil;
|
||||
import com.icegreen.greenmail.util.ServerSetupTest;
|
||||
import jakarta.annotation.Nonnull;
|
||||
import jakarta.mail.internet.MimeMessage;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
|
@ -11,7 +12,6 @@ import org.simplejavamail.MailException;
|
|||
import org.simplejavamail.api.email.Email;
|
||||
import org.simplejavamail.email.EmailBuilder;
|
||||
|
||||
import javax.mail.internet.MimeMessage;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
|
@ -86,13 +86,14 @@ public class MailSvcIT {
|
|||
@Test
|
||||
public void testSendMailWithInvalidToAddressExpectErrorHandler() {
|
||||
// setup
|
||||
final Email email = withEmail("xyz");
|
||||
String invalidEmailAdress = "xyz";
|
||||
final Email email = withEmail(invalidEmailAdress);
|
||||
// execute
|
||||
fixture.sendMail(email,
|
||||
() -> fail("Should not execute on Success"),
|
||||
(e) -> {
|
||||
assertTrue(e instanceof MailException);
|
||||
assertEquals("Invalid TO address: " + email, e.getMessage());
|
||||
assertEquals("Invalid TO address: " + invalidEmailAdress, e.getMessage());
|
||||
});
|
||||
// validate
|
||||
assertTrue(ourGreenMail.waitForIncomingEmail(1000, 0));
|
||||
|
|
34
pom.xml
34
pom.xml
|
@ -869,6 +869,7 @@
|
|||
<developer>
|
||||
<id>delopst</id>
|
||||
<name>Primož Delopst</name>
|
||||
<organization>Better</organization>
|
||||
</developer>
|
||||
<developer>
|
||||
<id>Zach Smith</id>
|
||||
|
@ -1160,27 +1161,38 @@
|
|||
<dependency>
|
||||
<groupId>org.simplejavamail</groupId>
|
||||
<artifactId>simple-java-mail</artifactId>
|
||||
<version>6.6.1</version>
|
||||
<version>8.11.2</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>com.sun.activation</groupId>
|
||||
<artifactId>jakarta.activation-api</artifactId>
|
||||
<groupId>com.github.bbottema</groupId>
|
||||
<artifactId>jetbrains-runtime-annotations</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>com.sun.activation</groupId>
|
||||
<artifactId>jakarta.activation</artifactId>
|
||||
<groupId>jakarta.mail</groupId>
|
||||
<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>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.icegreen</groupId>
|
||||
<artifactId>greenmail</artifactId>
|
||||
<version>1.6.4</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.icegreen</groupId>
|
||||
<artifactId>greenmail-junit5</artifactId>
|
||||
<version>1.6.4</version>
|
||||
<version>2.1.0-rc-1</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<!-- mail end -->
|
||||
|
|
Loading…
Reference in New Issue