(1) Changes to equal and not-equal operator logic for numerics to use exact values per discussions with Grahame and James

(2) Additional unit tests
(3) Changes to tests affected by (1) above
This commit is contained in:
Anthony Sute 2019-11-14 15:05:23 -05:00
parent 2c22b9d7ef
commit d2c0b21773
4 changed files with 242 additions and 45 deletions

View File

@ -1467,29 +1467,9 @@ public class SearchBuilder implements ISearchBuilder {
BigDecimal theValue, BigDecimal theValue,
final Expression<BigDecimal> thePath, final Expression<BigDecimal> thePath,
String invalidMessageName) { String invalidMessageName) {
return createPredicateNumeric(theResourceName,
theParamName,
theFrom,
builder,
theParam,
thePrefix,
theValue,
thePath,
invalidMessageName,
null);
}
private Predicate createPredicateNumeric(String theResourceName,
String theParamName,
From<?, ? extends BaseResourceIndexedSearchParam> theFrom,
CriteriaBuilder builder,
IQueryParameterType theParam,
ParamPrefixEnum thePrefix,
BigDecimal theValue,
final Expression<BigDecimal> thePath,
String invalidMessageName,
SearchFilterParser.CompareOperation operation) {
Predicate num; Predicate num;
// Per discussions with Grahame Grieve and James Agnew on 11/13/19, modified logic for EQUAL and NOT_EQUAL operators below so as to
// use exact value matching. The "fuzz amount" matching is still used with the APPROXIMATE operator.
switch (thePrefix) { switch (thePrefix) {
case GREATERTHAN: case GREATERTHAN:
num = builder.gt(thePath, theValue); num = builder.gt(thePath, theValue);
@ -1503,25 +1483,22 @@ public class SearchBuilder implements ISearchBuilder {
case LESSTHAN_OR_EQUALS: case LESSTHAN_OR_EQUALS:
num = builder.le(thePath, theValue); num = builder.le(thePath, theValue);
break; break;
case APPROXIMATE:
case EQUAL: case EQUAL:
num = builder.equal(thePath, theValue);
break;
case NOT_EQUAL: case NOT_EQUAL:
num = builder.notEqual(thePath, theValue);
break;
case APPROXIMATE:
BigDecimal mul = calculateFuzzAmount(thePrefix, theValue); BigDecimal mul = calculateFuzzAmount(thePrefix, theValue);
BigDecimal low = theValue.subtract(mul, MathContext.DECIMAL64); BigDecimal low = theValue.subtract(mul, MathContext.DECIMAL64);
BigDecimal high = theValue.add(mul, MathContext.DECIMAL64); BigDecimal high = theValue.add(mul, MathContext.DECIMAL64);
Predicate lowPred; Predicate lowPred;
Predicate highPred; Predicate highPred;
if (thePrefix != ParamPrefixEnum.NOT_EQUAL) { lowPred = builder.ge(thePath.as(BigDecimal.class), low);
lowPred = builder.ge(thePath.as(BigDecimal.class), low); highPred = builder.le(thePath.as(BigDecimal.class), high);
highPred = builder.le(thePath.as(BigDecimal.class), high); num = builder.and(lowPred, highPred);
num = builder.and(lowPred, highPred); ourLog.trace("Searching for {} <= val <= {}", low, high);
ourLog.trace("Searching for {} <= val <= {}", low, high);
} else {
// Prefix was "ne", so reverse it!
lowPred = builder.lt(thePath.as(BigDecimal.class), low);
highPred = builder.gt(thePath.as(BigDecimal.class), high);
num = builder.or(lowPred, highPred);
}
break; break;
case ENDS_BEFORE: case ENDS_BEFORE:
case STARTS_AFTER: case STARTS_AFTER:

View File

@ -359,12 +359,12 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
{ {
IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_QUANTITY, new QuantityParam("123", "foo", "bar")).setLoadSynchronous(true)); IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_QUANTITY, new QuantityParam("123", "foo", "bar")).setLoadSynchronous(true));
List<IIdType> list = toUnqualifiedVersionlessIds(found); List<IIdType> list = toUnqualifiedVersionlessIds(found);
assertThat(list, containsInAnyOrder(id3)); assertThat(list, Matchers.empty());
} }
{ {
IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_QUANTITY, new QuantityParam("123.0", "foo", "bar")).setLoadSynchronous(true)); IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_QUANTITY, new QuantityParam("123.0", "foo", "bar")).setLoadSynchronous(true));
List<IIdType> list = toUnqualifiedVersionlessIds(found); List<IIdType> list = toUnqualifiedVersionlessIds(found);
assertThat(list, containsInAnyOrder(id3)); assertThat(list, Matchers.empty());
} }
{ {
IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_QUANTITY, new QuantityParam("123.01", "foo", "bar")).setLoadSynchronous(true)); IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_QUANTITY, new QuantityParam("123.01", "foo", "bar")).setLoadSynchronous(true));
@ -379,12 +379,12 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
{ {
IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_QUANTITY, new QuantityParam("123.02", "foo", "bar")).setLoadSynchronous(true)); IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_QUANTITY, new QuantityParam("123.02", "foo", "bar")).setLoadSynchronous(true));
List<IIdType> list = toUnqualifiedVersionlessIds(found); List<IIdType> list = toUnqualifiedVersionlessIds(found);
assertThat(list, containsInAnyOrder()); assertThat(list, Matchers.empty());
} }
{ {
IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_QUANTITY, new QuantityParam("123.001", "foo", "bar")).setLoadSynchronous(true)); IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_QUANTITY, new QuantityParam("123.001", "foo", "bar")).setLoadSynchronous(true));
List<IIdType> list = toUnqualifiedVersionlessIds(found); List<IIdType> list = toUnqualifiedVersionlessIds(found);
assertThat(list, containsInAnyOrder()); assertThat(list, Matchers.empty());
} }
} }

