Use integers for date ordinals (#3346)
* Use integers for date ordinals * Add changelog * Adjust changelog
This commit is contained in:
parent
c6122bcb1b
commit
058e53616f
|
@ -242,27 +242,27 @@ public final class DateUtils {
|
|||
public static Pair<String, String> getCompletedDate(String theIncompleteDateStr) {
|
||||
|
||||
if (StringUtils.isBlank(theIncompleteDateStr))
|
||||
return new ImmutablePair<String, String>(null, null);
|
||||
return new ImmutablePair<>(null, null);
|
||||
|
||||
String lbStr, upStr;
|
||||
// YYYY only, return the last day of the year
|
||||
if (theIncompleteDateStr.length() == 4) {
|
||||
lbStr = theIncompleteDateStr + "-01-01"; // first day of the year
|
||||
upStr = theIncompleteDateStr + "-12-31"; // last day of the year
|
||||
return new ImmutablePair<String, String>(lbStr, upStr);
|
||||
return new ImmutablePair<>(lbStr, upStr);
|
||||
}
|
||||
|
||||
// Not YYYY-MM, no change
|
||||
if (theIncompleteDateStr.length() != 7)
|
||||
return new ImmutablePair<String, String>(theIncompleteDateStr, theIncompleteDateStr);
|
||||
return new ImmutablePair<>(theIncompleteDateStr, theIncompleteDateStr);
|
||||
|
||||
// YYYY-MM Only
|
||||
Date lb=null;
|
||||
Date lb;
|
||||
try {
|
||||
// first day of the month
|
||||
lb = new SimpleDateFormat("yyyy-MM-dd").parse(theIncompleteDateStr+"-01");
|
||||
} catch (ParseException e) {
|
||||
return new ImmutablePair<String, String>(theIncompleteDateStr, theIncompleteDateStr);
|
||||
return new ImmutablePair<>(theIncompleteDateStr, theIncompleteDateStr);
|
||||
}
|
||||
|
||||
// last day of the month
|
||||
|
@ -278,7 +278,7 @@ public final class DateUtils {
|
|||
lbStr = new SimpleDateFormat("yyyy-MM-dd").format(lb);
|
||||
upStr = new SimpleDateFormat("yyyy-MM-dd").format(ub);
|
||||
|
||||
return new ImmutablePair<String, String>(lbStr, upStr);
|
||||
return new ImmutablePair<>(lbStr, upStr);
|
||||
}
|
||||
|
||||
public static Date getEndOfDay(Date theDate) {
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
type: fix
|
||||
issue: 3346
|
||||
title: "When searching for date search parameters on Postgres, ordinals could sometimes be
|
||||
represented as strings, causing a search failure. This has been corrected."
|
|
@ -404,7 +404,7 @@ public class QueryStack {
|
|||
List<Condition> codePredicates = new ArrayList<>();
|
||||
|
||||
for (IQueryParameterType nextOr : theList) {
|
||||
Condition p = predicateBuilder.createPredicateDateWithoutIdentityPredicate(nextOr, predicateBuilder, theOperation);
|
||||
Condition p = predicateBuilder.createPredicateDateWithoutIdentityPredicate(nextOr, theOperation);
|
||||
codePredicates.add(p);
|
||||
}
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ import ca.uhn.fhir.rest.param.DateRangeParam;
|
|||
import ca.uhn.fhir.rest.param.ParamPrefixEnum;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import ca.uhn.fhir.util.DateUtils;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.healthmarketscience.sqlbuilder.ComboCondition;
|
||||
import com.healthmarketscience.sqlbuilder.Condition;
|
||||
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbColumn;
|
||||
|
@ -48,7 +48,6 @@ public class DatePredicateBuilder extends BaseSearchParamPredicateBuilder {
|
|||
private final DbColumn myColumnValueLow;
|
||||
private final DbColumn myColumnValueLowDateOrdinal;
|
||||
private final DbColumn myColumnValueHighDateOrdinal;
|
||||
|
||||
@Autowired
|
||||
private DaoConfig myDaoConfig;
|
||||
|
||||
|
@ -64,9 +63,12 @@ public class DatePredicateBuilder extends BaseSearchParamPredicateBuilder {
|
|||
myColumnValueHighDateOrdinal = getTable().addColumn("SP_VALUE_HIGH_DATE_ORDINAL");
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void setDaoConfigForUnitTest(DaoConfig theDaoConfig) {
|
||||
myDaoConfig = theDaoConfig;
|
||||
}
|
||||
|
||||
public Condition createPredicateDateWithoutIdentityPredicate(IQueryParameterType theParam,
|
||||
DatePredicateBuilder theFrom,
|
||||
SearchFilterParser.CompareOperation theOperation) {
|
||||
|
||||
Condition p;
|
||||
|
@ -77,17 +79,14 @@ public class DatePredicateBuilder extends BaseSearchParamPredicateBuilder {
|
|||
date = new DateParam(ParamPrefixEnum.EQUAL, date.getValueAsString());
|
||||
}
|
||||
DateRangeParam range = new DateRangeParam(date);
|
||||
p = createPredicateDateFromRange(theFrom, range, theOperation);
|
||||
p = createPredicateDateFromRange(range, theOperation);
|
||||
} else {
|
||||
// TODO: handle missing date param?
|
||||
p = null;
|
||||
}
|
||||
} else if (theParam instanceof DateRangeParam) {
|
||||
DateRangeParam range = (DateRangeParam) theParam;
|
||||
p = createPredicateDateFromRange(
|
||||
theFrom,
|
||||
range,
|
||||
theOperation);
|
||||
p = createPredicateDateFromRange(range, theOperation);
|
||||
} else {
|
||||
throw new IllegalArgumentException(Msg.code(1251) + "Invalid token type: " + theParam.getClass());
|
||||
}
|
||||
|
@ -95,8 +94,7 @@ public class DatePredicateBuilder extends BaseSearchParamPredicateBuilder {
|
|||
return p;
|
||||
}
|
||||
|
||||
private Condition createPredicateDateFromRange(DatePredicateBuilder theFrom,
|
||||
DateRangeParam theRange,
|
||||
private Condition createPredicateDateFromRange(DateRangeParam theRange,
|
||||
SearchFilterParser.CompareOperation theOperation) {
|
||||
|
||||
|
||||
|
@ -129,8 +127,7 @@ public class DatePredicateBuilder extends BaseSearchParamPredicateBuilder {
|
|||
genericLowerBound = lowerBoundAsOrdinal;
|
||||
genericUpperBound = upperBoundAsOrdinal;
|
||||
if (upperBound != null && upperBound.getPrecision().ordinal() <= TemporalPrecisionEnum.MONTH.ordinal()) {
|
||||
genericUpperBound = DateUtils.getCompletedDate(upperBound.getValueAsString()).getRight().replace("-", "");
|
||||
|
||||
genericUpperBound = Integer.parseInt(DateUtils.getCompletedDate(upperBound.getValueAsString()).getRight().replace("-", ""));
|
||||
}
|
||||
} else {
|
||||
lowValueField = DatePredicateBuilder.ColumnEnum.LOW;
|
||||
|
@ -138,7 +135,7 @@ public class DatePredicateBuilder extends BaseSearchParamPredicateBuilder {
|
|||
genericLowerBound = lowerBoundInstant;
|
||||
genericUpperBound = upperBoundInstant;
|
||||
if (upperBound != null && upperBound.getPrecision().ordinal() <= TemporalPrecisionEnum.MONTH.ordinal()) {
|
||||
String theCompleteDateStr = DateUtils.getCompletedDate(upperBound.getValueAsString()).getRight().replace("-", "");
|
||||
String theCompleteDateStr = DateUtils.getCompletedDate(upperBound.getValueAsString()).getRight().replace("-", "");
|
||||
genericUpperBound = DateUtils.parseDate(theCompleteDateStr);
|
||||
}
|
||||
}
|
||||
|
@ -146,14 +143,14 @@ public class DatePredicateBuilder extends BaseSearchParamPredicateBuilder {
|
|||
if (theOperation == SearchFilterParser.CompareOperation.lt || theOperation == SearchFilterParser.CompareOperation.le) {
|
||||
// use lower bound first
|
||||
if (lowerBoundInstant != null) {
|
||||
lb = theFrom.createPredicate(lowValueField, ParamPrefixEnum.LESSTHAN_OR_EQUALS, genericLowerBound);
|
||||
lb = this.createPredicate(lowValueField, ParamPrefixEnum.LESSTHAN_OR_EQUALS, genericLowerBound);
|
||||
if (myDaoConfig.isAccountForDateIndexNulls()) {
|
||||
lb = ComboCondition.or(lb, theFrom.createPredicate(highValueField, ParamPrefixEnum.LESSTHAN_OR_EQUALS, genericLowerBound));
|
||||
lb = ComboCondition.or(lb, this.createPredicate(highValueField, ParamPrefixEnum.LESSTHAN_OR_EQUALS, genericLowerBound));
|
||||
}
|
||||
} else if (upperBoundInstant != null) {
|
||||
ub = theFrom.createPredicate(lowValueField, ParamPrefixEnum.LESSTHAN_OR_EQUALS, genericUpperBound);
|
||||
ub = this.createPredicate(lowValueField, ParamPrefixEnum.LESSTHAN_OR_EQUALS, genericUpperBound);
|
||||
if (myDaoConfig.isAccountForDateIndexNulls()) {
|
||||
ub = ComboCondition.or(ub, theFrom.createPredicate(highValueField, ParamPrefixEnum.LESSTHAN_OR_EQUALS, genericUpperBound));
|
||||
ub = ComboCondition.or(ub, this.createPredicate(highValueField, ParamPrefixEnum.LESSTHAN_OR_EQUALS, genericUpperBound));
|
||||
}
|
||||
} else {
|
||||
throw new InvalidRequestException(Msg.code(1252) + "lowerBound and upperBound value not correctly specified for comparing " + theOperation);
|
||||
|
@ -161,14 +158,14 @@ public class DatePredicateBuilder extends BaseSearchParamPredicateBuilder {
|
|||
} else if (theOperation == SearchFilterParser.CompareOperation.gt || theOperation == SearchFilterParser.CompareOperation.ge) {
|
||||
// use upper bound first, e.g value between 6 and 10
|
||||
if (upperBoundInstant != null) {
|
||||
ub = theFrom.createPredicate(highValueField, ParamPrefixEnum.GREATERTHAN_OR_EQUALS, genericUpperBound);
|
||||
ub = this.createPredicate(highValueField, ParamPrefixEnum.GREATERTHAN_OR_EQUALS, genericUpperBound);
|
||||
if (myDaoConfig.isAccountForDateIndexNulls()) {
|
||||
ub = ComboCondition.or(ub, theFrom.createPredicate(lowValueField, ParamPrefixEnum.GREATERTHAN_OR_EQUALS, genericUpperBound));
|
||||
ub = ComboCondition.or(ub, this.createPredicate(lowValueField, ParamPrefixEnum.GREATERTHAN_OR_EQUALS, genericUpperBound));
|
||||
}
|
||||
} else if (lowerBoundInstant != null) {
|
||||
lb = theFrom.createPredicate(highValueField, ParamPrefixEnum.GREATERTHAN_OR_EQUALS, genericLowerBound);
|
||||
lb = this.createPredicate(highValueField, ParamPrefixEnum.GREATERTHAN_OR_EQUALS, genericLowerBound);
|
||||
if (myDaoConfig.isAccountForDateIndexNulls()) {
|
||||
lb = ComboCondition.or(lb, theFrom.createPredicate(lowValueField, ParamPrefixEnum.GREATERTHAN_OR_EQUALS, genericLowerBound));
|
||||
lb = ComboCondition.or(lb, this.createPredicate(lowValueField, ParamPrefixEnum.GREATERTHAN_OR_EQUALS, genericLowerBound));
|
||||
}
|
||||
} else {
|
||||
throw new InvalidRequestException(Msg.code(1253) + "upperBound and lowerBound value not correctly specified for compare theOperation");
|
||||
|
@ -178,16 +175,16 @@ public class DatePredicateBuilder extends BaseSearchParamPredicateBuilder {
|
|||
(upperBoundInstant == null)) {
|
||||
throw new InvalidRequestException(Msg.code(1254) + "lowerBound and/or upperBound value not correctly specified for compare theOperation");
|
||||
}
|
||||
lt = theFrom.createPredicate(lowValueField, ParamPrefixEnum.LESSTHAN, genericLowerBound);
|
||||
gt = theFrom.createPredicate(highValueField, ParamPrefixEnum.GREATERTHAN, genericUpperBound);
|
||||
lt = this.createPredicate(lowValueField, ParamPrefixEnum.LESSTHAN, genericLowerBound);
|
||||
gt = this.createPredicate(highValueField, ParamPrefixEnum.GREATERTHAN, genericUpperBound);
|
||||
lb = ComboCondition.or(lt, gt);
|
||||
} else if ((theOperation == SearchFilterParser.CompareOperation.eq)
|
||||
|| (theOperation == SearchFilterParser.CompareOperation.sa)
|
||||
|| (theOperation == SearchFilterParser.CompareOperation.eb)
|
||||
|| (theOperation == null)) {
|
||||
if (lowerBoundInstant != null) {
|
||||
gt = theFrom.createPredicate(lowValueField, ParamPrefixEnum.GREATERTHAN_OR_EQUALS, genericLowerBound);
|
||||
lt = theFrom.createPredicate(highValueField, ParamPrefixEnum.GREATERTHAN_OR_EQUALS, genericLowerBound);
|
||||
gt = this.createPredicate(lowValueField, ParamPrefixEnum.GREATERTHAN_OR_EQUALS, genericLowerBound);
|
||||
lt = this.createPredicate(highValueField, ParamPrefixEnum.GREATERTHAN_OR_EQUALS, genericLowerBound);
|
||||
|
||||
if (lowerBound.getPrefix() == ParamPrefixEnum.STARTS_AFTER || lowerBound.getPrefix() == ParamPrefixEnum.EQUAL) {
|
||||
lb = gt;
|
||||
|
@ -197,8 +194,8 @@ public class DatePredicateBuilder extends BaseSearchParamPredicateBuilder {
|
|||
}
|
||||
|
||||
if (upperBoundInstant != null) {
|
||||
gt = theFrom.createPredicate(lowValueField, ParamPrefixEnum.LESSTHAN_OR_EQUALS, genericUpperBound);
|
||||
lt = theFrom.createPredicate(highValueField, ParamPrefixEnum.LESSTHAN_OR_EQUALS, genericUpperBound);
|
||||
gt = this.createPredicate(lowValueField, ParamPrefixEnum.LESSTHAN_OR_EQUALS, genericUpperBound);
|
||||
lt = this.createPredicate(highValueField, ParamPrefixEnum.LESSTHAN_OR_EQUALS, genericUpperBound);
|
||||
|
||||
|
||||
if (theRange.getUpperBound().getPrefix() == ParamPrefixEnum.ENDS_BEFORE || theRange.getUpperBound().getPrefix() == ParamPrefixEnum.EQUAL) {
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
package ca.uhn.fhir.jpa.search.builder.sql;
|
||||
|
||||
import ca.uhn.fhir.jpa.api.config.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.dao.predicate.SearchFilterParser;
|
||||
import ca.uhn.fhir.jpa.search.builder.predicate.BaseJoiningPredicateBuilder;
|
||||
import ca.uhn.fhir.jpa.search.builder.predicate.DatePredicateBuilder;
|
||||
import ca.uhn.fhir.jpa.search.builder.predicate.ResourceTablePredicateBuilder;
|
||||
import ca.uhn.fhir.rest.param.DateParam;
|
||||
import ca.uhn.fhir.rest.param.DateRangeParam;
|
||||
import com.healthmarketscience.sqlbuilder.Condition;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.hibernate.dialect.Dialect;
|
||||
import org.hibernate.dialect.PostgreSQL10Dialect;
|
||||
import org.hibernate.dialect.PostgreSQL95Dialect;
|
||||
import org.hibernate.dialect.SQLServer2012Dialect;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.Locale;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class SearchQueryBuilderDialectPostgresTest extends BaseSearchQueryBuilderDialectTest {
|
||||
|
||||
/**
|
||||
* Make sure we're using integers and not strings as bind variables
|
||||
* for ordinals
|
||||
*/
|
||||
@Test
|
||||
public void testOrdinalSearchesUseIntegerParameters() {
|
||||
DaoConfig daoConfig = new DaoConfig();
|
||||
daoConfig.getModelConfig().setUseOrdinalDatesForDayPrecisionSearches(true);
|
||||
|
||||
SearchQueryBuilder searchQueryBuilder = createSearchQueryBuilder();
|
||||
when(mySqlObjectFactory.dateIndexTable(any())).thenReturn(new DatePredicateBuilder(searchQueryBuilder));
|
||||
|
||||
DatePredicateBuilder datePredicateBuilder = searchQueryBuilder.addDatePredicateBuilder(null);
|
||||
datePredicateBuilder.setDaoConfigForUnitTest(daoConfig);
|
||||
|
||||
Condition datePredicate = datePredicateBuilder.createPredicateDateWithoutIdentityPredicate(new DateParam("2022"), SearchFilterParser.CompareOperation.eq);
|
||||
Condition comboPredicate = datePredicateBuilder.combineWithHashIdentityPredicate("Observation", "date", datePredicate);
|
||||
|
||||
searchQueryBuilder.addPredicate(comboPredicate);
|
||||
|
||||
GeneratedSql generatedSql = searchQueryBuilder.generate(0, 500);
|
||||
logSql(generatedSql);
|
||||
|
||||
String sql = generatedSql.getSql();
|
||||
assertEquals("SELECT t0.RES_ID FROM HFJ_SPIDX_DATE t0 WHERE ((t0.HASH_IDENTITY = ?) AND ((t0.SP_VALUE_LOW_DATE_ORDINAL >= ?) AND (t0.SP_VALUE_HIGH_DATE_ORDINAL <= ?))) limit ?", sql);
|
||||
|
||||
assertEquals(4, StringUtils.countMatches(sql, "?"));
|
||||
assertEquals(4, generatedSql.getBindVariables().size());
|
||||
assertEquals(123682819940570799L, generatedSql.getBindVariables().get(0));
|
||||
assertEquals(20220101, generatedSql.getBindVariables().get(1));
|
||||
assertEquals(20221231, generatedSql.getBindVariables().get(2));
|
||||
assertEquals(500, generatedSql.getBindVariables().get(3));
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
protected Dialect createDialect() {
|
||||
return new PostgreSQL10Dialect();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue