Don't apply last updated to includes

This commit is contained in:
James Agnew 2018-08-11 12:20:07 -04:00
parent b9b85f5ba9
commit ea1f17762e
9 changed files with 337 additions and 245 deletions

View File

@ -25,9 +25,9 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -36,6 +36,7 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
* #L%
*/
@SuppressWarnings("UnusedReturnValue")
public class DateRangeParam implements IQueryParameterAnd<DateParam> {
private static final long serialVersionUID = 1L;
@ -208,6 +209,52 @@ public class DateRangeParam implements IQueryParameterAnd<DateParam> {
return this;
}
/**
* Sets the lower bound using a string that is compliant with
* FHIR dateTime format (ISO-8601).
* <p>
* This lower bound is assumed to have a <code>ge</code>
* (greater than or equals) modifier.
* </p>
*/
public DateRangeParam setLowerBound(String theLowerBound) {
setLowerBound(new DateParam(GREATERTHAN_OR_EQUALS, theLowerBound));
return this;
}
/**
* Sets the lower bound to be greaterthan or equal to the given date
*/
public DateRangeParam setLowerBoundInclusive(Date theLowerBound) {
validateAndSet(new DateParam(ParamPrefixEnum.GREATERTHAN_OR_EQUALS, theLowerBound), myUpperBound);
return this;
}
/**
* Sets the upper bound to be greaterthan or equal to the given date
*/
public DateRangeParam setUpperBoundInclusive(Date theUpperBound) {
validateAndSet(myLowerBound, new DateParam(ParamPrefixEnum.LESSTHAN_OR_EQUALS, theUpperBound));
return this;
}
/**
* Sets the lower bound to be greaterthan to the given date
*/
public DateRangeParam setLowerBoundExclusive(Date theLowerBound) {
validateAndSet(new DateParam(ParamPrefixEnum.GREATERTHAN, theLowerBound), myUpperBound);
return this;
}
/**
* Sets the upper bound to be greaterthan to the given date
*/
public DateRangeParam setUpperBoundExclusive(Date theUpperBound) {
validateAndSet(myLowerBound, new DateParam(ParamPrefixEnum.LESSTHAN, theUpperBound));
return this;
}
public Date getLowerBoundAsInstant() {
if (myLowerBound == null) {
return null;
@ -237,6 +284,19 @@ public class DateRangeParam implements IQueryParameterAnd<DateParam> {
return myUpperBound;
}
/**
* Sets the upper bound using a string that is compliant with
* FHIR dateTime format (ISO-8601).
* <p>
* This upper bound is assumed to have a <code>le</code>
* (less than or equals) modifier.
* </p>
*/
public DateRangeParam setUpperBound(String theUpperBound) {
setUpperBound(new DateParam(LESSTHAN_OR_EQUALS, theUpperBound));
return this;
}
public DateRangeParam setUpperBound(DateParam theUpperBound) {
validateAndSet(myLowerBound, theUpperBound);
return this;
@ -298,19 +358,6 @@ public class DateRangeParam implements IQueryParameterAnd<DateParam> {
return (getLowerBoundAsInstant() == null) && (getUpperBoundAsInstant() == null);
}
/**
* Sets the lower bound using a string that is compliant with
* FHIR dateTime format (ISO-8601).
* <p>
* This lower bound is assumed to have a <code>ge</code>
* (greater than or equals) modifier.
* </p>
*/
public DateRangeParam setLowerBound(String theLowerBound) {
setLowerBound(new DateParam(GREATERTHAN_OR_EQUALS, theLowerBound));
return this;
}
/**
* Sets the range from a pair of dates, inclusive on both ends
*
@ -394,19 +441,6 @@ public class DateRangeParam implements IQueryParameterAnd<DateParam> {
validateAndSet(lowerBound, upperBound);
}
/**
* Sets the upper bound using a string that is compliant with
* FHIR dateTime format (ISO-8601).
* <p>
* This upper bound is assumed to have a <code>le</code>
* (less than or equals) modifier.
* </p>
*/
public DateRangeParam setUpperBound(String theUpperBound) {
setUpperBound(new DateParam(LESSTHAN_OR_EQUALS, theUpperBound));
return this;
}
@Override
public void setValuesAsQueryTokens(FhirContext theContext, String theParamName, List<QualifiedParamList> theParameters)
throws InvalidRequestException {
@ -512,4 +546,5 @@ public class DateRangeParam implements IQueryParameterAnd<DateParam> {
myLowerBound = lowerBound;
myUpperBound = upperBound;
}
}

View File

@ -38,8 +38,8 @@ public interface ISearchBuilder {
void loadResourcesByPid(Collection<Long> theIncludePids, List<IBaseResource> theResourceListToPopulate, Set<Long> theRevIncludedPids, boolean theForHistoryOperation, EntityManager theEntityManager,
FhirContext theContext, IDao theDao);
Set<Long> loadReverseIncludes(IDao theCallingDao, FhirContext theContext, EntityManager theEntityManager, Collection<Long> theMatches, Set<Include> theRevIncludes, boolean theReverseMode,
DateRangeParam theLastUpdated);
Set<Long> loadIncludes(IDao theCallingDao, FhirContext theContext, EntityManager theEntityManager, Collection<Long> theMatches, Set<Include> theRevIncludes, boolean theReverseMode,
DateRangeParam theLastUpdated);
/**
* How many results may be fetched at once

View File

@ -9,9 +9,9 @@ package ca.uhn.fhir.jpa.dao;
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -112,7 +112,7 @@ public class SearchBuilder implements ISearchBuilder {
protected IResourceTagDao myResourceTagDao;
protected IResourceSearchViewDao myResourceSearchViewDao;
/**
* Constructor
*/
@ -1595,17 +1595,17 @@ public class SearchBuilder implements ISearchBuilder {
// -- get the resource from the searchView
Collection<ResourceSearchView> resourceSearchViewList = myResourceSearchViewDao.findByResourceIds(pids);
//-- preload all tags with tag definition if any
Map<Long, Collection<ResourceTag>> tagMap = getResourceTagMap(resourceSearchViewList);
Long resourceId = null;
for (ResourceSearchView next : resourceSearchViewList) {
Class<? extends IBaseResource> resourceType = context.getResourceDefinition(next.getResourceType()).getImplementingClass();
resourceId = next.getId();
IBaseResource resource = theDao.toResource(resourceType, next, tagMap.get(resourceId), theForHistoryOperation);
if (resource == null) {
ourLog.warn("Unable to find resource {}/{}/_history/{} in database", next.getResourceType(), next.getIdDt().getIdPart(), next.getVersion());
@ -1635,30 +1635,30 @@ public class SearchBuilder implements ISearchBuilder {
}
}
private Map<Long, Collection<ResourceTag>> getResourceTagMap(Collection<ResourceSearchView> theResourceSearchViewList) {
private Map<Long, Collection<ResourceTag>> getResourceTagMap(Collection<ResourceSearchView> theResourceSearchViewList) {
List<Long> idList = new ArrayList<Long>(theResourceSearchViewList.size());
//-- find all resource has tags
for (ResourceSearchView resource: theResourceSearchViewList) {
for (ResourceSearchView resource: theResourceSearchViewList) {
if (resource.isHasTags())
idList.add(resource.getId());
}
Map<Long, Collection<ResourceTag>> tagMap = new HashMap<>();
//-- no tags
if (idList.size() == 0)
return tagMap;
//-- get all tags for the idList
Collection<ResourceTag> tagList = myResourceTagDao.findByResourceIds(idList);
//-- build the map, key = resourceId, value = list of ResourceTag
Long resourceId;
Collection<ResourceTag> tagCol;
for (ResourceTag tag : tagList) {
resourceId = tag.getResourceId();
tagCol = tagMap.get(resourceId);
if (tagCol == null) {
@ -1670,7 +1670,7 @@ public class SearchBuilder implements ISearchBuilder {
}
}
return tagMap;
return tagMap;
}
@Override
@ -1712,8 +1712,8 @@ public class SearchBuilder implements ISearchBuilder {
* THIS SHOULD RETURN HASHSET and not just Set because we add to it later (so it can't be Collections.emptySet())
*/
@Override
public HashSet<Long> loadReverseIncludes(IDao theCallingDao, FhirContext theContext, EntityManager theEntityManager, Collection<Long> theMatches, Set<Include> theRevIncludes,
boolean theReverseMode, DateRangeParam theLastUpdated) {
public HashSet<Long> loadIncludes(IDao theCallingDao, FhirContext theContext, EntityManager theEntityManager, Collection<Long> theMatches, Set<Include> theRevIncludes,
boolean theReverseMode, DateRangeParam theLastUpdated) {
if (theMatches.size() == 0) {
return new HashSet<>();
}
@ -1820,8 +1820,10 @@ public class SearchBuilder implements ISearchBuilder {
}
}
if (theLastUpdated != null && (theLastUpdated.getLowerBoundAsInstant() != null || theLastUpdated.getUpperBoundAsInstant() != null)) {
pidsToInclude = new HashSet<>(filterResourceIdsByLastUpdated(theEntityManager, theLastUpdated, pidsToInclude));
if (theReverseMode) {
if (theLastUpdated != null && (theLastUpdated.getLowerBoundAsInstant() != null || theLastUpdated.getUpperBoundAsInstant() != null)) {
pidsToInclude = new HashSet<>(filterResourceIdsByLastUpdated(theEntityManager, theLastUpdated, pidsToInclude));
}
}
for (Long next : pidsToInclude) {
if (original.contains(next) == false && allAdded.contains(next) == false) {
@ -2172,7 +2174,7 @@ public class SearchBuilder implements ISearchBuilder {
myCurrentOffset = end;
Collection<Long> pidsToScan = myCurrentPids.subList(start, end);
Set<Include> includes = Collections.singleton(new Include("*", true));
Set<Long> newPids = loadReverseIncludes(myCallingDao, myContext, myEntityManager, pidsToScan, includes, false, myParams.getLastUpdated());
Set<Long> newPids = loadIncludes(myCallingDao, myContext, myEntityManager, pidsToScan, includes, false, myParams.getLastUpdated());
myCurrentIterator = newPids.iterator();
}

View File

@ -1,5 +1,13 @@
package ca.uhn.fhir.jpa.entity;
import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.rest.param.DateRangeParam;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.*;
import static org.apache.commons.lang3.StringUtils.left;
/*
@ -11,9 +19,9 @@ import static org.apache.commons.lang3.StringUtils.left;
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -22,106 +30,88 @@ import static org.apache.commons.lang3.StringUtils.left;
* #L%
*/
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import org.hibernate.annotations.Fetch;
import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.rest.param.DateRangeParam;
@Entity
@Table(name = "HFJ_SEARCH", uniqueConstraints= {
@UniqueConstraint(name="IDX_SEARCH_UUID", columnNames="SEARCH_UUID")
}, indexes= {
@Index(name="IDX_SEARCH_LASTRETURNED", columnList="SEARCH_LAST_RETURNED"),
@Index(name="IDX_SEARCH_RESTYPE_HASHS", columnList="RESOURCE_TYPE,SEARCH_QUERY_STRING_HASH,CREATED")
@Table(name = "HFJ_SEARCH", uniqueConstraints = {
@UniqueConstraint(name = "IDX_SEARCH_UUID", columnNames = "SEARCH_UUID")
}, indexes = {
@Index(name = "IDX_SEARCH_LASTRETURNED", columnList = "SEARCH_LAST_RETURNED"),
@Index(name = "IDX_SEARCH_RESTYPE_HASHS", columnList = "RESOURCE_TYPE,SEARCH_QUERY_STRING_HASH,CREATED")
})
public class Search implements Serializable {
private static final int MAX_SEARCH_QUERY_STRING = 10000;
private static final int UUID_COLUMN_LENGTH = 36;
private static final int FAILURE_MESSAGE_LENGTH = 500;
private static final long serialVersionUID = 1L;
public static final int MAX_SEARCH_QUERY_STRING = 10000;
public static final int UUID_COLUMN_LENGTH = 36;
@Temporal(TemporalType.TIMESTAMP)
@Column(name="CREATED", nullable=false, updatable=false)
@Column(name = "CREATED", nullable = false, updatable = false)
private Date myCreated;
@Column(name="FAILURE_CODE", nullable=true)
@Column(name = "FAILURE_CODE", nullable = true)
private Integer myFailureCode;
@Column(name="FAILURE_MESSAGE", length=FAILURE_MESSAGE_LENGTH, nullable=true)
@Column(name = "FAILURE_MESSAGE", length = FAILURE_MESSAGE_LENGTH, nullable = true)
private String myFailureMessage;
@Id
@GeneratedValue(strategy = GenerationType.AUTO, generator="SEQ_SEARCH")
@SequenceGenerator(name="SEQ_SEARCH", sequenceName="SEQ_SEARCH")
@GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_SEARCH")
@SequenceGenerator(name = "SEQ_SEARCH", sequenceName = "SEQ_SEARCH")
@Column(name = "PID")
private Long myId;
@OneToMany(mappedBy="mySearch")
@OneToMany(mappedBy = "mySearch")
private Collection<SearchInclude> myIncludes;
@Temporal(TemporalType.TIMESTAMP)
@Column(name="LAST_UPDATED_HIGH", nullable=true, insertable=true, updatable=false)
@Column(name = "LAST_UPDATED_HIGH", nullable = true, insertable = true, updatable = false)
private Date myLastUpdatedHigh;
@Temporal(TemporalType.TIMESTAMP)
@Column(name="LAST_UPDATED_LOW", nullable=true, insertable=true, updatable=false)
@Column(name = "LAST_UPDATED_LOW", nullable = true, insertable = true, updatable = false)
private Date myLastUpdatedLow;
@Column(name="NUM_FOUND", nullable=false)
@Column(name = "NUM_FOUND", nullable = false)
private int myNumFound;
@Column(name="PREFERRED_PAGE_SIZE", nullable=true)
@Column(name = "PREFERRED_PAGE_SIZE", nullable = true)
private Integer myPreferredPageSize;
@Column(name="RESOURCE_ID", nullable=true)
@Column(name = "RESOURCE_ID", nullable = true)
private Long myResourceId;
@Column(name="RESOURCE_TYPE", length=200, nullable=true)
@Column(name = "RESOURCE_TYPE", length = 200, nullable = true)
private String myResourceType;
@OneToMany(mappedBy="mySearch")
@OneToMany(mappedBy = "mySearch")
private Collection<SearchResult> myResults;
@NotNull
@Temporal(TemporalType.TIMESTAMP)
@Column(name="SEARCH_LAST_RETURNED", nullable=false, updatable=false)
@Column(name = "SEARCH_LAST_RETURNED", nullable = false, updatable = false)
private Date mySearchLastReturned;
@Lob()
@Basic(fetch=FetchType.LAZY)
@Column(name="SEARCH_QUERY_STRING", nullable=true, updatable=false, length = MAX_SEARCH_QUERY_STRING)
@Basic(fetch = FetchType.LAZY)
@Column(name = "SEARCH_QUERY_STRING", nullable = true, updatable = false, length = MAX_SEARCH_QUERY_STRING)
private String mySearchQueryString;
@Column(name="SEARCH_QUERY_STRING_HASH", nullable=true, updatable=false)
@Column(name = "SEARCH_QUERY_STRING_HASH", nullable = true, updatable = false)
private Integer mySearchQueryStringHash;
@Enumerated(EnumType.ORDINAL)
@Column(name="SEARCH_TYPE", nullable=false)
@Column(name = "SEARCH_TYPE", nullable = false)
private SearchTypeEnum mySearchType;
@Enumerated(EnumType.STRING)
@Column(name="SEARCH_STATUS", nullable=false, length=10)
@Column(name = "SEARCH_STATUS", nullable = false, length = 10)
private SearchStatusEnum myStatus;
@Column(name="TOTAL_COUNT", nullable=true)
@Column(name = "TOTAL_COUNT", nullable = true)
private Integer myTotalCount;
@Column(name="SEARCH_UUID", length= UUID_COLUMN_LENGTH, nullable=false, updatable=false)
@Column(name = "SEARCH_UUID", length = UUID_COLUMN_LENGTH, nullable = false, updatable = false)
private String myUuid;
/**
* Constructor
*/
@ -133,21 +123,33 @@ public class Search implements Serializable {
return myCreated;
}
public void setCreated(Date theCreated) {
myCreated = theCreated;
}
public Integer getFailureCode() {
return myFailureCode;
}
public void setFailureCode(Integer theFailureCode) {
myFailureCode = theFailureCode;
}
public String getFailureMessage() {
return myFailureMessage;
}
public void setFailureMessage(String theFailureMessage) {
myFailureMessage = left(theFailureMessage, FAILURE_MESSAGE_LENGTH);
}
public Long getId() {
return myId;
}
public Collection<SearchInclude> getIncludes() {
if (myIncludes == null) {
myIncludes = new ArrayList<SearchInclude>();
myIncludes = new ArrayList<>();
}
return myIncludes;
}
@ -160,6 +162,16 @@ public class Search implements Serializable {
}
}
public void setLastUpdated(DateRangeParam theLastUpdated) {
if (theLastUpdated == null) {
myLastUpdatedLow = null;
myLastUpdatedHigh = null;
} else {
myLastUpdatedLow = theLastUpdated.getLowerBoundAsInstant();
myLastUpdatedHigh = theLastUpdated.getUpperBoundAsInstant();
}
}
public Date getLastUpdatedHigh() {
return myLastUpdatedHigh;
}
@ -172,90 +184,46 @@ public class Search implements Serializable {
return myNumFound;
}
public void setNumFound(int theNumFound) {
myNumFound = theNumFound;
}
public Integer getPreferredPageSize() {
return myPreferredPageSize;
}
public void setPreferredPageSize(Integer thePreferredPageSize) {
myPreferredPageSize = thePreferredPageSize;
}
public Long getResourceId() {
return myResourceId;
}
public String getResourceType() {
return myResourceType;
}
public Date getSearchLastReturned() {
return mySearchLastReturned;
}
public String getSearchQueryString() {
return mySearchQueryString;
}
public SearchTypeEnum getSearchType() {
return mySearchType;
}
public SearchStatusEnum getStatus() {
return myStatus;
}
public Integer getTotalCount() {
return myTotalCount;
}
public String getUuid() {
return myUuid;
}
public void setCreated(Date theCreated) {
myCreated = theCreated;
}
public void setFailureCode(Integer theFailureCode) {
myFailureCode = theFailureCode;
}
public void setFailureMessage(String theFailureMessage) {
myFailureMessage = left(theFailureMessage, FAILURE_MESSAGE_LENGTH);
}
public void setLastUpdated(Date theLowerBound, Date theUpperBound) {
myLastUpdatedLow = theLowerBound;
myLastUpdatedHigh = theUpperBound;
}
public void setLastUpdated(DateRangeParam theLastUpdated) {
if (theLastUpdated == null) {
myLastUpdatedLow = null;
myLastUpdatedHigh = null;
} else {
myLastUpdatedLow = theLastUpdated.getLowerBoundAsInstant();
myLastUpdatedHigh = theLastUpdated.getUpperBoundAsInstant();
}
}
public void setNumFound(int theNumFound) {
myNumFound = theNumFound;
}
public void setPreferredPageSize(Integer thePreferredPageSize) {
myPreferredPageSize = thePreferredPageSize;
}
public void setResourceId(Long theResourceId) {
myResourceId = theResourceId;
}
public String getResourceType() {
return myResourceType;
}
public void setResourceType(String theResourceType) {
myResourceType = theResourceType;
}
public Date getSearchLastReturned() {
return mySearchLastReturned;
}
public void setSearchLastReturned(Date theDate) {
mySearchLastReturned = theDate;
}
public String getSearchQueryString() {
return mySearchQueryString;
}
public void setSearchQueryString(String theSearchQueryString) {
if (theSearchQueryString != null && theSearchQueryString.length() > MAX_SEARCH_QUERY_STRING) {
mySearchQueryString = null;
@ -264,26 +232,47 @@ public class Search implements Serializable {
}
}
public void setSearchQueryStringHash(Integer theSearchQueryStringHash) {
mySearchQueryStringHash = theSearchQueryStringHash;
public SearchTypeEnum getSearchType() {
return mySearchType;
}
public void setSearchType(SearchTypeEnum theSearchType) {
mySearchType = theSearchType;
}
public SearchStatusEnum getStatus() {
return myStatus;
}
public void setStatus(SearchStatusEnum theStatus) {
myStatus = theStatus;
}
public Integer getTotalCount() {
return myTotalCount;
}
public void setTotalCount(Integer theTotalCount) {
myTotalCount = theTotalCount;
}
public String getUuid() {
return myUuid;
}
public void setUuid(String theUuid) {
myUuid = theUuid;
}
public void setLastUpdated(Date theLowerBound, Date theUpperBound) {
myLastUpdatedLow = theLowerBound;
myLastUpdatedHigh = theUpperBound;
}
public void setSearchQueryStringHash(Integer theSearchQueryStringHash) {
mySearchQueryStringHash = theSearchQueryStringHash;
}
private Set<Include> toIncList(boolean theWantReverse) {
HashSet<Include> retVal = new HashSet<Include>();
for (SearchInclude next : getIncludes()) {
@ -301,5 +290,8 @@ public class Search implements Serializable {
public Set<Include> toRevIncludesList() {
return toIncList(true);
}
public void addInclude(SearchInclude theInclude) {
getIncludes().add(theInclude);
}
}

View File

@ -32,6 +32,7 @@ import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
@ -134,13 +135,8 @@ public class PersistedJpaBundleProvider implements IBundleProvider {
final List<Long> pidsSubList = mySearchCoordinatorSvc.getResources(myUuid, theFromIndex, theToIndex);
TransactionTemplate template = new TransactionTemplate(myPlatformTransactionManager);
template.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRED);
return template.execute(new TransactionCallback<List<IBaseResource>>() {
@Override
public List<IBaseResource> doInTransaction(TransactionStatus theStatus) {
return toResourceList(sb, pidsSubList);
}
});
template.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
return template.execute(theStatus -> toResourceList(sb, pidsSubList));
}
private void ensureDependenciesInjected() {
@ -273,8 +269,8 @@ public class PersistedJpaBundleProvider implements IBundleProvider {
List<IBaseResource> toResourceList(ISearchBuilder sb, List<Long> pidsSubList) {
Set<Long> includedPids = new HashSet<>();
if (mySearchEntity.getSearchType() == SearchTypeEnum.SEARCH) {
includedPids.addAll(sb.loadReverseIncludes(myDao, myContext, myEntityManager, pidsSubList, mySearchEntity.toRevIncludesList(), true, mySearchEntity.getLastUpdated()));
includedPids.addAll(sb.loadReverseIncludes(myDao, myContext, myEntityManager, pidsSubList, mySearchEntity.toIncludesList(), false, mySearchEntity.getLastUpdated()));
includedPids.addAll(sb.loadIncludes(myDao, myContext, myEntityManager, pidsSubList, mySearchEntity.toRevIncludesList(), true, mySearchEntity.getLastUpdated()));
includedPids.addAll(sb.loadIncludes(myDao, myContext, myEntityManager, pidsSubList, mySearchEntity.toIncludesList(), false, mySearchEntity.getLastUpdated()));
}
// Execute the query and make sure we return distinct results

View File

@ -258,8 +258,8 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
* individually for pages as we return them to clients
*/
final Set<Long> includedPids = new HashSet<Long>();
includedPids.addAll(sb.loadReverseIncludes(theCallingDao, myContext, myEntityManager, pids, theParams.getRevIncludes(), true, theParams.getLastUpdated()));
includedPids.addAll(sb.loadReverseIncludes(theCallingDao, myContext, myEntityManager, pids, theParams.getIncludes(), false, theParams.getLastUpdated()));
includedPids.addAll(sb.loadIncludes(theCallingDao, myContext, myEntityManager, pids, theParams.getRevIncludes(), true, theParams.getLastUpdated()));
includedPids.addAll(sb.loadIncludes(theCallingDao, myContext, myEntityManager, pids, theParams.getIncludes(), false, theParams.getLastUpdated()));
List<IBaseResource> resources = new ArrayList<IBaseResource>();
sb.loadResourcesByPid(pids, resources, includedPids, false, myEntityManager, myContext, theCallingDao);
@ -336,10 +336,10 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
search.setSearchQueryStringHash(queryString.hashCode());
for (Include next : theParams.getIncludes()) {
search.getIncludes().add(new SearchInclude(search, next.getValue(), false, next.isRecurse()));
search.addInclude(new SearchInclude(search, next.getValue(), false, next.isRecurse()));
}
for (Include next : theParams.getRevIncludes()) {
search.getIncludes().add(new SearchInclude(search, next.getValue(), true, next.isRecurse()));
search.addInclude(new SearchInclude(search, next.getValue(), true, next.isRecurse()));
}
SearchTask task = new SearchTask(search, theCallingDao, theParams, theResourceType, searchUuid);

View File

@ -47,7 +47,7 @@ import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.mock;
@SuppressWarnings("unchecked")
@SuppressWarnings({"unchecked", "Duplicates"})
public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4SearchNoFtTest.class);
@ -145,6 +145,59 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
assertThat(ids, empty());
}
/**
* See #1053
*/
@Test
public void testLastUpdateShouldntApplyToIncludes() {
SearchParameterMap map;
List<String> ids;
Date beforeAll = new Date();
ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast(100);
Organization org = new Organization();
org.setName("O1");
org.setId("O1");
myOrganizationDao.update(org);
ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast(100);
Date beforePatient = new Date();
ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast(100);
Patient p = new Patient();
p.setId("P1");
p.setActive(true);
p.setManagingOrganization(new Reference("Organization/O1"));
myPatientDao.update(p);
ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast(100);
Date afterAll = new Date();
// Search with between date (should still return Organization even though
// it was created before that date, since it's an include)
map = new SearchParameterMap();
map.setLastUpdated(new DateRangeParam().setLowerBoundInclusive(beforePatient));
map.addInclude(Patient.INCLUDE_ORGANIZATION);
ids = toUnqualifiedVersionlessIdValues(myPatientDao.search(map));
assertThat(ids, contains("Patient/P1", "Organization/O1"));
// Search before everything
map = new SearchParameterMap();
map.setLastUpdated(new DateRangeParam().setLowerBoundInclusive(beforeAll));
map.addInclude(Patient.INCLUDE_ORGANIZATION);
ids = toUnqualifiedVersionlessIdValues(myPatientDao.search(map));
assertThat(ids, contains("Patient/P1", "Organization/O1"));
// Search after everything
map = new SearchParameterMap();
map.setLastUpdated(new DateRangeParam().setLowerBoundInclusive(afterAll));
map.addInclude(Patient.INCLUDE_ORGANIZATION);
ids = toUnqualifiedVersionlessIdValues(myPatientDao.search(map));
assertThat(ids, empty());
}
@Test
public void testEverythingTimings() {
String methodName = "testEverythingTimings";

View File

@ -1,31 +1,5 @@
package ca.uhn.fhir.rest.param;
import static ca.uhn.fhir.rest.param.ParamPrefixEnum.APPROXIMATE;
import static ca.uhn.fhir.rest.param.ParamPrefixEnum.EQUAL;
import static ca.uhn.fhir.rest.param.ParamPrefixEnum.GREATERTHAN;
import static ca.uhn.fhir.rest.param.ParamPrefixEnum.GREATERTHAN_OR_EQUALS;
import static ca.uhn.fhir.rest.param.ParamPrefixEnum.LESSTHAN;
import static ca.uhn.fhir.rest.param.ParamPrefixEnum.LESSTHAN_OR_EQUALS;
import static ca.uhn.fhir.rest.param.ParamPrefixEnum.NOT_EQUAL;
import static com.google.common.collect.Lists.newArrayList;
import static java.lang.System.currentTimeMillis;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.hamcrest.core.Is.is;
import static org.hamcrest.core.IsEqual.equalTo;
import static org.hamcrest.core.IsNot.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;
import com.google.common.testing.EqualsTester;
import org.junit.AfterClass;
import org.junit.Test;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
import ca.uhn.fhir.model.primitive.DateTimeDt;
@ -33,18 +7,57 @@ import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.rest.api.QualifiedParamList;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.util.TestUtil;
import com.google.common.testing.EqualsTester;
import org.junit.AfterClass;
import org.junit.Test;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;
import static ca.uhn.fhir.rest.param.ParamPrefixEnum.*;
import static java.lang.System.currentTimeMillis;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
public class DateRangeParamTest {
private static FhirContext ourCtx = FhirContext.forDstu3();
private static final SimpleDateFormat ourFmt;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(DateRangeParamTest.class);
private static FhirContext ourCtx = FhirContext.forDstu3();
static {
ourFmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSS");
}
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
private static DateRangeParam create(String theLower, String theUpper) throws InvalidRequestException {
DateRangeParam p = new DateRangeParam();
List<QualifiedParamList> tokens = new ArrayList<QualifiedParamList>();
tokens.add(QualifiedParamList.singleton(null, theLower));
if (theUpper != null) {
tokens.add(QualifiedParamList.singleton(null, theUpper));
}
p.setValuesAsQueryTokens(ourCtx, null, tokens);
return p;
}
public static Date parse(String theString) throws ParseException {
return ourFmt.parse(theString);
}
public static Date parseM1(String theString) throws ParseException {
return new Date(ourFmt.parse(theString).getTime() - 1L);
}
private DateRangeParam create(String theString) {
return new DateRangeParam(new DateParam(theString));
}
@ -85,7 +98,7 @@ public class DateRangeParamTest {
assertEquals(parseM1("2011-01-02 00:00:00.0000"), create(">=2011-01-01", "<2011-01-02").getUpperBoundAsInstant());
assertEquals(parse("2011-01-02 00:00:00.0000"), create(">2011-01-01", "<=2011-01-02").getLowerBoundAsInstant());
assertEquals(parseM1("2011-01-03 00:00:00.0000"), create(">2011-01-01", "<=2011-01-02").getUpperBoundAsInstant());
assertEquals(parse("2011-01-01 00:00:00.0000"), create("ge2011-01-01", "lt2011-01-02").getLowerBoundAsInstant());
assertEquals(parseM1("2011-01-02 00:00:00.0000"), create("ge2011-01-01", "lt2011-01-02").getUpperBoundAsInstant());
assertEquals(parse("2011-01-02 00:00:00.0000"), create("gt2011-01-01", "le2011-01-02").getLowerBoundAsInstant());
@ -119,6 +132,26 @@ public class DateRangeParamTest {
assertEquals(parseM1("2011-01-02 00:00:00.0000"), create("2011-01-01").getUpperBoundAsInstant());
}
@Test
public void testSetBoundsWithDatesInclusive() {
DateRangeParam range = new DateRangeParam();
range.setLowerBoundInclusive(new Date());
range.setUpperBoundInclusive(new Date());
assertEquals(ParamPrefixEnum.GREATERTHAN_OR_EQUALS, range.getLowerBound().getPrefix());
assertEquals(ParamPrefixEnum.LESSTHAN_OR_EQUALS, range.getUpperBound().getPrefix());
}
@Test
public void testSetBoundsWithDatesExclusive() {
DateRangeParam range = new DateRangeParam();
range.setLowerBoundExclusive(new Date());
range.setUpperBoundExclusive(new Date());
assertEquals(ParamPrefixEnum.GREATERTHAN, range.getLowerBound().getPrefix());
assertEquals(ParamPrefixEnum.LESSTHAN, range.getUpperBound().getPrefix());
}
@Test
public void testOrList() {
assertNotNull(new DateOrListParam().newInstance());
@ -195,46 +228,22 @@ public class DateRangeParamTest {
assertEquals(parseM1("2014-01-01 00:00:00.0000"), create("gt2011", "le2013").getUpperBoundAsInstant());
}
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
@Test()
public void testEqualsAndHashCode() {
Date lowerBound = new Date(currentTimeMillis());
Date upperBound = new Date(lowerBound.getTime() + SECONDS.toMillis(1));
new EqualsTester()
.addEqualityGroup(new DateRangeParam(),
new DateRangeParam((Date) null, (Date) null))
new DateRangeParam((Date) null, (Date) null))
.addEqualityGroup(new DateRangeParam(lowerBound, upperBound),
new DateRangeParam(new DateParam(GREATERTHAN_OR_EQUALS, lowerBound), new DateParam(LESSTHAN_OR_EQUALS, upperBound)))
new DateRangeParam(new DateParam(GREATERTHAN_OR_EQUALS, lowerBound), new DateParam(LESSTHAN_OR_EQUALS, upperBound)))
.addEqualityGroup(new DateRangeParam(new DateParam(EQUAL, lowerBound)),
new DateRangeParam(new DateParam(null, lowerBound)),
new DateRangeParam(new DateParam(EQUAL, lowerBound), new DateParam(EQUAL, lowerBound)))
new DateRangeParam(new DateParam(null, lowerBound)),
new DateRangeParam(new DateParam(EQUAL, lowerBound), new DateParam(EQUAL, lowerBound)))
.addEqualityGroup(new DateRangeParam(lowerBound, null),
new DateRangeParam(new DateParam(GREATERTHAN_OR_EQUALS, lowerBound), null))
new DateRangeParam(new DateParam(GREATERTHAN_OR_EQUALS, lowerBound), null))
.addEqualityGroup(new DateRangeParam(null, upperBound),
new DateRangeParam(null, new DateParam(LESSTHAN_OR_EQUALS, upperBound)))
new DateRangeParam(null, new DateParam(LESSTHAN_OR_EQUALS, upperBound)))
.testEquals();
}
private static DateRangeParam create(String theLower, String theUpper) throws InvalidRequestException {
DateRangeParam p = new DateRangeParam();
List<QualifiedParamList> tokens = new ArrayList<QualifiedParamList>();
tokens.add(QualifiedParamList.singleton(null, theLower));
if (theUpper != null) {
tokens.add(QualifiedParamList.singleton(null, theUpper));
}
p.setValuesAsQueryTokens(ourCtx, null, tokens);
return p;
}
public static Date parse(String theString) throws ParseException {
return ourFmt.parse(theString);
}
public static Date parseM1(String theString) throws ParseException {
return new Date(ourFmt.parse(theString).getTime() - 1L);
}
}

View File

@ -249,6 +249,11 @@
queue (only the ID), which should reduce the memory/disk footprint of the queue
when it grows long.
</action>
<action type="fix" issue="1053">
A bug was fixed in JPA server searches: When performing a search with a _lastUpdate
filter, the filter was applied to any _include values, which it should not have been.
Thanks to Deepak Garg for reporting!
</action>
</release>
<release version="3.4.0" date="2018-05-28">
<action type="add">