View File

@ -8,9 +8,7 @@ import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import org.hamcrest.Matchers; import org.hamcrest.Matchers;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.CarePlan; import org.hl7.fhir.r4.model.*;
import org.hl7.fhir.r4.model.DateType;
import org.hl7.fhir.r4.model.Patient;
import org.junit.After; import org.junit.After;
import org.junit.AfterClass; import org.junit.AfterClass;
import org.junit.Before; import org.junit.Before;
@ -723,6 +721,228 @@ public class FhirResourceDaoR4FilterTest extends BaseJpaR4Test {
} }
@Test
public void testNumericComparatorEq() {
RiskAssessment ra1 = new RiskAssessment();
RiskAssessment ra2 = new RiskAssessment();
RiskAssessment.RiskAssessmentPredictionComponent component = new RiskAssessment.RiskAssessmentPredictionComponent();
DecimalType doseNumber = new DecimalType(0.25);
component.setProbability(doseNumber);
ra1.addPrediction(component);
String raId1 = myRiskAssessmentDao.create(ra1).getId().toUnqualifiedVersionless().getValue();
component = new RiskAssessment.RiskAssessmentPredictionComponent();
doseNumber = new DecimalType(0.3);
component.setProbability(doseNumber);
ra2.addPrediction(component);
String raId2 = myRiskAssessmentDao.create(ra2).getId().toUnqualifiedVersionless().getValue();
SearchParameterMap map;
List<String> found;
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Constants.PARAM_FILTER, new StringParam("probability eq 0.25"));
found = toUnqualifiedVersionlessIdValues(myRiskAssessmentDao.search(map));
assertThat(found, containsInAnyOrder(raId1));
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Constants.PARAM_FILTER, new StringParam("probability eq 0.3"));
found = toUnqualifiedVersionlessIdValues(myRiskAssessmentDao.search(map));
assertThat(found, containsInAnyOrder(raId2));
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Constants.PARAM_FILTER, new StringParam("probability eq 0.1"));
found = toUnqualifiedVersionlessIdValues(myRiskAssessmentDao.search(map));
assertThat(found, Matchers.empty());
}
@Test
public void testNumericComparatorNe() {
RiskAssessment ra1 = new RiskAssessment();
RiskAssessment ra2 = new RiskAssessment();
RiskAssessment.RiskAssessmentPredictionComponent component = new RiskAssessment.RiskAssessmentPredictionComponent();
DecimalType doseNumber = new DecimalType(0.25);
component.setProbability(doseNumber);
ra1.addPrediction(component);
String raId1 = myRiskAssessmentDao.create(ra1).getId().toUnqualifiedVersionless().getValue();
component = new RiskAssessment.RiskAssessmentPredictionComponent();
doseNumber = new DecimalType(0.3);
component.setProbability(doseNumber);
ra2.addPrediction(component);
String raId2 = myRiskAssessmentDao.create(ra2).getId().toUnqualifiedVersionless().getValue();
SearchParameterMap map;
List<String> found;
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Constants.PARAM_FILTER, new StringParam("probability ne 0.25"));
found = toUnqualifiedVersionlessIdValues(myRiskAssessmentDao.search(map));
assertThat(found, containsInAnyOrder(raId2));
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Constants.PARAM_FILTER, new StringParam("probability ne 0.3"));
found = toUnqualifiedVersionlessIdValues(myRiskAssessmentDao.search(map));
assertThat(found, containsInAnyOrder(raId1));
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Constants.PARAM_FILTER, new StringParam("probability ne 0.1"));
found = toUnqualifiedVersionlessIdValues(myRiskAssessmentDao.search(map));
assertThat(found, containsInAnyOrder(raId1, raId2));
}
@Test
public void testNumericComparatorGt() {
RiskAssessment ra1 = new RiskAssessment();
RiskAssessment ra2 = new RiskAssessment();
RiskAssessment.RiskAssessmentPredictionComponent component = new RiskAssessment.RiskAssessmentPredictionComponent();
DecimalType doseNumber = new DecimalType(0.25);
component.setProbability(doseNumber);
ra1.addPrediction(component);
String raId1 = myRiskAssessmentDao.create(ra1).getId().toUnqualifiedVersionless().getValue();
component = new RiskAssessment.RiskAssessmentPredictionComponent();
doseNumber = new DecimalType(0.3);
component.setProbability(doseNumber);
ra2.addPrediction(component);
String raId2 = myRiskAssessmentDao.create(ra2).getId().toUnqualifiedVersionless().getValue();
SearchParameterMap map;
List<String> found;
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Constants.PARAM_FILTER, new StringParam("probability gt 0.25"));
found = toUnqualifiedVersionlessIdValues(myRiskAssessmentDao.search(map));
assertThat(found, containsInAnyOrder(raId2));
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Constants.PARAM_FILTER, new StringParam("probability gt 0.3"));
found = toUnqualifiedVersionlessIdValues(myRiskAssessmentDao.search(map));
assertThat(found, Matchers.empty());
}
@Test
public void testNumericComparatorLt() {
RiskAssessment ra1 = new RiskAssessment();
RiskAssessment ra2 = new RiskAssessment();
RiskAssessment.RiskAssessmentPredictionComponent component = new RiskAssessment.RiskAssessmentPredictionComponent();
DecimalType doseNumber = new DecimalType(0.25);
component.setProbability(doseNumber);
ra1.addPrediction(component);
String raId1 = myRiskAssessmentDao.create(ra1).getId().toUnqualifiedVersionless().getValue();
component = new RiskAssessment.RiskAssessmentPredictionComponent();
doseNumber = new DecimalType(0.3);
component.setProbability(doseNumber);
ra2.addPrediction(component);
String raId2 = myRiskAssessmentDao.create(ra2).getId().toUnqualifiedVersionless().getValue();
SearchParameterMap map;
List<String> found;
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Constants.PARAM_FILTER, new StringParam("probability lt 0.3"));
found = toUnqualifiedVersionlessIdValues(myRiskAssessmentDao.search(map));
assertThat(found, containsInAnyOrder(raId1));
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Constants.PARAM_FILTER, new StringParam("probability lt 0.25"));
found = toUnqualifiedVersionlessIdValues(myRiskAssessmentDao.search(map));
assertThat(found, Matchers.empty());
}
@Test
public void testNumericComparatorGe() {
RiskAssessment ra1 = new RiskAssessment();
RiskAssessment ra2 = new RiskAssessment();
RiskAssessment.RiskAssessmentPredictionComponent component = new RiskAssessment.RiskAssessmentPredictionComponent();
DecimalType doseNumber = new DecimalType(0.25);
component.setProbability(doseNumber);
ra1.addPrediction(component);
String raId1 = myRiskAssessmentDao.create(ra1).getId().toUnqualifiedVersionless().getValue();
component = new RiskAssessment.RiskAssessmentPredictionComponent();
doseNumber = new DecimalType(0.3);
component.setProbability(doseNumber);
ra2.addPrediction(component);
String raId2 = myRiskAssessmentDao.create(ra2).getId().toUnqualifiedVersionless().getValue();
SearchParameterMap map;
List<String> found;
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Constants.PARAM_FILTER, new StringParam("probability ge 0.25"));
found = toUnqualifiedVersionlessIdValues(myRiskAssessmentDao.search(map));
assertThat(found, containsInAnyOrder(raId1, raId2));
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Constants.PARAM_FILTER, new StringParam("probability ge 0.3"));
found = toUnqualifiedVersionlessIdValues(myRiskAssessmentDao.search(map));
assertThat(found, containsInAnyOrder(raId2));
}
@Test
public void testNumericComparatorLe() {
RiskAssessment ra1 = new RiskAssessment();
RiskAssessment ra2 = new RiskAssessment();
RiskAssessment.RiskAssessmentPredictionComponent component = new RiskAssessment.RiskAssessmentPredictionComponent();
DecimalType doseNumber = new DecimalType(0.25);
component.setProbability(doseNumber);
ra1.addPrediction(component);
String raId1 = myRiskAssessmentDao.create(ra1).getId().toUnqualifiedVersionless().getValue();
component = new RiskAssessment.RiskAssessmentPredictionComponent();
doseNumber = new DecimalType(0.3);
component.setProbability(doseNumber);
ra2.addPrediction(component);
String raId2 = myRiskAssessmentDao.create(ra2).getId().toUnqualifiedVersionless().getValue();
SearchParameterMap map;
List<String> found;
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Constants.PARAM_FILTER, new StringParam("probability le 0.25"));
found = toUnqualifiedVersionlessIdValues(myRiskAssessmentDao.search(map));
assertThat(found, containsInAnyOrder(raId1));
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Constants.PARAM_FILTER, new StringParam("probability le 0.3"));
found = toUnqualifiedVersionlessIdValues(myRiskAssessmentDao.search(map));
assertThat(found, containsInAnyOrder(raId1, raId2));
}
@AfterClass @AfterClass
public static void afterClassClearContext() { public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest(); TestUtil.clearAllStaticFieldsForUnitTest();

View File

@ -462,12 +462,12 @@ public class FhirResourceDaoR4Test extends BaseJpaR4Test {
{ {
IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_QUANTITY, new QuantityParam("123", "foo", "bar")).setLoadSynchronous(true)); IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_QUANTITY, new QuantityParam("123", "foo", "bar")).setLoadSynchronous(true));
List<IIdType> list = toUnqualifiedVersionlessIds(found); List<IIdType> list = toUnqualifiedVersionlessIds(found);
assertThat(list, containsInAnyOrder(id3)); assertThat(list, Matchers.empty());
} }
{ {
IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_QUANTITY, new QuantityParam("123.0", "foo", "bar")).setLoadSynchronous(true)); IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_QUANTITY, new QuantityParam("123.0", "foo", "bar")).setLoadSynchronous(true));
List<IIdType> list = toUnqualifiedVersionlessIds(found); List<IIdType> list = toUnqualifiedVersionlessIds(found);
assertThat(list, containsInAnyOrder(id3)); assertThat(list, Matchers.empty());
} }
{ {
IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_QUANTITY, new QuantityParam("123.01", "foo", "bar")).setLoadSynchronous(true)); IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_QUANTITY, new QuantityParam("123.01", "foo", "bar")).setLoadSynchronous(true));
@ -482,12 +482,12 @@ public class FhirResourceDaoR4Test extends BaseJpaR4Test {
{ {
IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_QUANTITY, new QuantityParam("123.02", "foo", "bar")).setLoadSynchronous(true)); IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_QUANTITY, new QuantityParam("123.02", "foo", "bar")).setLoadSynchronous(true));
List<IIdType> list = toUnqualifiedVersionlessIds(found); List<IIdType> list = toUnqualifiedVersionlessIds(found);
assertThat(list, containsInAnyOrder()); assertThat(list, Matchers.empty());
} }
{ {
IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_QUANTITY, new QuantityParam("123.001", "foo", "bar")).setLoadSynchronous(true)); IBundleProvider found = myObservationDao.search(new SearchParameterMap(Observation.SP_VALUE_QUANTITY, new QuantityParam("123.001", "foo", "bar")).setLoadSynchronous(true));
List<IIdType> list = toUnqualifiedVersionlessIds(found); List<IIdType> list = toUnqualifiedVersionlessIds(found);
assertThat(list, containsInAnyOrder()); assertThat(list, Matchers.empty());
} }
} }