Add some tests for unique search params
This commit is contained in:
parent
377bae8c16
commit
abf76a778f
|
@ -181,12 +181,6 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
InstantDt createHistoryToTimestamp() {
|
|
||||||
// final InstantDt end = new InstantDt(DateUtils.addSeconds(DateUtils.truncate(new Date(), Calendar.SECOND),
|
|
||||||
// -1));
|
|
||||||
return InstantDt.withCurrentTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Set<ResourceIndexedCompositeStringUnique> extractCompositeStringUniques(ResourceTable theEntity, Set<ResourceIndexedSearchParamString> theStringParams, Set<ResourceIndexedSearchParamToken> theTokenParams, Set<ResourceIndexedSearchParamNumber> theNumberParams, Set<ResourceIndexedSearchParamQuantity> theQuantityParams, Set<ResourceIndexedSearchParamDate> theDateParams, Set<ResourceIndexedSearchParamUri> theUriParams, Set<ResourceLink> theLinks) {
|
private Set<ResourceIndexedCompositeStringUnique> extractCompositeStringUniques(ResourceTable theEntity, Set<ResourceIndexedSearchParamString> theStringParams, Set<ResourceIndexedSearchParamToken> theTokenParams, Set<ResourceIndexedSearchParamNumber> theNumberParams, Set<ResourceIndexedSearchParamQuantity> theQuantityParams, Set<ResourceIndexedSearchParamDate> theDateParams, Set<ResourceIndexedSearchParamUri> theUriParams, Set<ResourceLink> theLinks) {
|
||||||
Set<ResourceIndexedCompositeStringUnique> compositeStringUniques;
|
Set<ResourceIndexedCompositeStringUnique> compositeStringUniques;
|
||||||
compositeStringUniques = new HashSet<>();
|
compositeStringUniques = new HashSet<>();
|
||||||
|
@ -198,6 +192,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
|
||||||
for (RuntimeSearchParam nextCompositeOf : next.getCompositeOf()) {
|
for (RuntimeSearchParam nextCompositeOf : next.getCompositeOf()) {
|
||||||
Set<? extends BaseResourceIndexedSearchParam> paramsListForCompositePart = null;
|
Set<? extends BaseResourceIndexedSearchParam> paramsListForCompositePart = null;
|
||||||
Set<ResourceLink> linksForCompositePart = null;
|
Set<ResourceLink> linksForCompositePart = null;
|
||||||
|
Set<String> linksForCompositePartWantPaths = null;
|
||||||
switch (nextCompositeOf.getParamType()) {
|
switch (nextCompositeOf.getParamType()) {
|
||||||
case NUMBER:
|
case NUMBER:
|
||||||
paramsListForCompositePart = theNumberParams;
|
paramsListForCompositePart = theNumberParams;
|
||||||
|
@ -213,6 +208,8 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
|
||||||
break;
|
break;
|
||||||
case REFERENCE:
|
case REFERENCE:
|
||||||
linksForCompositePart = theLinks;
|
linksForCompositePart = theLinks;
|
||||||
|
linksForCompositePartWantPaths = new HashSet<>();
|
||||||
|
linksForCompositePartWantPaths.addAll(nextCompositeOf.getPathsSplit());
|
||||||
break;
|
break;
|
||||||
case QUANTITY:
|
case QUANTITY:
|
||||||
paramsListForCompositePart = theQuantityParams;
|
paramsListForCompositePart = theQuantityParams;
|
||||||
|
@ -243,10 +240,12 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
|
||||||
}
|
}
|
||||||
if (linksForCompositePart != null) {
|
if (linksForCompositePart != null) {
|
||||||
for (ResourceLink nextLink : linksForCompositePart) {
|
for (ResourceLink nextLink : linksForCompositePart) {
|
||||||
String value = nextLink.getTargetResource().getIdDt().toUnqualifiedVersionless().getValue();
|
if (linksForCompositePartWantPaths.contains(nextLink.getSourcePath())) {
|
||||||
if (isNotBlank(value)) {
|
String value = nextLink.getTargetResource().getIdDt().toUnqualifiedVersionless().getValue();
|
||||||
value = UrlUtil.escapeUrlParam(value);
|
if (isNotBlank(value)) {
|
||||||
nextChoicesList.add(key + "=" + value);
|
value = UrlUtil.escapeUrlParam(value);
|
||||||
|
nextChoicesList.add(key + "=" + value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -942,7 +941,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
ourLog.info("Encoded {} chars of resource body as {} bytes", encoded.length(), bytes.length);
|
ourLog.debug("Encoded {} chars of resource body as {} bytes", encoded.length(), bytes.length);
|
||||||
|
|
||||||
boolean changed = false;
|
boolean changed = false;
|
||||||
|
|
||||||
|
@ -1615,7 +1614,13 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
|
||||||
* Create history entry
|
* Create history entry
|
||||||
*/
|
*/
|
||||||
if (theCreateNewHistoryEntry) {
|
if (theCreateNewHistoryEntry) {
|
||||||
final ResourceHistoryTable historyEntry = theEntity.toHistory(null);
|
|
||||||
|
ResourceHistoryTable existing = null;
|
||||||
|
// if (theEntity.getVersion() > 1) {
|
||||||
|
// existing = myResourceHistoryTableDao.findForIdAndVersion(theEntity.getId(), theEntity.getVersion());
|
||||||
|
// ourLog.warn("Reusing existing history entry entity {}", theEntity.getIdDt().getValue());
|
||||||
|
// }
|
||||||
|
final ResourceHistoryTable historyEntry = theEntity.toHistory(existing);
|
||||||
|
|
||||||
ourLog.info("Saving history entry {}", historyEntry.getIdDt());
|
ourLog.info("Saving history entry {}", historyEntry.getIdDt());
|
||||||
myResourceHistoryTableDao.save(historyEntry);
|
myResourceHistoryTableDao.save(historyEntry);
|
||||||
|
@ -1692,6 +1697,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
|
||||||
if (getConfig().isUniqueIndexesEnabled()) {
|
if (getConfig().isUniqueIndexesEnabled()) {
|
||||||
for (ResourceIndexedCompositeStringUnique next : existingCompositeStringUniques) {
|
for (ResourceIndexedCompositeStringUnique next : existingCompositeStringUniques) {
|
||||||
if (!compositeStringUniques.contains(next)) {
|
if (!compositeStringUniques.contains(next)) {
|
||||||
|
ourLog.info("Removing unique index: {}", next);
|
||||||
myEntityManager.remove(next);
|
myEntityManager.remove(next);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1703,7 +1709,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
|
||||||
throw new PreconditionFailedException("Can not create resource of type " + theEntity.getResourceType() + " as it would create a duplicate index matching query: " + next.getIndexString() + " (existing index belongs to " + existing.getResource().getIdDt().toUnqualifiedVersionless().getValue() + ")");
|
throw new PreconditionFailedException("Can not create resource of type " + theEntity.getResourceType() + " as it would create a duplicate index matching query: " + next.getIndexString() + " (existing index belongs to " + existing.getResource().getIdDt().toUnqualifiedVersionless().getValue() + ")");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ourLog.debug("Persisting unique index: {}", next);
|
ourLog.info("Persisting unique index: {}", next);
|
||||||
myEntityManager.persist(next);
|
myEntityManager.persist(next);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -593,22 +593,23 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void markResourcesMatchingExpressionAsNeedingReindexing(String theExpression) {
|
protected void markResourcesMatchingExpressionAsNeedingReindexing(String theExpression) {
|
||||||
if (isNotBlank(theExpression)) {
|
if (myDaoConfig.isMarkResourcesForReindexingUponSearchParameterChange()) {
|
||||||
final String resourceType = theExpression.substring(0, theExpression.indexOf('.'));
|
if (isNotBlank(theExpression)) {
|
||||||
ourLog.info("Marking all resources of type {} for reindexing due to updated search parameter with path: {}", resourceType, theExpression);
|
final String resourceType = theExpression.substring(0, theExpression.indexOf('.'));
|
||||||
|
ourLog.info("Marking all resources of type {} for reindexing due to updated search parameter with path: {}", resourceType, theExpression);
|
||||||
|
|
||||||
TransactionTemplate txTemplate = new TransactionTemplate(myPlatformTransactionManager);
|
TransactionTemplate txTemplate = new TransactionTemplate(myPlatformTransactionManager);
|
||||||
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
|
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
|
||||||
int updatedCount = txTemplate.execute(new TransactionCallback<Integer>() {
|
int updatedCount = txTemplate.execute(new TransactionCallback<Integer>() {
|
||||||
@Override
|
@Override
|
||||||
public Integer doInTransaction(TransactionStatus theStatus) {
|
public Integer doInTransaction(TransactionStatus theStatus) {
|
||||||
return myResourceTableDao.markResourcesOfTypeAsRequiringReindexing(resourceType);
|
return myResourceTableDao.markResourcesOfTypeAsRequiringReindexing(resourceType);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ourLog.info("Marked {} resources for reindexing", updatedCount);
|
ourLog.info("Marked {} resources for reindexing", updatedCount);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mySearchParamRegistry.forceRefresh();
|
mySearchParamRegistry.forceRefresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,9 +17,9 @@ import java.util.*;
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@ -109,6 +109,7 @@ public class DaoConfig {
|
||||||
private Integer myCacheControlNoStoreMaxResultsUpperLimit = 1000;
|
private Integer myCacheControlNoStoreMaxResultsUpperLimit = 1000;
|
||||||
private Integer myCountSearchResultsUpTo = null;
|
private Integer myCountSearchResultsUpTo = null;
|
||||||
private IdStrategyEnum myResourceServerIdStrategy = IdStrategyEnum.SEQUENTIAL_NUMERIC;
|
private IdStrategyEnum myResourceServerIdStrategy = IdStrategyEnum.SEQUENTIAL_NUMERIC;
|
||||||
|
private boolean myMarkResourcesForReindexingUponSearchParameterChange;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
|
@ -117,6 +118,7 @@ public class DaoConfig {
|
||||||
setSubscriptionEnabled(true);
|
setSubscriptionEnabled(true);
|
||||||
setSubscriptionPollDelay(0);
|
setSubscriptionPollDelay(0);
|
||||||
setSubscriptionPurgeInactiveAfterMillis(Long.MAX_VALUE);
|
setSubscriptionPurgeInactiveAfterMillis(Long.MAX_VALUE);
|
||||||
|
setMarkResourcesForReindexingUponSearchParameterChange(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -340,18 +342,6 @@ public class DaoConfig {
|
||||||
myHardTagListLimit = theHardTagListLimit;
|
myHardTagListLimit = theHardTagListLimit;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* This is the maximum number of resources that will be added to a single page of returned resources. Because of
|
|
||||||
* includes with wildcards and other possibilities it is possible for a client to make requests that include very
|
|
||||||
* large amounts of data, so this hard limit can be imposed to prevent runaway requests.
|
|
||||||
*
|
|
||||||
* @deprecated Deprecated in HAPI FHIR 3.2.0 as this method doesn't actually do anything
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
public void setIncludeLimit(@SuppressWarnings("unused") int theIncludeLimit) {
|
|
||||||
// nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If set to {@link IndexEnabledEnum#DISABLED} (default is {@link IndexEnabledEnum#DISABLED})
|
* If set to {@link IndexEnabledEnum#DISABLED} (default is {@link IndexEnabledEnum#DISABLED})
|
||||||
* the server will not create search indexes for search parameters with no values in resources.
|
* the server will not create search indexes for search parameters with no values in resources.
|
||||||
|
@ -404,11 +394,8 @@ public class DaoConfig {
|
||||||
/**
|
/**
|
||||||
* This may be used to optionally register server interceptors directly against the DAOs.
|
* This may be used to optionally register server interceptors directly against the DAOs.
|
||||||
*/
|
*/
|
||||||
public void setInterceptors(IServerInterceptor... theInterceptor) {
|
public void setInterceptors(List<IServerInterceptor> theInterceptors) {
|
||||||
setInterceptors(new ArrayList<IServerInterceptor>());
|
myInterceptors = theInterceptors;
|
||||||
if (theInterceptor != null && theInterceptor.length != 0) {
|
|
||||||
getInterceptors().addAll(Arrays.asList(theInterceptor));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -465,25 +452,6 @@ public class DaoConfig {
|
||||||
myResourceEncoding = theResourceEncoding;
|
myResourceEncoding = theResourceEncoding;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* This setting configures the strategy to use in generating IDs for newly
|
|
||||||
* created resources on the server. The default is {@link IdStrategyEnum#SEQUENTIAL_NUMERIC}.
|
|
||||||
*/
|
|
||||||
public IdStrategyEnum getResourceServerIdStrategy() {
|
|
||||||
return myResourceServerIdStrategy;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This setting configures the strategy to use in generating IDs for newly
|
|
||||||
* created resources on the server. The default is {@link IdStrategyEnum#SEQUENTIAL_NUMERIC}.
|
|
||||||
*
|
|
||||||
* @param theResourceIdStrategy The strategy. Must not be null.
|
|
||||||
*/
|
|
||||||
public void setResourceServerIdStrategy(IdStrategyEnum theResourceIdStrategy) {
|
|
||||||
Validate.notNull(theResourceIdStrategy, "theResourceIdStrategy must not be null");
|
|
||||||
myResourceServerIdStrategy = theResourceIdStrategy;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If set, an individual resource will not be allowed to have more than the
|
* If set, an individual resource will not be allowed to have more than the
|
||||||
* given number of tags, profiles, and security labels (the limit is for the combined
|
* given number of tags, profiles, and security labels (the limit is for the combined
|
||||||
|
@ -514,6 +482,25 @@ public class DaoConfig {
|
||||||
myResourceMetaCountHardLimit = theResourceMetaCountHardLimit;
|
myResourceMetaCountHardLimit = theResourceMetaCountHardLimit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This setting configures the strategy to use in generating IDs for newly
|
||||||
|
* created resources on the server. The default is {@link IdStrategyEnum#SEQUENTIAL_NUMERIC}.
|
||||||
|
*/
|
||||||
|
public IdStrategyEnum getResourceServerIdStrategy() {
|
||||||
|
return myResourceServerIdStrategy;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This setting configures the strategy to use in generating IDs for newly
|
||||||
|
* created resources on the server. The default is {@link IdStrategyEnum#SEQUENTIAL_NUMERIC}.
|
||||||
|
*
|
||||||
|
* @param theResourceIdStrategy The strategy. Must not be null.
|
||||||
|
*/
|
||||||
|
public void setResourceServerIdStrategy(IdStrategyEnum theResourceIdStrategy) {
|
||||||
|
Validate.notNull(theResourceIdStrategy, "theResourceIdStrategy must not be null");
|
||||||
|
myResourceServerIdStrategy = theResourceIdStrategy;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If set to a non {@literal null} value (default is {@link #DEFAULT_REUSE_CACHED_SEARCH_RESULTS_FOR_MILLIS non null})
|
* If set to a non {@literal null} value (default is {@link #DEFAULT_REUSE_CACHED_SEARCH_RESULTS_FOR_MILLIS non null})
|
||||||
* if an identical search is requested multiple times within this window, the same results will be returned
|
* if an identical search is requested multiple times within this window, the same results will be returned
|
||||||
|
@ -922,6 +909,24 @@ public class DaoConfig {
|
||||||
myIndexContainedResources = theIndexContainedResources;
|
myIndexContainedResources = theIndexContainedResources;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should resources be marked as needing reindexing when a
|
||||||
|
* SearchParameter resource is added or changed. This should generally
|
||||||
|
* be true (which is the default)
|
||||||
|
*/
|
||||||
|
public boolean isMarkResourcesForReindexingUponSearchParameterChange() {
|
||||||
|
return myMarkResourcesForReindexingUponSearchParameterChange;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should resources be marked as needing reindexing when a
|
||||||
|
* SearchParameter resource is added or changed. This should generally
|
||||||
|
* be true (which is the default)
|
||||||
|
*/
|
||||||
|
public void setMarkResourcesForReindexingUponSearchParameterChange(boolean theMarkResourcesForReindexingUponSearchParameterChange) {
|
||||||
|
myMarkResourcesForReindexingUponSearchParameterChange = theMarkResourcesForReindexingUponSearchParameterChange;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isSchedulingDisabled() {
|
public boolean isSchedulingDisabled() {
|
||||||
return mySchedulingDisabled;
|
return mySchedulingDisabled;
|
||||||
}
|
}
|
||||||
|
@ -979,7 +984,7 @@ public class DaoConfig {
|
||||||
* a new one.
|
* a new one.
|
||||||
* <p>
|
* <p>
|
||||||
* This causes friendlier error messages to be generated, but adds an
|
* This causes friendlier error messages to be generated, but adds an
|
||||||
* extra round-trip to the database for eavh save so it can cause
|
* extra round-trip to the database for each save so it can cause
|
||||||
* a small performance hit.
|
* a small performance hit.
|
||||||
* </p>
|
* </p>
|
||||||
*/
|
*/
|
||||||
|
@ -1022,11 +1027,26 @@ public class DaoConfig {
|
||||||
// this method does nothing
|
// this method does nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the maximum number of resources that will be added to a single page of returned resources. Because of
|
||||||
|
* includes with wildcards and other possibilities it is possible for a client to make requests that include very
|
||||||
|
* large amounts of data, so this hard limit can be imposed to prevent runaway requests.
|
||||||
|
*
|
||||||
|
* @deprecated Deprecated in HAPI FHIR 3.2.0 as this method doesn't actually do anything
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public void setIncludeLimit(@SuppressWarnings("unused") int theIncludeLimit) {
|
||||||
|
// nothing
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This may be used to optionally register server interceptors directly against the DAOs.
|
* This may be used to optionally register server interceptors directly against the DAOs.
|
||||||
*/
|
*/
|
||||||
public void setInterceptors(List<IServerInterceptor> theInterceptors) {
|
public void setInterceptors(IServerInterceptor... theInterceptor) {
|
||||||
myInterceptors = theInterceptors;
|
setInterceptors(new ArrayList<IServerInterceptor>());
|
||||||
|
if (theInterceptor != null && theInterceptor.length != 0) {
|
||||||
|
getInterceptors().addAll(Arrays.asList(theInterceptor));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -9,9 +9,9 @@ package ca.uhn.fhir.jpa.entity;
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@ -22,7 +22,6 @@ package ca.uhn.fhir.jpa.entity;
|
||||||
|
|
||||||
import org.apache.commons.lang3.Validate;
|
import org.apache.commons.lang3.Validate;
|
||||||
import org.apache.commons.lang3.builder.*;
|
import org.apache.commons.lang3.builder.*;
|
||||||
import org.hl7.fhir.r4.model.Resource;
|
|
||||||
|
|
||||||
import javax.persistence.*;
|
import javax.persistence.*;
|
||||||
|
|
||||||
|
@ -44,20 +43,10 @@ public class ResourceIndexedCompositeStringUnique implements Comparable<Resource
|
||||||
private Long myId;
|
private Long myId;
|
||||||
|
|
||||||
@ManyToOne
|
@ManyToOne
|
||||||
@JoinColumn(name = "RES_ID", referencedColumnName = "RES_ID", foreignKey = @ForeignKey(name="FK_IDXCMPSTRUNIQ_RES_ID"))
|
@JoinColumn(name = "RES_ID", referencedColumnName = "RES_ID", foreignKey = @ForeignKey(name = "FK_IDXCMPSTRUNIQ_RES_ID"))
|
||||||
private ResourceTable myResource;
|
private ResourceTable myResource;
|
||||||
|
@Column(name = "RES_ID", insertable = false, updatable = false)
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
|
|
||||||
.append("resourceId", myResourceId)
|
|
||||||
.append("indexString", myIndexString)
|
|
||||||
.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Column(name="RES_ID", insertable = false, updatable = false)
|
|
||||||
private Long myResourceId;
|
private Long myResourceId;
|
||||||
|
|
||||||
@Column(name = "IDX_STRING", nullable = false, length = MAX_STRING_LENGTH)
|
@Column(name = "IDX_STRING", nullable = false, length = MAX_STRING_LENGTH)
|
||||||
private String myIndexString;
|
private String myIndexString;
|
||||||
|
|
||||||
|
@ -88,12 +77,13 @@ public class ResourceIndexedCompositeStringUnique implements Comparable<Resource
|
||||||
public boolean equals(Object theO) {
|
public boolean equals(Object theO) {
|
||||||
if (this == theO) return true;
|
if (this == theO) return true;
|
||||||
|
|
||||||
if (theO == null || getClass() != theO.getClass()) return false;
|
if (!(theO instanceof ResourceIndexedCompositeStringUnique)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
ResourceIndexedCompositeStringUnique that = (ResourceIndexedCompositeStringUnique) theO;
|
ResourceIndexedCompositeStringUnique that = (ResourceIndexedCompositeStringUnique) theO;
|
||||||
|
|
||||||
return new EqualsBuilder()
|
return new EqualsBuilder()
|
||||||
.append(getResource(), that.getResource())
|
|
||||||
.append(myIndexString, that.myIndexString)
|
.append(myIndexString, that.myIndexString)
|
||||||
.isEquals();
|
.isEquals();
|
||||||
}
|
}
|
||||||
|
@ -118,8 +108,16 @@ public class ResourceIndexedCompositeStringUnique implements Comparable<Resource
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return new HashCodeBuilder(17, 37)
|
return new HashCodeBuilder(17, 37)
|
||||||
.append(getResource())
|
|
||||||
.append(myIndexString)
|
.append(myIndexString)
|
||||||
.toHashCode();
|
.toHashCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
|
||||||
|
.append("id", myId)
|
||||||
|
.append("resourceId", myResourceId)
|
||||||
|
.append("indexString", myIndexString)
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package ca.uhn.fhir.jpa.dao.r4;
|
package ca.uhn.fhir.jpa.dao.r4;
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||||
|
import ca.uhn.fhir.jpa.dao.DaoMethodOutcome;
|
||||||
import ca.uhn.fhir.jpa.dao.SearchBuilder;
|
import ca.uhn.fhir.jpa.dao.SearchBuilder;
|
||||||
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
|
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
|
||||||
import ca.uhn.fhir.jpa.entity.ResourceIndexedCompositeStringUnique;
|
import ca.uhn.fhir.jpa.entity.ResourceIndexedCompositeStringUnique;
|
||||||
|
@ -19,6 +20,9 @@ import org.junit.AfterClass;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.springframework.orm.jpa.JpaSystemException;
|
import org.springframework.orm.jpa.JpaSystemException;
|
||||||
|
import org.springframework.transaction.TransactionStatus;
|
||||||
|
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
|
||||||
|
import org.springframework.transaction.support.TransactionTemplate;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -44,6 +48,32 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
|
||||||
myDaoConfig.setDefaultSearchParamsCanBeOverridden(true);
|
myDaoConfig.setDefaultSearchParamsCanBeOverridden(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNonTransaction() {
|
||||||
|
createUniqueBirthdateAndGenderSps();
|
||||||
|
|
||||||
|
Patient p = new Patient();
|
||||||
|
p.setGender(Enumerations.AdministrativeGender.MALE);
|
||||||
|
p.setBirthDateElement(new DateType("2001-01-01"));
|
||||||
|
|
||||||
|
Bundle input = new Bundle();
|
||||||
|
input.setType(Bundle.BundleType.TRANSACTION);
|
||||||
|
input.addEntry()
|
||||||
|
.setResource(p)
|
||||||
|
.setFullUrl("Patient")
|
||||||
|
.getRequest()
|
||||||
|
.setMethod(Bundle.HTTPVerb.POST)
|
||||||
|
.setUrl("/Patient")
|
||||||
|
.setIfNoneExist("Patient?gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale&birthdate=2001-01-01");
|
||||||
|
|
||||||
|
Bundle output0 = mySystemDao.transaction(mySrd, input);
|
||||||
|
Bundle output1 = mySystemDao.transaction(mySrd, input);
|
||||||
|
assertEquals(output1.getEntry().get(0).getFullUrl(), output0.getEntry().get(0).getFullUrl());
|
||||||
|
Bundle output2 = mySystemDao.transaction(mySrd, input);
|
||||||
|
assertEquals(output2.getEntry().get(0).getFullUrl(), output0.getEntry().get(0).getFullUrl());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private void createUniqueBirthdateAndGenderSps() {
|
private void createUniqueBirthdateAndGenderSps() {
|
||||||
SearchParameter sp = new SearchParameter();
|
SearchParameter sp = new SearchParameter();
|
||||||
sp.setId("SearchParameter/patient-gender");
|
sp.setId("SearchParameter/patient-gender");
|
||||||
|
@ -387,6 +417,90 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
|
||||||
mySearchParamRegsitry.forceRefresh();
|
mySearchParamRegsitry.forceRefresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void createUniqueIndexPatientIdentifier() {
|
||||||
|
|
||||||
|
SearchParameter sp = new SearchParameter();
|
||||||
|
sp.setId("SearchParameter/patient-identifier");
|
||||||
|
sp.setCode("identifier");
|
||||||
|
sp.setExpression("Patient.identifier");
|
||||||
|
sp.setType(Enumerations.SearchParamType.TOKEN);
|
||||||
|
sp.setStatus(PublicationStatus.ACTIVE);
|
||||||
|
sp.addBase("Patient");
|
||||||
|
mySearchParameterDao.update(sp);
|
||||||
|
|
||||||
|
sp = new SearchParameter();
|
||||||
|
sp.setId("SearchParameter/patient-uniq-identifier");
|
||||||
|
sp.setCode("patient-uniq-identifier");
|
||||||
|
sp.setExpression("Patient.identifier");
|
||||||
|
sp.setType(Enumerations.SearchParamType.COMPOSITE);
|
||||||
|
sp.setStatus(PublicationStatus.ACTIVE);
|
||||||
|
sp.addBase("Patient");
|
||||||
|
sp.addComponent()
|
||||||
|
.setExpression("Patient")
|
||||||
|
.setDefinition(new Reference("/SearchParameter/patient-identifier"));
|
||||||
|
sp.addExtension()
|
||||||
|
.setUrl(JpaConstants.EXT_SP_UNIQUE)
|
||||||
|
.setValue(new BooleanType(true));
|
||||||
|
mySearchParameterDao.update(sp);
|
||||||
|
mySearchParamRegsitry.forceRefresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createUniqueIndexPatientIdentifierCount1() {
|
||||||
|
|
||||||
|
SearchParameter sp = new SearchParameter();
|
||||||
|
sp.setId("SearchParameter/patient-identifier");
|
||||||
|
sp.setCode("first-identifier");
|
||||||
|
sp.setExpression("Patient.identifier.first()");
|
||||||
|
sp.setType(Enumerations.SearchParamType.TOKEN);
|
||||||
|
sp.setStatus(PublicationStatus.ACTIVE);
|
||||||
|
sp.addBase("Patient");
|
||||||
|
mySearchParameterDao.update(sp);
|
||||||
|
|
||||||
|
sp = new SearchParameter();
|
||||||
|
sp.setId("SearchParameter/patient-uniq-identifier");
|
||||||
|
sp.setCode("patient-uniq-identifier");
|
||||||
|
sp.setExpression("Patient.identifier");
|
||||||
|
sp.setType(Enumerations.SearchParamType.COMPOSITE);
|
||||||
|
sp.setStatus(PublicationStatus.ACTIVE);
|
||||||
|
sp.addBase("Patient");
|
||||||
|
sp.addComponent()
|
||||||
|
.setExpression("Patient")
|
||||||
|
.setDefinition(new Reference("/SearchParameter/patient-identifier"));
|
||||||
|
sp.addExtension()
|
||||||
|
.setUrl(JpaConstants.EXT_SP_UNIQUE)
|
||||||
|
.setValue(new BooleanType(true));
|
||||||
|
mySearchParameterDao.update(sp);
|
||||||
|
mySearchParamRegsitry.forceRefresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createUniqueIndexObservationSubject() {
|
||||||
|
|
||||||
|
SearchParameter sp = new SearchParameter();
|
||||||
|
sp.setId("SearchParameter/observation-subject");
|
||||||
|
sp.setCode("observation-subject");
|
||||||
|
sp.setExpression("Observation.subject");
|
||||||
|
sp.setType(Enumerations.SearchParamType.REFERENCE);
|
||||||
|
sp.setStatus(PublicationStatus.ACTIVE);
|
||||||
|
sp.addBase("Observation");
|
||||||
|
mySearchParameterDao.update(sp);
|
||||||
|
|
||||||
|
sp = new SearchParameter();
|
||||||
|
sp.setId("SearchParameter/observation-uniq-subject");
|
||||||
|
sp.setCode("observation-uniq-subject");
|
||||||
|
sp.setExpression("Observation.subject");
|
||||||
|
sp.setType(Enumerations.SearchParamType.COMPOSITE);
|
||||||
|
sp.setStatus(PublicationStatus.ACTIVE);
|
||||||
|
sp.addBase("Observation");
|
||||||
|
sp.addComponent()
|
||||||
|
.setExpression("Observation")
|
||||||
|
.setDefinition(new Reference("/SearchParameter/observation-subject"));
|
||||||
|
sp.addExtension()
|
||||||
|
.setUrl(JpaConstants.EXT_SP_UNIQUE)
|
||||||
|
.setValue(new BooleanType(true));
|
||||||
|
mySearchParameterDao.update(sp);
|
||||||
|
mySearchParamRegsitry.forceRefresh();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testIndexTransactionWithMatchUrl() {
|
public void testIndexTransactionWithMatchUrl() {
|
||||||
Patient pt2 = new Patient();
|
Patient pt2 = new Patient();
|
||||||
|
@ -412,6 +526,161 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDoubleMatching() {
|
||||||
|
createUniqueIndexPatientIdentifier();
|
||||||
|
|
||||||
|
Patient pt = new Patient();
|
||||||
|
pt.addIdentifier().setSystem("urn").setValue("111");
|
||||||
|
pt.addIdentifier().setSystem("urn").setValue("222");
|
||||||
|
|
||||||
|
Bundle input = new Bundle();
|
||||||
|
input.setType(Bundle.BundleType.TRANSACTION);
|
||||||
|
input.addEntry()
|
||||||
|
.setResource(pt)
|
||||||
|
.setFullUrl("Patient")
|
||||||
|
.getRequest()
|
||||||
|
.setMethod(Bundle.HTTPVerb.POST)
|
||||||
|
.setUrl("/Patient")
|
||||||
|
.setIfNoneExist("/Patient?identifier=urn|111,urn|222");
|
||||||
|
mySystemDao.transaction(mySrd, input);
|
||||||
|
|
||||||
|
myEntityManager.clear();
|
||||||
|
|
||||||
|
pt = new Patient();
|
||||||
|
pt.setActive(true);
|
||||||
|
pt.addIdentifier().setSystem("urn").setValue("111");
|
||||||
|
pt.addIdentifier().setSystem("urn").setValue("222");
|
||||||
|
|
||||||
|
input = new Bundle();
|
||||||
|
input.setType(Bundle.BundleType.TRANSACTION);
|
||||||
|
input.addEntry()
|
||||||
|
.setResource(pt)
|
||||||
|
.setFullUrl("Patient")
|
||||||
|
.getRequest()
|
||||||
|
.setMethod(Bundle.HTTPVerb.POST)
|
||||||
|
.setUrl("/Patient")
|
||||||
|
.setIfNoneExist("/Patient?identifier=urn|111,urn|222");
|
||||||
|
mySystemDao.transaction(mySrd, input);
|
||||||
|
|
||||||
|
new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() {
|
||||||
|
@Override
|
||||||
|
protected void doInTransactionWithoutResult(TransactionStatus status) {
|
||||||
|
List<ResourceIndexedCompositeStringUnique> all = myResourceIndexedCompositeStringUniqueDao.findAll();
|
||||||
|
assertEquals(2, all.size());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testObservationSubject() {
|
||||||
|
createUniqueIndexObservationSubject();
|
||||||
|
|
||||||
|
Patient pt = new Patient();
|
||||||
|
pt.addIdentifier().setSystem("urn").setValue("111");
|
||||||
|
pt.addIdentifier().setSystem("urn").setValue("222");
|
||||||
|
IIdType ptid = myPatientDao.create(pt).getId().toUnqualifiedVersionless();
|
||||||
|
|
||||||
|
Encounter enc = new Encounter();
|
||||||
|
enc.setSubject(new Reference(ptid));
|
||||||
|
IIdType encid = myEncounterDao.create(enc).getId().toUnqualifiedVersionless();
|
||||||
|
|
||||||
|
Observation obs = new Observation();
|
||||||
|
obs.setSubject(new Reference(ptid));
|
||||||
|
obs.setContext(new Reference(encid));
|
||||||
|
myObservationDao.create(obs);
|
||||||
|
|
||||||
|
new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() {
|
||||||
|
@Override
|
||||||
|
protected void doInTransactionWithoutResult(TransactionStatus status) {
|
||||||
|
List<ResourceIndexedCompositeStringUnique> all = myResourceIndexedCompositeStringUniqueDao.findAll();
|
||||||
|
assertEquals(all.toString(), 1, all.size());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}er
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIndexFirstMatchOnly() {
|
||||||
|
createUniqueIndexPatientIdentifierCount1();
|
||||||
|
|
||||||
|
Patient pt = new Patient();
|
||||||
|
pt.addIdentifier().setSystem("urn").setValue("111");
|
||||||
|
pt.addIdentifier().setSystem("urn").setValue("222");
|
||||||
|
myPatientDao.create(pt);
|
||||||
|
|
||||||
|
pt = new Patient();
|
||||||
|
pt.addIdentifier().setSystem("urn").setValue("111");
|
||||||
|
pt.addIdentifier().setSystem("urn").setValue("222");
|
||||||
|
try {
|
||||||
|
myPatientDao.create(pt);
|
||||||
|
fail();
|
||||||
|
} catch (PreconditionFailedException e) {
|
||||||
|
// good
|
||||||
|
}
|
||||||
|
|
||||||
|
pt = new Patient();
|
||||||
|
pt.addIdentifier().setSystem("urn").setValue("333");
|
||||||
|
pt.addIdentifier().setSystem("urn").setValue("222");
|
||||||
|
myPatientDao.create(pt);
|
||||||
|
|
||||||
|
new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() {
|
||||||
|
@Override
|
||||||
|
protected void doInTransactionWithoutResult(TransactionStatus status) {
|
||||||
|
List<ResourceIndexedCompositeStringUnique> all = myResourceIndexedCompositeStringUniqueDao.findAll();
|
||||||
|
assertEquals(2, all.size());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDoubleMatchingPut() {
|
||||||
|
createUniqueIndexPatientIdentifier();
|
||||||
|
|
||||||
|
Patient pt = new Patient();
|
||||||
|
pt.addIdentifier().setSystem("urn").setValue("111");
|
||||||
|
pt.addIdentifier().setSystem("urn").setValue("222");
|
||||||
|
|
||||||
|
Bundle input = new Bundle();
|
||||||
|
input.setType(Bundle.BundleType.TRANSACTION);
|
||||||
|
input.addEntry()
|
||||||
|
.setResource(pt)
|
||||||
|
.setFullUrl("Patient")
|
||||||
|
.getRequest()
|
||||||
|
.setMethod(Bundle.HTTPVerb.PUT)
|
||||||
|
.setUrl("/Patient?identifier=urn|111,urn|222");
|
||||||
|
mySystemDao.transaction(mySrd, input);
|
||||||
|
|
||||||
|
myEntityManager.clear();
|
||||||
|
|
||||||
|
pt = new Patient();
|
||||||
|
pt.setActive(true);
|
||||||
|
pt.addIdentifier().setSystem("urn").setValue("111");
|
||||||
|
pt.addIdentifier().setSystem("urn").setValue("222");
|
||||||
|
|
||||||
|
input = new Bundle();
|
||||||
|
input.setType(Bundle.BundleType.TRANSACTION);
|
||||||
|
input.addEntry()
|
||||||
|
.setResource(pt)
|
||||||
|
.setFullUrl("Patient")
|
||||||
|
.getRequest()
|
||||||
|
.setMethod(Bundle.HTTPVerb.PUT)
|
||||||
|
.setUrl("/Patient?identifier=urn|111,urn|222");
|
||||||
|
mySystemDao.transaction(mySrd, input);
|
||||||
|
|
||||||
|
new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() {
|
||||||
|
@Override
|
||||||
|
protected void doInTransactionWithoutResult(TransactionStatus status) {
|
||||||
|
List<ResourceIndexedCompositeStringUnique> all = myResourceIndexedCompositeStringUniqueDao.findAll();
|
||||||
|
assertEquals(2, all.size());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testIndexTransactionWithMatchUrl2() {
|
public void testIndexTransactionWithMatchUrl2() {
|
||||||
createUniqueIndexCoverageBeneficiary();
|
createUniqueIndexCoverageBeneficiary();
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<configuration>
|
<configuration>
|
||||||
|
|
||||||
|
|
||||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||||
<level>INFO</level>
|
<level>INFO</level>
|
||||||
|
|
Loading…
Reference in New Issue