Hacky first-pass of adding ordinal date field to ResourceIndexedSearchParamDate and using it for search

This commit is contained in:
Gary Graham 2020-02-24 19:34:16 -05:00
parent 18d859281e
commit 7f5b3394e0
6 changed files with 120 additions and 35 deletions

View File

@ -262,6 +262,22 @@ public class DateRangeParam implements IQueryParameterAnd<DateParam> {
validateAndSet(myLowerBound, new DateParam(ParamPrefixEnum.LESSTHAN, theUpperBound)); validateAndSet(myLowerBound, new DateParam(ParamPrefixEnum.LESSTHAN, theUpperBound));
return this; return this;
} }
public Integer getLowerBoundAsDateOrdinal() {
if (myLowerBound == null || myLowerBound.getValue() == null) {
return null;
}
Calendar cal = DateUtils.toCalendar(myLowerBound.getValue());
String s = new StringBuilder().append(cal.get(Calendar.YEAR)).append(cal.get(Calendar.MONTH)).append(cal.get(Calendar.DAY_OF_MONTH)).toString();
return Integer.parseInt(s);
}
public Integer getUpperBoundAsDateOrdinal() {
if (myUpperBound == null || myUpperBound.getValue() == null) {
return null;
}
Calendar cal = DateUtils.toCalendar(myUpperBound.getValue());
String s = new StringBuilder().append(cal.get(Calendar.YEAR)).append(cal.get(Calendar.MONTH)).append(cal.get(Calendar.DAY_OF_MONTH)).toString();
return Integer.parseInt(s);
}
public Date getLowerBoundAsInstant() { public Date getLowerBoundAsInstant() {
if (myLowerBound == null || myLowerBound.getValue() == null) { if (myLowerBound == null || myLowerBound.getValue() == null) {
@ -334,6 +350,12 @@ public class DateRangeParam implements IQueryParameterAnd<DateParam> {
Date retVal = myUpperBound.getValue(); Date retVal = myUpperBound.getValue();
//if (myUpperBound.getPrecision().ordinal() == TemporalPrecisionEnum.DAY.ordinal()) {
// DateUtils.setHours(retVal, 23);
// DateUtils.setMinutes(retVal, 59);
// DateUtils.setSeconds(retVal, 59);
// }
if (myUpperBound.getPrecision().ordinal() <= TemporalPrecisionEnum.DAY.ordinal()) { if (myUpperBound.getPrecision().ordinal() <= TemporalPrecisionEnum.DAY.ordinal()) {
Calendar cal = DateUtils.toCalendar(retVal); Calendar cal = DateUtils.toCalendar(retVal);
cal.setTimeZone(TimeZone.getTimeZone("GMT+11:30")); cal.setTimeZone(TimeZone.getTimeZone("GMT+11:30"));

View File

@ -126,7 +126,6 @@ abstract class BasePredicateBuilder {
} }
void addPredicateParamMissing(String theResourceName, String theParamName, boolean theMissing, Join<ResourceTable, ? extends BaseResourceIndexedSearchParam> theJoin) { void addPredicateParamMissing(String theResourceName, String theParamName, boolean theMissing, Join<ResourceTable, ? extends BaseResourceIndexedSearchParam> theJoin) {
myQueryRoot.addPredicate(myBuilder.equal(theJoin.get("myResourceType"), theResourceName)); myQueryRoot.addPredicate(myBuilder.equal(theJoin.get("myResourceType"), theResourceName));
myQueryRoot.addPredicate(myBuilder.equal(theJoin.get("myParamName"), theParamName)); myQueryRoot.addPredicate(myBuilder.equal(theJoin.get("myParamName"), theParamName));
myQueryRoot.addPredicate(myBuilder.equal(theJoin.get("myMissing"), theMissing)); myQueryRoot.addPredicate(myBuilder.equal(theJoin.get("myMissing"), theMissing));

View File

@ -24,6 +24,7 @@ import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
import ca.uhn.fhir.rest.param.DateParam; import ca.uhn.fhir.rest.param.DateParam;
import ca.uhn.fhir.rest.param.DateRangeParam; import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.param.ParamPrefixEnum; import ca.uhn.fhir.rest.param.ParamPrefixEnum;
@ -127,67 +128,102 @@ public class PredicateBuilderDate extends BasePredicateBuilder implements IPredi
return combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, p); return combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, p);
} }
private boolean isNullOrDayPrecision(DateParam theDateParam) {
return theDateParam == null || theDateParam.getPrecision().ordinal() == TemporalPrecisionEnum.DAY.ordinal();
}
private Predicate createPredicateDateFromRange(CriteriaBuilder theBuilder, private Predicate createPredicateDateFromRange(CriteriaBuilder theBuilder,
From<?, ResourceIndexedSearchParamDate> theFrom, From<?, ResourceIndexedSearchParamDate> theFrom,
DateRangeParam theRange, DateRangeParam theRange,
SearchFilterParser.CompareOperation operation) { SearchFilterParser.CompareOperation operation) {
Date lowerBound = theRange.getLowerBoundAsInstant(); Date lowerBoundInstant = theRange.getLowerBoundAsInstant();
Date upperBound = theRange.getUpperBoundAsInstant(); Date upperBoundInstant = theRange.getUpperBoundAsInstant();
DateParam lowerBound = theRange.getLowerBound();
DateParam upperBound = theRange.getUpperBound();
boolean isOrdinalComparison = isNullOrDayPrecision(lowerBound) && isNullOrDayPrecision(upperBound);
Predicate lt = null; Predicate lt = null;
Predicate gt = null; Predicate gt = null;
Predicate lb = null; Predicate lb = null;
Predicate ub = null; Predicate ub = null;
if (operation == SearchFilterParser.CompareOperation.lt) { if (operation == SearchFilterParser.CompareOperation.lt) {
if (lowerBound == null) { if (lowerBoundInstant == null) {
throw new InvalidRequestException("lowerBound value not correctly specified for compare operation"); throw new InvalidRequestException("lowerBound value not correctly specified for compare operation");
} }
lb = theBuilder.lessThan(theFrom.get("myValueLow"), lowerBound); //im like 80% sure this should be ub and not lb, as it is an UPPER bound.
if (isOrdinalComparison) {
lb = theBuilder.lessThan(theFrom.get("myValueLowDateOrdinal"), theRange.getLowerBoundAsDateOrdinal());
} else {
lb = theBuilder.lessThan(theFrom.get("myValueLow"), lowerBoundInstant);
}
} else if (operation == SearchFilterParser.CompareOperation.le) { } else if (operation == SearchFilterParser.CompareOperation.le) {
if (upperBound == null) { if (upperBoundInstant == null) {
throw new InvalidRequestException("upperBound value not correctly specified for compare operation"); throw new InvalidRequestException("upperBound value not correctly specified for compare operation");
} }
lb = theBuilder.lessThanOrEqualTo(theFrom.get("myValueHigh"), upperBound); //im like 80% sure this should be ub and not lb, as it is an UPPER bound.
if (isOrdinalComparison) {
lb = theBuilder.lessThanOrEqualTo(theFrom.get("myValueHighDateOrdinal"), theRange.getUpperBoundAsDateOrdinal());
} else {
lb = theBuilder.lessThanOrEqualTo(theFrom.get("myValueHigh"), upperBoundInstant);
}
} else if (operation == SearchFilterParser.CompareOperation.gt) { } else if (operation == SearchFilterParser.CompareOperation.gt) {
if (upperBound == null) { if (upperBoundInstant == null) {
throw new InvalidRequestException("upperBound value not correctly specified for compare operation"); throw new InvalidRequestException("upperBound value not correctly specified for compare operation");
} }
lb = theBuilder.greaterThan(theFrom.get("myValueHigh"), upperBound); if (isOrdinalComparison) {
} else if (operation == SearchFilterParser.CompareOperation.ge) { lb = theBuilder.greaterThan(theFrom.get("myValueHighDateOrdinal"), theRange.getUpperBoundAsDateOrdinal());
if (lowerBound == null) { } else {
lb = theBuilder.greaterThan(theFrom.get("myValueHigh"), upperBoundInstant);
}
} else if (operation == SearchFilterParser.CompareOperation.ge) {
if (lowerBoundInstant == null) {
throw new InvalidRequestException("lowerBound value not correctly specified for compare operation"); throw new InvalidRequestException("lowerBound value not correctly specified for compare operation");
} }
lb = theBuilder.greaterThanOrEqualTo(theFrom.get("myValueLow"), lowerBound); if (isOrdinalComparison) {
lb = theBuilder.greaterThanOrEqualTo(theFrom.get("myValueLowDateOrdinal"), theRange.getLowerBoundAsDateOrdinal());
} else {
lb = theBuilder.greaterThanOrEqualTo(theFrom.get("myValueLow"), lowerBoundInstant);
}
} else if (operation == SearchFilterParser.CompareOperation.ne) { } else if (operation == SearchFilterParser.CompareOperation.ne) {
if ((lowerBound == null) || if ((lowerBoundInstant == null) ||
(upperBound == null)) { (upperBoundInstant == null)) {
throw new InvalidRequestException("lowerBound and/or upperBound value not correctly specified for compare operation"); throw new InvalidRequestException("lowerBound and/or upperBound value not correctly specified for compare operation");
} }
/*Predicate*/ if (isOrdinalComparison){
lt = theBuilder.lessThanOrEqualTo(theFrom.get("myValueLow"), lowerBound); lt = theBuilder.lessThanOrEqualTo(theFrom.get("myValueLowDateOrdinal"), theRange.getLowerBoundAsDateOrdinal());
/*Predicate*/ gt = theBuilder.greaterThanOrEqualTo(theFrom.get("myValueHighDateOrdinal"), theRange.getUpperBoundAsDateOrdinal());
gt = theBuilder.greaterThanOrEqualTo(theFrom.get("myValueHigh"), upperBound); } else {
lt = theBuilder.lessThanOrEqualTo(theFrom.get("myValueLow"), lowerBoundInstant);
gt = theBuilder.greaterThanOrEqualTo(theFrom.get("myValueHigh"), upperBoundInstant);
}
lb = theBuilder.or(lt, lb = theBuilder.or(lt,
gt); gt);
} else if ((operation == SearchFilterParser.CompareOperation.eq) || } else if ((operation == SearchFilterParser.CompareOperation.eq) || (operation == null)) {
(operation == null)) { if (lowerBoundInstant != null) {
if (lowerBound != null) { if (isOrdinalComparison) {
/*Predicate*/ gt = theBuilder.greaterThanOrEqualTo(theFrom.get("myValueLowDateOrdinal"), theRange.getLowerBoundAsDateOrdinal());
gt = theBuilder.greaterThanOrEqualTo(theFrom.get("myValueLow"), lowerBound); lt = theBuilder.greaterThanOrEqualTo(theFrom.get("myValueHighDateOrdinal"), theRange.getLowerBoundAsDateOrdinal());
/*Predicate*/ //also try a strict equality here.
lt = theBuilder.greaterThanOrEqualTo(theFrom.get("myValueHigh"), lowerBound); }
if (theRange.getLowerBound().getPrefix() == ParamPrefixEnum.STARTS_AFTER || theRange.getLowerBound().getPrefix() == ParamPrefixEnum.EQUAL) { else {
gt = theBuilder.greaterThanOrEqualTo(theFrom.get("myValueLow"), lowerBoundInstant);
lt = theBuilder.greaterThanOrEqualTo(theFrom.get("myValueHigh"), lowerBoundInstant);
}
if (lowerBound.getPrefix() == ParamPrefixEnum.STARTS_AFTER || lowerBound.getPrefix() == ParamPrefixEnum.EQUAL) {
lb = gt; lb = gt;
} else { } else {
lb = theBuilder.or(gt, lt); lb = theBuilder.or(gt, lt);
} }
} }
if (upperBound != null) { if (upperBoundInstant != null) {
/*Predicate*/ if (isOrdinalComparison) {
gt = theBuilder.lessThanOrEqualTo(theFrom.get("myValueLow"), upperBound); gt = theBuilder.lessThanOrEqualTo(theFrom.get("myValueLowDateOrdinal"), theRange.getUpperBoundAsDateOrdinal());
/*Predicate*/ lt = theBuilder.lessThanOrEqualTo(theFrom.get("myValueHighDateOrdinal"), theRange.getUpperBoundAsDateOrdinal());
lt = theBuilder.lessThanOrEqualTo(theFrom.get("myValueHigh"), upperBound); } else {
gt = theBuilder.lessThanOrEqualTo(theFrom.get("myValueLow"), upperBoundInstant);
lt = theBuilder.lessThanOrEqualTo(theFrom.get("myValueHigh"), upperBoundInstant);
}
if (theRange.getUpperBound().getPrefix() == ParamPrefixEnum.ENDS_BEFORE || theRange.getUpperBound().getPrefix() == ParamPrefixEnum.EQUAL) { if (theRange.getUpperBound().getPrefix() == ParamPrefixEnum.ENDS_BEFORE || theRange.getUpperBound().getPrefix() == ParamPrefixEnum.EQUAL) {
ub = lt; ub = lt;
} else { } else {
@ -198,8 +234,10 @@ public class PredicateBuilderDate extends BasePredicateBuilder implements IPredi
throw new InvalidRequestException(String.format("Unsupported operator specified, operator=%s", throw new InvalidRequestException(String.format("Unsupported operator specified, operator=%s",
operation.name())); operation.name()));
} }
if (isOrdinalComparison) {
ourLog.trace("Date range is {} - {}", lowerBound, upperBound); ourLog.trace("Ordinal date range is {} - {} ", theRange.getLowerBoundAsDateOrdinal(), theRange.getUpperBoundAsDateOrdinal());
}
ourLog.trace("Date range is {} - {}", lowerBoundInstant, upperBoundInstant);
if (lb != null && ub != null) { if (lb != null && ub != null) {
return (theBuilder.and(lb, ub)); return (theBuilder.and(lb, ub));

View File

@ -4094,7 +4094,6 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
obs.setId(theId); obs.setId(theId);
obs.setEffective(new DateTimeType(theEffective)); obs.setEffective(new DateTimeType(theEffective));
myObservationDao.update(obs); myObservationDao.update(obs);
ourLog.info("Obs {} has time {}", theId, obs.getEffectiveDateTimeType().getValue().toString()); ourLog.info("Obs {} has time {}", theId, obs.getEffectiveDateTimeType().getValue().toString());
} }

View File

@ -27,7 +27,7 @@
<appender-ref ref="STDOUT" /> <appender-ref ref="STDOUT" />
</logger> </logger>
<logger name="ca.uhn.fhir.jpa.dao" additivity="false" level="info"> <logger name="ca.uhn.fhir.jpa.dao" additivity="false" level="trace">
<appender-ref ref="STDOUT" /> <appender-ref ref="STDOUT" />
</logger> </logger>

View File

@ -29,10 +29,12 @@ import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle; import org.apache.commons.lang3.builder.ToStringStyle;
import org.apache.commons.lang3.time.DateUtils;
import org.hibernate.search.annotations.Field; import org.hibernate.search.annotations.Field;
import org.hl7.fhir.r4.model.DateTimeType; import org.hl7.fhir.r4.model.DateTimeType;
import javax.persistence.*; import javax.persistence.*;
import java.util.Calendar;
import java.util.Date; import java.util.Date;
@Embeddable @Embeddable
@ -54,6 +56,12 @@ public class ResourceIndexedSearchParamDate extends BaseResourceIndexedSearchPar
@Temporal(TemporalType.TIMESTAMP) @Temporal(TemporalType.TIMESTAMP)
@Field @Field
public Date myValueLow; public Date myValueLow;
@Column(name="SP_VALUE_LOW_DATE_ORDINAL")
public int myValueLowDateOrdinal;
@Column(name="SP_VALUE_HIGH_DATE_ORDINAL")
public int myValueHighDateOrdinal;
@Transient @Transient
private transient String myOriginalValue; private transient String myOriginalValue;
@Id @Id
@ -82,9 +90,28 @@ public class ResourceIndexedSearchParamDate extends BaseResourceIndexedSearchPar
setParamName(theParamName); setParamName(theParamName);
setValueLow(theLow); setValueLow(theLow);
setValueHigh(theHigh); setValueHigh(theHigh);
computeValueHighDateOrdinal(theHigh);
computeValueLowDateOrdinal(theLow);
myOriginalValue = theOriginalValue; myOriginalValue = theOriginalValue;
} }
private void computeValueHighDateOrdinal(Date theHigh) {
this.myValueHighDateOrdinal = generateOrdinalDateInteger(theHigh);
}
private int generateOrdinalDateInteger(Date theDate) {
Calendar calendar = DateUtils.toCalendar(theDate);
String ordinalDateString = new StringBuilder()
.append(calendar.get(Calendar.YEAR))
.append(calendar.get(Calendar.MONTH))
.append(calendar.get(Calendar.DAY_OF_MONTH))
.toString();
return Integer.parseInt(ordinalDateString);
}
private void computeValueLowDateOrdinal(Date theLow) {
this.myValueLowDateOrdinal = generateOrdinalDateInteger(theLow);
}
@Override @Override
@PrePersist @PrePersist
public void calculateHashes() { public void calculateHashes() {