Freetext search lastupdated param support (#3630)
* Initial implementation * Implement job cancellation * Handle uri, _tag, _security and _profile freetext search parameters * Fix combined 'and' and 'or' clauses and add tests Use collection instead of typed array to eliminate warnings * Eliminate predicate nesting level * Implement _source freetext search * Implement _lastUpdated freetext search * add ITestDataBuilder.withLastUpdated(String theIsoDate) method Co-authored-by: juan.marchionatto <juan.marchionatto@smilecdr.com>
This commit is contained in:
parent
464a07e284
commit
ad3294f8e8
|
@ -24,6 +24,7 @@ import ca.uhn.fhir.context.FhirContext;
|
|||
import ca.uhn.fhir.context.RuntimeSearchParam;
|
||||
import ca.uhn.fhir.jpa.api.config.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceLink;
|
||||
import ca.uhn.fhir.jpa.model.search.ExtendedLuceneIndexData;
|
||||
import ca.uhn.fhir.jpa.searchparam.extractor.ISearchParamExtractor;
|
||||
|
@ -107,6 +108,12 @@ public class ExtendedLuceneIndexExtractor {
|
|||
retVal.addUriIndexData("_source", source);
|
||||
}
|
||||
|
||||
if (theResource.getMeta().getLastUpdated() != null) {
|
||||
int ordinal = ResourceIndexedSearchParamDate.calculateOrdinalValue(theResource.getMeta().getLastUpdated()).intValue();
|
||||
retVal.addDateIndexData("_lastUpdated", theResource.getMeta().getLastUpdated(), ordinal,
|
||||
theResource.getMeta().getLastUpdated(), ordinal);
|
||||
}
|
||||
|
||||
|
||||
if (!theNewParams.myLinks.isEmpty()) {
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ package ca.uhn.fhir.jpa.dao.search;
|
|||
import ca.uhn.fhir.context.RuntimeSearchParam;
|
||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
||||
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
|
||||
import ca.uhn.fhir.rest.api.Constants;
|
||||
import ca.uhn.fhir.rest.param.DateParam;
|
||||
import ca.uhn.fhir.rest.param.QuantityParam;
|
||||
|
@ -56,6 +57,7 @@ public class ExtendedLuceneSearchBuilder {
|
|||
*/
|
||||
public boolean isSupportsSomeOf(SearchParameterMap myParams) {
|
||||
return
|
||||
myParams.getLastUpdated() != null ||
|
||||
myParams.entrySet().stream()
|
||||
.filter(e -> !ourUnsafeSearchParmeters.contains(e.getKey()))
|
||||
// each and clause may have a different modifier, so split down to the ORs
|
||||
|
@ -113,10 +115,8 @@ public class ExtendedLuceneSearchBuilder {
|
|||
}
|
||||
return false;
|
||||
} else if (param instanceof UriParam) {
|
||||
if (EMPTY_MODIFIER.equals(modifier)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return modifier.equals(EMPTY_MODIFIER);
|
||||
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
@ -124,7 +124,7 @@ public class ExtendedLuceneSearchBuilder {
|
|||
|
||||
public void addAndConsumeAdvancedQueryClauses(ExtendedLuceneClauseBuilder builder, String theResourceType, SearchParameterMap theParams, ISearchParamRegistry theSearchParamRegistry) {
|
||||
// copy the keys to avoid concurrent modification error
|
||||
ArrayList<String> paramNames = Lists.newArrayList(theParams.keySet());
|
||||
ArrayList<String> paramNames = compileParamNames(theParams);
|
||||
for (String nextParam : paramNames) {
|
||||
if (ourUnsafeSearchParmeters.contains(nextParam)) {
|
||||
continue;
|
||||
|
@ -170,7 +170,8 @@ public class ExtendedLuceneSearchBuilder {
|
|||
break;
|
||||
|
||||
case DATE:
|
||||
List<List<IQueryParameterType>> dateAndOrTerms = theParams.removeByNameUnmodified(nextParam);
|
||||
List<List<IQueryParameterType>> dateAndOrTerms = nextParam.equalsIgnoreCase("_lastupdated")
|
||||
? getLastUpdatedAndOrList(theParams) : theParams.removeByNameUnmodified(nextParam);
|
||||
builder.addDateUnmodifiedSearch(nextParam, dateAndOrTerms);
|
||||
break;
|
||||
|
||||
|
@ -183,4 +184,36 @@ public class ExtendedLuceneSearchBuilder {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private List<List<IQueryParameterType>> getLastUpdatedAndOrList(SearchParameterMap theParams) {
|
||||
DateParam activeBound = theParams.getLastUpdated().getLowerBound() != null
|
||||
? theParams.getLastUpdated().getLowerBound()
|
||||
: theParams.getLastUpdated().getUpperBound();
|
||||
|
||||
TemporalPrecisionEnum precision = activeBound.getPrecision();
|
||||
|
||||
List<List<IQueryParameterType>> result = List.of( List.of(activeBound) );
|
||||
|
||||
// indicate parameter was processed
|
||||
theParams.setLastUpdated(null);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Param name list is not only the params.keySet, but also the "special" parameters extracted from input
|
||||
* (as _lastUpdated when the input myLastUpdated field is not null, etc).
|
||||
*/
|
||||
private ArrayList<String> compileParamNames(SearchParameterMap theParams) {
|
||||
ArrayList<String> nameList = Lists.newArrayList(theParams.keySet());
|
||||
|
||||
if (theParams.getLastUpdated() != null) {
|
||||
nameList.add("_lastUpdated");
|
||||
}
|
||||
|
||||
return nameList;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -91,6 +91,7 @@ import javax.persistence.EntityManager;
|
|||
import java.io.IOException;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
@ -1675,6 +1676,97 @@ public class FhirResourceDaoR4SearchWithElasticSearchIT extends BaseJpaTest {
|
|||
|
||||
|
||||
|
||||
@Nested
|
||||
public class LastUpdatedTests {
|
||||
|
||||
private String myOldObsId, myNewObsId;
|
||||
private String myOldLastUpdatedDateTime = "2017-03-24T03:21:47";
|
||||
|
||||
@BeforeEach
|
||||
public void enableResourceStorage() {
|
||||
myDaoConfig.setStoreResourceInLuceneIndex(true);
|
||||
|
||||
myOldObsId = myTestDataBuilder.createObservation(List.of(
|
||||
myTestDataBuilder.withObservationCode("http://example.com/", "theCodeOld"),
|
||||
myTestDataBuilder.withLastUpdated(myOldLastUpdatedDateTime) )).getIdPart();
|
||||
|
||||
myNewObsId = myTestDataBuilder.createObservation(List.of(
|
||||
myTestDataBuilder.withObservationCode("http://example.com/", "theCodeNew"),
|
||||
myTestDataBuilder.withLastUpdated(new Date()) )).getIdPart();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void resetResourceStorage() {
|
||||
myDaoConfig.setStoreResourceInLuceneIndex(new DaoConfig().isStoreResourceInLuceneIndex());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void eq() {
|
||||
myCaptureQueriesListener.clear();
|
||||
List<String> allIds = myTestDaoSearch.searchForIds("/Observation?_lastUpdated=eq" + myOldLastUpdatedDateTime);
|
||||
|
||||
assertEquals(0, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size(), "we build the bundle with no sql");
|
||||
assertThat(allIds, contains(myOldObsId));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void eqLessPrecisionRequest() {
|
||||
myCaptureQueriesListener.clear();
|
||||
List<String> allIds = myTestDaoSearch.searchForIds("/Observation?_lastUpdated=eq2017-03-24");
|
||||
|
||||
assertEquals(0, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size(), "we build the bundle with no sql");
|
||||
assertThat(allIds, contains(myOldObsId));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ne() {
|
||||
myCaptureQueriesListener.clear();
|
||||
List<String> allIds = myTestDaoSearch.searchForIds("/Observation?_lastUpdated=ne" + myOldLastUpdatedDateTime);
|
||||
|
||||
assertEquals(0, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size(), "we build the bundle with no sql");
|
||||
assertThat(allIds, contains(myNewObsId));
|
||||
}
|
||||
|
||||
@Test
|
||||
void gt() {
|
||||
myCaptureQueriesListener.clear();
|
||||
List<String> allIds = myTestDaoSearch.searchForIds("/Observation?_lastUpdated=gt2018-01-01");
|
||||
|
||||
assertEquals(0, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size(), "we build the bundle with no sql");
|
||||
assertThat(allIds, contains(myNewObsId));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ge() {
|
||||
myCaptureQueriesListener.clear();
|
||||
List<String> allIds = myTestDaoSearch.searchForIds("/Observation?_lastUpdated=ge" + myOldLastUpdatedDateTime);
|
||||
|
||||
assertEquals(0, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size(), "we build the bundle with no sql");
|
||||
assertThat(allIds, contains(myOldObsId, myNewObsId));
|
||||
}
|
||||
|
||||
@Test
|
||||
void lt() {
|
||||
myCaptureQueriesListener.clear();
|
||||
List<String> allIds = myTestDaoSearch.searchForIds("/Observation?_lastUpdated=lt2018-01-01");
|
||||
|
||||
assertEquals(0, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size(), "we build the bundle with no sql");
|
||||
assertThat(allIds, contains(myOldObsId));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void le() {
|
||||
myCaptureQueriesListener.clear();
|
||||
List<String> allIds = myTestDaoSearch.searchForIds("/Observation?_lastUpdated=le" + myOldLastUpdatedDateTime);
|
||||
|
||||
assertEquals(0, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size(), "we build the bundle with no sql");
|
||||
assertThat(allIds, contains(myOldObsId));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Nested
|
||||
public class TotalParameter {
|
||||
|
||||
|
|
|
@ -32,6 +32,12 @@
|
|||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>org.hl7.fhir.r4</artifactId>
|
||||
<version>${fhir_core_version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- General -->
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
|
@ -156,7 +162,7 @@
|
|||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
|
|
|
@ -33,9 +33,11 @@ import org.hl7.fhir.instance.model.api.IBaseResource;
|
|||
import org.hl7.fhir.instance.model.api.ICompositeType;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
import org.hl7.fhir.r4.model.InstantType;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
@ -159,6 +161,14 @@ public interface ITestDataBuilder {
|
|||
return t -> MetaUtil.setSource(theContext, t.getMeta(), theSource);
|
||||
}
|
||||
|
||||
default Consumer<IBaseResource> withLastUpdated(Date theLastUpdated) {
|
||||
return t -> t.getMeta().setLastUpdated(theLastUpdated);
|
||||
}
|
||||
|
||||
default Consumer<IBaseResource> withLastUpdated(String theIsoDate) {
|
||||
return t -> t.getMeta().setLastUpdated(new InstantType(theIsoDate).getValue());
|
||||
}
|
||||
|
||||
default IIdType createObservation(Consumer<IBaseResource>... theModifiers) {
|
||||
return createResource("Observation", theModifiers);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue