Add some tests for unique search params

This commit is contained in:
James Agnew 2018-01-08 07:15:31 -05:00
parent 377bae8c16
commit abf76a778f
6 changed files with 379 additions and 86 deletions

View File

@ -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);
} }
} }

View File

@ -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();
} }

View File

@ -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));
}
} }
/** /**

View File

@ -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();
}
} }

View File

@ -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();

View File

@ -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>