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

@ -36,6 +36,7 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
* #L% * #L%
*/ */
@SuppressWarnings("UnusedReturnValue")
public class DateRangeParam implements IQueryParameterAnd<DateParam> { public class DateRangeParam implements IQueryParameterAnd<DateParam> {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@ -208,6 +209,52 @@ public class DateRangeParam implements IQueryParameterAnd<DateParam> {
return this; 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() { public Date getLowerBoundAsInstant() {
if (myLowerBound == null) { if (myLowerBound == null) {
return null; return null;
@ -237,6 +284,19 @@ public class DateRangeParam implements IQueryParameterAnd<DateParam> {
return myUpperBound; 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) { public DateRangeParam setUpperBound(DateParam theUpperBound) {
validateAndSet(myLowerBound, theUpperBound); validateAndSet(myLowerBound, theUpperBound);
return this; return this;
@ -298,19 +358,6 @@ public class DateRangeParam implements IQueryParameterAnd<DateParam> {
return (getLowerBoundAsInstant() == null) && (getUpperBoundAsInstant() == null); 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 * 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); 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 @Override
public void setValuesAsQueryTokens(FhirContext theContext, String theParamName, List<QualifiedParamList> theParameters) public void setValuesAsQueryTokens(FhirContext theContext, String theParamName, List<QualifiedParamList> theParameters)
throws InvalidRequestException { throws InvalidRequestException {
@ -512,4 +546,5 @@ public class DateRangeParam implements IQueryParameterAnd<DateParam> {
myLowerBound = lowerBound; myLowerBound = lowerBound;
myUpperBound = upperBound; myUpperBound = upperBound;
} }
} }

View File

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

View File

@ -1712,7 +1712,7 @@ 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()) * THIS SHOULD RETURN HASHSET and not just Set because we add to it later (so it can't be Collections.emptySet())
*/ */
@Override @Override
public HashSet<Long> loadReverseIncludes(IDao theCallingDao, FhirContext theContext, EntityManager theEntityManager, Collection<Long> theMatches, Set<Include> theRevIncludes, public HashSet<Long> loadIncludes(IDao theCallingDao, FhirContext theContext, EntityManager theEntityManager, Collection<Long> theMatches, Set<Include> theRevIncludes,
boolean theReverseMode, DateRangeParam theLastUpdated) { boolean theReverseMode, DateRangeParam theLastUpdated) {
if (theMatches.size() == 0) { if (theMatches.size() == 0) {
return new HashSet<>(); return new HashSet<>();
@ -1820,9 +1820,11 @@ public class SearchBuilder implements ISearchBuilder {
} }
} }
if (theReverseMode) {
if (theLastUpdated != null && (theLastUpdated.getLowerBoundAsInstant() != null || theLastUpdated.getUpperBoundAsInstant() != null)) { if (theLastUpdated != null && (theLastUpdated.getLowerBoundAsInstant() != null || theLastUpdated.getUpperBoundAsInstant() != null)) {
pidsToInclude = new HashSet<>(filterResourceIdsByLastUpdated(theEntityManager, theLastUpdated, pidsToInclude)); pidsToInclude = new HashSet<>(filterResourceIdsByLastUpdated(theEntityManager, theLastUpdated, pidsToInclude));
} }
}
for (Long next : pidsToInclude) { for (Long next : pidsToInclude) {
if (original.contains(next) == false && allAdded.contains(next) == false) { if (original.contains(next) == false && allAdded.contains(next) == false) {
theMatches.add(next); theMatches.add(next);
@ -2172,7 +2174,7 @@ public class SearchBuilder implements ISearchBuilder {
myCurrentOffset = end; myCurrentOffset = end;
Collection<Long> pidsToScan = myCurrentPids.subList(start, end); Collection<Long> pidsToScan = myCurrentPids.subList(start, end);
Set<Include> includes = Collections.singleton(new Include("*", true)); 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(); myCurrentIterator = newPids.iterator();
} }

View File

@ -1,5 +1,13 @@
package ca.uhn.fhir.jpa.entity; 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; import static org.apache.commons.lang3.StringUtils.left;
/* /*
@ -22,104 +30,86 @@ import static org.apache.commons.lang3.StringUtils.left;
* #L% * #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 @Entity
@Table(name = "HFJ_SEARCH", uniqueConstraints= { @Table(name = "HFJ_SEARCH", uniqueConstraints = {
@UniqueConstraint(name="IDX_SEARCH_UUID", columnNames="SEARCH_UUID") @UniqueConstraint(name = "IDX_SEARCH_UUID", columnNames = "SEARCH_UUID")
}, indexes= { }, indexes = {
@Index(name="IDX_SEARCH_LASTRETURNED", columnList="SEARCH_LAST_RETURNED"), @Index(name = "IDX_SEARCH_LASTRETURNED", columnList = "SEARCH_LAST_RETURNED"),
@Index(name="IDX_SEARCH_RESTYPE_HASHS", columnList="RESOURCE_TYPE,SEARCH_QUERY_STRING_HASH,CREATED") @Index(name = "IDX_SEARCH_RESTYPE_HASHS", columnList = "RESOURCE_TYPE,SEARCH_QUERY_STRING_HASH,CREATED")
}) })
public class Search implements Serializable { 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 int FAILURE_MESSAGE_LENGTH = 500;
private static final long serialVersionUID = 1L; 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) @Temporal(TemporalType.TIMESTAMP)
@Column(name="CREATED", nullable=false, updatable=false) @Column(name = "CREATED", nullable = false, updatable = false)
private Date myCreated; private Date myCreated;
@Column(name="FAILURE_CODE", nullable=true) @Column(name = "FAILURE_CODE", nullable = true)
private Integer myFailureCode; 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; private String myFailureMessage;
@Id @Id
@GeneratedValue(strategy = GenerationType.AUTO, generator="SEQ_SEARCH") @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_SEARCH")
@SequenceGenerator(name="SEQ_SEARCH", sequenceName="SEQ_SEARCH") @SequenceGenerator(name = "SEQ_SEARCH", sequenceName = "SEQ_SEARCH")
@Column(name = "PID") @Column(name = "PID")
private Long myId; private Long myId;
@OneToMany(mappedBy="mySearch") @OneToMany(mappedBy = "mySearch")
private Collection<SearchInclude> myIncludes; private Collection<SearchInclude> myIncludes;
@Temporal(TemporalType.TIMESTAMP) @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; private Date myLastUpdatedHigh;
@Temporal(TemporalType.TIMESTAMP) @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; private Date myLastUpdatedLow;
@Column(name="NUM_FOUND", nullable=false) @Column(name = "NUM_FOUND", nullable = false)
private int myNumFound; private int myNumFound;
@Column(name="PREFERRED_PAGE_SIZE", nullable=true) @Column(name = "PREFERRED_PAGE_SIZE", nullable = true)
private Integer myPreferredPageSize; private Integer myPreferredPageSize;
@Column(name="RESOURCE_ID", nullable=true) @Column(name = "RESOURCE_ID", nullable = true)
private Long myResourceId; private Long myResourceId;
@Column(name="RESOURCE_TYPE", length=200, nullable=true) @Column(name = "RESOURCE_TYPE", length = 200, nullable = true)
private String myResourceType; private String myResourceType;
@OneToMany(mappedBy="mySearch") @OneToMany(mappedBy = "mySearch")
private Collection<SearchResult> myResults; private Collection<SearchResult> myResults;
@NotNull @NotNull
@Temporal(TemporalType.TIMESTAMP) @Temporal(TemporalType.TIMESTAMP)
@Column(name="SEARCH_LAST_RETURNED", nullable=false, updatable=false) @Column(name = "SEARCH_LAST_RETURNED", nullable = false, updatable = false)
private Date mySearchLastReturned; private Date mySearchLastReturned;
@Lob() @Lob()
@Basic(fetch=FetchType.LAZY) @Basic(fetch = FetchType.LAZY)
@Column(name="SEARCH_QUERY_STRING", nullable=true, updatable=false, length = MAX_SEARCH_QUERY_STRING) @Column(name = "SEARCH_QUERY_STRING", nullable = true, updatable = false, length = MAX_SEARCH_QUERY_STRING)
private String mySearchQueryString; 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; private Integer mySearchQueryStringHash;
@Enumerated(EnumType.ORDINAL) @Enumerated(EnumType.ORDINAL)
@Column(name="SEARCH_TYPE", nullable=false) @Column(name = "SEARCH_TYPE", nullable = false)
private SearchTypeEnum mySearchType; private SearchTypeEnum mySearchType;
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Column(name="SEARCH_STATUS", nullable=false, length=10) @Column(name = "SEARCH_STATUS", nullable = false, length = 10)
private SearchStatusEnum myStatus; private SearchStatusEnum myStatus;
@Column(name="TOTAL_COUNT", nullable=true) @Column(name = "TOTAL_COUNT", nullable = true)
private Integer myTotalCount; 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; private String myUuid;
/** /**
@ -133,21 +123,33 @@ public class Search implements Serializable {
return myCreated; return myCreated;
} }
public void setCreated(Date theCreated) {
myCreated = theCreated;
}
public Integer getFailureCode() { public Integer getFailureCode() {
return myFailureCode; return myFailureCode;
} }
public void setFailureCode(Integer theFailureCode) {
myFailureCode = theFailureCode;
}
public String getFailureMessage() { public String getFailureMessage() {
return myFailureMessage; return myFailureMessage;
} }
public void setFailureMessage(String theFailureMessage) {
myFailureMessage = left(theFailureMessage, FAILURE_MESSAGE_LENGTH);
}
public Long getId() { public Long getId() {
return myId; return myId;
} }
public Collection<SearchInclude> getIncludes() { public Collection<SearchInclude> getIncludes() {
if (myIncludes == null) { if (myIncludes == null) {
myIncludes = new ArrayList<SearchInclude>(); myIncludes = new ArrayList<>();
} }
return myIncludes; 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() { public Date getLastUpdatedHigh() {
return myLastUpdatedHigh; return myLastUpdatedHigh;
} }
@ -172,90 +184,46 @@ public class Search implements Serializable {
return myNumFound; return myNumFound;
} }
public Integer getPreferredPageSize() {
return myPreferredPageSize;
}
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) { public void setNumFound(int theNumFound) {
myNumFound = theNumFound; myNumFound = theNumFound;
} }
public Integer getPreferredPageSize() {
return myPreferredPageSize;
}
public void setPreferredPageSize(Integer thePreferredPageSize) { public void setPreferredPageSize(Integer thePreferredPageSize) {
myPreferredPageSize = thePreferredPageSize; myPreferredPageSize = thePreferredPageSize;
} }
public Long getResourceId() {
return myResourceId;
}
public void setResourceId(Long theResourceId) { public void setResourceId(Long theResourceId) {
myResourceId = theResourceId; myResourceId = theResourceId;
} }
public String getResourceType() {
return myResourceType;
}
public void setResourceType(String theResourceType) { public void setResourceType(String theResourceType) {
myResourceType = theResourceType; myResourceType = theResourceType;
} }
public Date getSearchLastReturned() {
return mySearchLastReturned;
}
public void setSearchLastReturned(Date theDate) { public void setSearchLastReturned(Date theDate) {
mySearchLastReturned = theDate; mySearchLastReturned = theDate;
} }
public String getSearchQueryString() {
return mySearchQueryString;
}
public void setSearchQueryString(String theSearchQueryString) { public void setSearchQueryString(String theSearchQueryString) {
if (theSearchQueryString != null && theSearchQueryString.length() > MAX_SEARCH_QUERY_STRING) { if (theSearchQueryString != null && theSearchQueryString.length() > MAX_SEARCH_QUERY_STRING) {
mySearchQueryString = null; mySearchQueryString = null;
@ -264,26 +232,47 @@ public class Search implements Serializable {
} }
} }
public void setSearchQueryStringHash(Integer theSearchQueryStringHash) { public SearchTypeEnum getSearchType() {
mySearchQueryStringHash = theSearchQueryStringHash; return mySearchType;
} }
public void setSearchType(SearchTypeEnum theSearchType) { public void setSearchType(SearchTypeEnum theSearchType) {
mySearchType = theSearchType; mySearchType = theSearchType;
} }
public SearchStatusEnum getStatus() {
return myStatus;
}
public void setStatus(SearchStatusEnum theStatus) { public void setStatus(SearchStatusEnum theStatus) {
myStatus = theStatus; myStatus = theStatus;
} }
public Integer getTotalCount() {
return myTotalCount;
}
public void setTotalCount(Integer theTotalCount) { public void setTotalCount(Integer theTotalCount) {
myTotalCount = theTotalCount; myTotalCount = theTotalCount;
} }
public String getUuid() {
return myUuid;
}
public void setUuid(String theUuid) { public void setUuid(String theUuid) {
myUuid = 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) { private Set<Include> toIncList(boolean theWantReverse) {
HashSet<Include> retVal = new HashSet<Include>(); HashSet<Include> retVal = new HashSet<Include>();
for (SearchInclude next : getIncludes()) { for (SearchInclude next : getIncludes()) {
@ -302,4 +291,7 @@ public class Search implements Serializable {
return toIncList(true); 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 ca.uhn.fhir.rest.api.server.IBundleProvider;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback; import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionCallbackWithoutResult;
@ -134,13 +135,8 @@ public class PersistedJpaBundleProvider implements IBundleProvider {
final List<Long> pidsSubList = mySearchCoordinatorSvc.getResources(myUuid, theFromIndex, theToIndex); final List<Long> pidsSubList = mySearchCoordinatorSvc.getResources(myUuid, theFromIndex, theToIndex);
TransactionTemplate template = new TransactionTemplate(myPlatformTransactionManager); TransactionTemplate template = new TransactionTemplate(myPlatformTransactionManager);
template.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRED); template.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
return template.execute(new TransactionCallback<List<IBaseResource>>() { return template.execute(theStatus -> toResourceList(sb, pidsSubList));
@Override
public List<IBaseResource> doInTransaction(TransactionStatus theStatus) {
return toResourceList(sb, pidsSubList);
}
});
} }
private void ensureDependenciesInjected() { private void ensureDependenciesInjected() {
@ -273,8 +269,8 @@ public class PersistedJpaBundleProvider implements IBundleProvider {
List<IBaseResource> toResourceList(ISearchBuilder sb, List<Long> pidsSubList) { List<IBaseResource> toResourceList(ISearchBuilder sb, List<Long> pidsSubList) {
Set<Long> includedPids = new HashSet<>(); Set<Long> includedPids = new HashSet<>();
if (mySearchEntity.getSearchType() == SearchTypeEnum.SEARCH) { if (mySearchEntity.getSearchType() == SearchTypeEnum.SEARCH) {
includedPids.addAll(sb.loadReverseIncludes(myDao, myContext, myEntityManager, pidsSubList, mySearchEntity.toRevIncludesList(), true, mySearchEntity.getLastUpdated())); includedPids.addAll(sb.loadIncludes(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.toIncludesList(), false, mySearchEntity.getLastUpdated()));
} }
// Execute the query and make sure we return distinct results // 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 * individually for pages as we return them to clients
*/ */
final Set<Long> includedPids = new HashSet<Long>(); final Set<Long> includedPids = new HashSet<Long>();
includedPids.addAll(sb.loadReverseIncludes(theCallingDao, myContext, myEntityManager, pids, theParams.getRevIncludes(), true, theParams.getLastUpdated())); includedPids.addAll(sb.loadIncludes(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.getIncludes(), false, theParams.getLastUpdated()));
List<IBaseResource> resources = new ArrayList<IBaseResource>(); List<IBaseResource> resources = new ArrayList<IBaseResource>();
sb.loadResourcesByPid(pids, resources, includedPids, false, myEntityManager, myContext, theCallingDao); sb.loadResourcesByPid(pids, resources, includedPids, false, myEntityManager, myContext, theCallingDao);
@ -336,10 +336,10 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
search.setSearchQueryStringHash(queryString.hashCode()); search.setSearchQueryStringHash(queryString.hashCode());
for (Include next : theParams.getIncludes()) { 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()) { 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); 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.junit.Assert.*;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
@SuppressWarnings("unchecked") @SuppressWarnings({"unchecked", "Duplicates"})
public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test { public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4SearchNoFtTest.class); 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()); 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 @Test
public void testEverythingTimings() { public void testEverythingTimings() {
String methodName = "testEverythingTimings"; String methodName = "testEverythingTimings";

View File

@ -1,31 +1,5 @@
package ca.uhn.fhir.rest.param; 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.context.FhirContext;
import ca.uhn.fhir.model.api.TemporalPrecisionEnum; import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
import ca.uhn.fhir.model.primitive.DateTimeDt; 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.api.QualifiedParamList;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.util.TestUtil; 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 { public class DateRangeParamTest {
private static FhirContext ourCtx = FhirContext.forDstu3();
private static final SimpleDateFormat ourFmt; private static final SimpleDateFormat ourFmt;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(DateRangeParamTest.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(DateRangeParamTest.class);
private static FhirContext ourCtx = FhirContext.forDstu3();
static { static {
ourFmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSS"); 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) { private DateRangeParam create(String theString) {
return new DateRangeParam(new DateParam(theString)); return new DateRangeParam(new DateParam(theString));
} }
@ -119,6 +132,26 @@ public class DateRangeParamTest {
assertEquals(parseM1("2011-01-02 00:00:00.0000"), create("2011-01-01").getUpperBoundAsInstant()); 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 @Test
public void testOrList() { public void testOrList() {
assertNotNull(new DateOrListParam().newInstance()); assertNotNull(new DateOrListParam().newInstance());
@ -195,11 +228,6 @@ public class DateRangeParamTest {
assertEquals(parseM1("2014-01-01 00:00:00.0000"), create("gt2011", "le2013").getUpperBoundAsInstant()); assertEquals(parseM1("2014-01-01 00:00:00.0000"), create("gt2011", "le2013").getUpperBoundAsInstant());
} }
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
@Test() @Test()
public void testEqualsAndHashCode() { public void testEqualsAndHashCode() {
Date lowerBound = new Date(currentTimeMillis()); Date lowerBound = new Date(currentTimeMillis());
@ -218,23 +246,4 @@ public class DateRangeParamTest {
new DateRangeParam(null, new DateParam(LESSTHAN_OR_EQUALS, upperBound))) new DateRangeParam(null, new DateParam(LESSTHAN_OR_EQUALS, upperBound)))
.testEquals(); .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 queue (only the ID), which should reduce the memory/disk footprint of the queue
when it grows long. when it grows long.
</action> </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>
<release version="3.4.0" date="2018-05-28"> <release version="3.4.0" date="2018-05-28">
<action type="add"> <action type="add">