Impropve transaction Performance (#2717)

* Work on changes

* Work on perf

* Work on testing

* Work on perf

* Work on perf

* Work on fix

* Work on perf

* Ongoing work

* Add changelog

* Additional docs

* Test fixes

* Address review comments

* Test fix
This commit is contained in:
James Agnew 2021-06-14 13:08:19 -04:00 committed by GitHub
parent 48eea5a7cc
commit b934abb297
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 3217 additions and 381 deletions

View File

@ -0,0 +1,7 @@
---
type: perf
issue: 2717
title: "A new setting has been added to the DaoConfig called Tag Versioning Mode. This setting controls whether a single collection of
tags/profiles/security labels is maintained across all versions of a single resource, or whether each version of the
resource maintains its own independent collection. Previously each version always maintained an independent collection,
which is useful sometimes, but is often not useful and can affect performance."

View File

@ -0,0 +1,6 @@
---
type: perf
issue: 2717
title: "FHIR transactions in the JPA server that perform writes will now aggressively pre-fetch as many entities
as possible at the very start of transaction processing. This can drastically reduce the number of
round-trips, especially as the number of resources in a transaction gets bigger."

View File

@ -84,6 +84,10 @@ public class DaoConfig {
*/
public static final boolean DEFAULT_ENABLE_TASKS = true;
public static final int DEFAULT_MAXIMUM_INCLUDES_TO_LOAD_PER_PAGE = 1000;
/**
* @since 5.5.0
*/
public static final TagStorageModeEnum DEFAULT_TAG_STORAGE_MODE = TagStorageModeEnum.VERSIONED;
/**
* Default value for {@link #setMaximumSearchResultCountInTransaction(Integer)}
*
@ -129,6 +133,7 @@ public class DaoConfig {
private SearchTotalModeEnum myDefaultTotalMode = null;
private int myEverythingIncludesFetchPageSize = 50;
private int myBulkImportMaxRetryCount = 10;
private TagStorageModeEnum myTagStorageMode = DEFAULT_TAG_STORAGE_MODE;
/**
* update setter javadoc if default changes
*/
@ -219,7 +224,7 @@ public class DaoConfig {
/**
* @since 5.4.0
*/
private boolean myMatchUrlCache;
private boolean myMatchUrlCacheEnabled;
/**
* @since 5.5.0
*/
@ -266,6 +271,26 @@ public class DaoConfig {
}
}
/**
* Sets the tag storage mode for the server. Default is {@link TagStorageModeEnum#VERSIONED}.
*
* @since 5.5.0
*/
@Nonnull
public TagStorageModeEnum getTagStorageMode() {
return myTagStorageMode;
}
/**
* Sets the tag storage mode for the server. Default is {@link TagStorageModeEnum#VERSIONED}.
*
* @since 5.5.0
*/
public void setTagStorageMode(@Nonnull TagStorageModeEnum theTagStorageMode) {
Validate.notNull(theTagStorageMode, "theTagStorageMode must not be null");
myTagStorageMode = theTagStorageMode;
}
/**
* Specifies the maximum number of times that a chunk will be retried during bulk import
* processes before giving up.
@ -421,9 +446,25 @@ public class DaoConfig {
* Default is <code>false</code>
*
* @since 5.4.0
* @deprecated Deprecated in 5.5.0. Use {@link #isMatchUrlCacheEnabled()} instead (the name of this method is misleading)
*/
@Deprecated
public boolean getMatchUrlCache() {
return myMatchUrlCache;
return myMatchUrlCacheEnabled;
}
/**
* If enabled, resolutions for match URLs (e.g. conditional create URLs, conditional update URLs, etc) will be
* cached in an in-memory cache. This cache can have a noticeable improvement on write performance on servers
* where conditional operations are frequently performed, but note that this cache will not be
* invalidated based on updates to resources so this may have detrimental effects.
* <p>
* Default is <code>false</code>
*
* @since 5.5.0
*/
public boolean isMatchUrlCacheEnabled() {
return getMatchUrlCache();
}
/**
@ -435,9 +476,25 @@ public class DaoConfig {
* Default is <code>false</code>
*
* @since 5.4.0
* @deprecated Deprecated in 5.5.0. Use {@link #setMatchUrlCacheEnabled(boolean)} instead (the name of this method is misleading)
*/
@Deprecated
public void setMatchUrlCache(boolean theMatchUrlCache) {
myMatchUrlCache = theMatchUrlCache;
myMatchUrlCacheEnabled = theMatchUrlCache;
}
/**
* If enabled, resolutions for match URLs (e.g. conditional create URLs, conditional update URLs, etc) will be
* cached in an in-memory cache. This cache can have a noticeable improvement on write performance on servers
* where conditional operations are frequently performed, but note that this cache will not be
* invalidated based on updates to resources so this may have detrimental effects.
* <p>
* Default is <code>false</code>
*
* @since 5.5.0
*/
public void setMatchUrlCacheEnabled(boolean theMatchUrlCache) {
setMatchUrlCache(theMatchUrlCache);
}
/**
@ -2548,4 +2605,17 @@ public class DaoConfig {
ANY
}
public enum TagStorageModeEnum {
/**
* A separate set of tags is stored for each resource version
*/
VERSIONED,
/**
* A single set of tags is shared by all resource versions
*/
NON_VERSIONED
}
}

View File

@ -1,6 +1,7 @@
package ca.uhn.fhir.jpa.api.dao;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
@ -26,6 +27,6 @@ import org.hl7.fhir.instance.model.api.IIdType;
public interface IFhirResourceDaoSubscription<T extends IBaseResource> extends IFhirResourceDao<T> {
Long getSubscriptionTablePidForSubscriptionResource(IIdType theId, RequestDetails theRequest);
Long getSubscriptionTablePidForSubscriptionResource(IIdType theId, RequestDetails theRequest, TransactionDetails theTransactionDetails);
}

View File

@ -58,7 +58,6 @@ import ca.uhn.fhir.jpa.searchparam.matcher.InMemoryResourceMatcher;
import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc;
import ca.uhn.fhir.jpa.term.api.ITermReadSvc;
import ca.uhn.fhir.jpa.util.AddRemoveCount;
import ca.uhn.fhir.rest.server.util.CompositeInterceptorBroadcaster;
import ca.uhn.fhir.jpa.util.MemoryCacheService;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
@ -84,6 +83,7 @@ import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.rest.server.util.CompositeInterceptorBroadcaster;
import ca.uhn.fhir.util.CoverageIgnore;
import ca.uhn.fhir.util.HapiExtensions;
import ca.uhn.fhir.util.MetaUtil;
@ -137,6 +137,7 @@ import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
@ -185,6 +186,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
private static final Logger ourLog = LoggerFactory.getLogger(BaseHapiFhirDao.class);
private static final Map<FhirVersionEnum, FhirContext> ourRetrievalContexts = new HashMap<>();
private static final String PROCESSING_SUB_REQUEST = "BaseHapiFhirDao.processingSubRequest";
private static final String TRANSACTION_DETAILS_CACHE_KEY_EXISTING_SEARCH_PARAMS = BaseHapiFhirDao.class.getName() + "_EXISTING_SEARCH_PARAMS";
private static boolean ourValidationDisabledForUnitTest;
private static boolean ourDisableIncrementOnUpdateForUnitTest = false;
@ -394,6 +396,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
@Autowired
public void setContext(FhirContext theContext) {
super.myFhirContext = theContext;
myContext = theContext;
}
@ -668,6 +671,10 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
// Don't check existing - We'll rely on the SHA256 hash only
} else if (theEntity.getVersion() == 1L && theEntity.getCurrentVersionEntity() == null) {
// No previous version if this is the first version
} else {
ResourceHistoryTable currentHistoryVersion = theEntity.getCurrentVersionEntity();
if (currentHistoryVersion == null) {
@ -791,27 +798,23 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
res.getMeta().setLastUpdated(theEntity.getUpdatedDate());
IDao.RESOURCE_PID.put(res, theEntity.getResourceId());
Collection<? extends BaseTag> tags = theTagList;
if (theEntity.isHasTags()) {
for (BaseTag next : tags) {
switch (next.getTag().getTagType()) {
case PROFILE:
res.getMeta().addProfile(next.getTag().getCode());
break;
case SECURITY_LABEL:
IBaseCoding sec = res.getMeta().addSecurity();
sec.setSystem(next.getTag().getSystem());
sec.setCode(next.getTag().getCode());
sec.setDisplay(next.getTag().getDisplay());
break;
case TAG:
IBaseCoding tag = res.getMeta().addTag();
tag.setSystem(next.getTag().getSystem());
tag.setCode(next.getTag().getCode());
tag.setDisplay(next.getTag().getDisplay());
break;
}
for (BaseTag next : theTagList) {
switch (next.getTag().getTagType()) {
case PROFILE:
res.getMeta().addProfile(next.getTag().getCode());
break;
case SECURITY_LABEL:
IBaseCoding sec = res.getMeta().addSecurity();
sec.setSystem(next.getTag().getSystem());
sec.setCode(next.getTag().getCode());
sec.setDisplay(next.getTag().getDisplay());
break;
case TAG:
IBaseCoding tag = res.getMeta().addTag();
tag.setSystem(next.getTag().getSystem());
tag.setCode(next.getTag().getCode());
tag.setDisplay(next.getTag().getDisplay());
break;
}
}
@ -912,7 +915,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
// 1. get resource, it's encoding and the tags if any
byte[] resourceBytes;
ResourceEncodingEnum resourceEncoding;
Collection<? extends BaseTag> myTagList;
Collection<? extends BaseTag> tagList = Collections.emptyList();
long version;
String provenanceSourceUri = null;
String provenanceRequestId = null;
@ -921,10 +924,14 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
ResourceHistoryTable history = (ResourceHistoryTable) theEntity;
resourceBytes = history.getResource();
resourceEncoding = history.getEncoding();
if (history.isHasTags()) {
myTagList = history.getTags();
if (getConfig().getTagStorageMode() == DaoConfig.TagStorageModeEnum.VERSIONED) {
if (history.isHasTags()) {
tagList = history.getTags();
}
} else {
myTagList = Collections.emptyList();
if (history.getResourceTable().isHasTags()) {
tagList = history.getResourceTable().getTags();
}
}
version = history.getVersion();
if (history.getProvenance() != null) {
@ -948,9 +955,9 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
resourceBytes = history.getResource();
resourceEncoding = history.getEncoding();
if (resource.isHasTags()) {
myTagList = resource.getTags();
tagList = resource.getTags();
} else {
myTagList = Collections.emptyList();
tagList = Collections.emptyList();
}
version = history.getVersion();
if (history.getProvenance() != null) {
@ -966,9 +973,9 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
provenanceRequestId = view.getProvenanceRequestId();
provenanceSourceUri = view.getProvenanceSourceUri();
if (theTagList == null)
myTagList = new HashSet<>();
tagList = new HashSet<>();
else
myTagList = theTagList;
tagList = theTagList;
} else {
// something wrong
return null;
@ -980,7 +987,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
// 3. Use the appropriate custom type if one is specified in the context
Class<R> resourceType = theResourceType;
if (myContext.hasDefaultTypeForProfile()) {
for (BaseTag nextTag : myTagList) {
for (BaseTag nextTag : tagList) {
if (nextTag.getTag().getTagType() == TagTypeEnum.PROFILE) {
String profile = nextTag.getTag().getCode();
if (isNotBlank(profile)) {
@ -1030,10 +1037,10 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
// 5. fill MetaData
if (retVal instanceof IResource) {
IResource res = (IResource) retVal;
retVal = populateResourceMetadataHapi(resourceType, theEntity, myTagList, theForHistoryOperation, res, version);
retVal = populateResourceMetadataHapi(resourceType, theEntity, tagList, theForHistoryOperation, res, version);
} else {
IAnyResource res = (IAnyResource) retVal;
retVal = populateResourceMetadataRi(resourceType, theEntity, myTagList, theForHistoryOperation, res, version);
retVal = populateResourceMetadataRi(resourceType, theEntity, tagList, theForHistoryOperation, res, version);
}
// 6. Handle source (provenance)
@ -1152,14 +1159,22 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
changed = populateResourceIntoEntity(theTransactionDetails, theRequest, theResource, entity, true);
} else {
// CREATE or UPDATE
existingParams = new ResourceIndexedSearchParams(entity);
IdentityHashMap<ResourceTable, ResourceIndexedSearchParams> existingSearchParams = theTransactionDetails.getOrCreateUserData(TRANSACTION_DETAILS_CACHE_KEY_EXISTING_SEARCH_PARAMS, () -> new IdentityHashMap<>());
existingParams = existingSearchParams.get(entity);
if (existingParams == null) {
existingParams = new ResourceIndexedSearchParams(entity);
existingSearchParams.put(entity, existingParams);
}
entity.setDeleted(null);
if (thePerformIndexing) {
// TODO: is this IF statement always true? Try removing it
if (thePerformIndexing || ((ResourceTable) theEntity).getVersion() == 1) {
newParams = new ResourceIndexedSearchParams();
mySearchParamWithInlineReferencesExtractor.populateFromResource(newParams, theTransactionDetails, entity, theResource, existingParams, theRequest);
mySearchParamWithInlineReferencesExtractor.populateFromResource(newParams, theTransactionDetails, entity, theResource, existingParams, theRequest, thePerformIndexing);
changed = populateResourceIntoEntity(theTransactionDetails, theRequest, theResource, entity, true);
@ -1175,7 +1190,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
// to match a resource and then update it in a way that it no longer
// matches. We could certainly make this configurable though in the
// future.
if (entity.getVersion() <= 1L && entity.getCreatedByMatchUrl() != null) {
if (entity.getVersion() <= 1L && entity.getCreatedByMatchUrl() != null && thePerformIndexing) {
verifyMatchUrlForConditionalCreate(theResource, entity.getCreatedByMatchUrl(), entity, newParams);
}
@ -1205,7 +1220,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
}
if (thePerformIndexing && changed != null && !changed.isChanged() && !theForceUpdate && myConfig.isSuppressUpdatesWithNoChange()) {
if (thePerformIndexing && changed != null && !changed.isChanged() && !theForceUpdate && myConfig.isSuppressUpdatesWithNoChange() && (entity.getVersion() > 1 || theUpdateVersion)) {
ourLog.debug("Resource {} has not changed", entity.getIdDt().toUnqualified().getValue());
if (theResource != null) {
updateResourceMetadata(entity, theResource);
@ -1245,7 +1260,8 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
* Create history entry
*/
if (theCreateNewHistoryEntry) {
final ResourceHistoryTable historyEntry = entity.toHistory();
boolean versionedTags = getConfig().getTagStorageMode() == DaoConfig.TagStorageModeEnum.VERSIONED;
final ResourceHistoryTable historyEntry = entity.toHistory(versionedTags);
historyEntry.setEncoding(changed.getEncoding());
historyEntry.setResource(changed.getResource());
@ -1575,6 +1591,11 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
// nothing yet
}
@VisibleForTesting
public void setDaoConfigForUnitTest(DaoConfig theDaoConfig) {
myConfig = theDaoConfig;
}
private class AddTagDefinitionToCacheAfterCommitSynchronization implements TransactionSynchronization {
private final TagDefinition myTagDefinition;
@ -1726,11 +1747,6 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
ourDisableIncrementOnUpdateForUnitTest = theDisableIncrementOnUpdateForUnitTest;
}
@VisibleForTesting
public void setDaoConfigForUnitTest(DaoConfig theDaoConfig) {
myConfig = theDaoConfig;
}
/**
* Do not call this method outside of unit tests
*/

View File

@ -35,6 +35,7 @@ import ca.uhn.fhir.jpa.api.model.ExpungeOptions;
import ca.uhn.fhir.jpa.api.model.ExpungeOutcome;
import ca.uhn.fhir.jpa.api.model.LazyDaoMethodOutcome;
import ca.uhn.fhir.jpa.dao.expunge.DeleteExpungeService;
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
import ca.uhn.fhir.jpa.dao.tx.HapiTransactionService;
import ca.uhn.fhir.jpa.delete.DeleteConflictService;
import ca.uhn.fhir.jpa.model.entity.BaseHasResource;
@ -57,7 +58,6 @@ import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc;
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.searchparam.extractor.ResourceIndexedSearchParams;
import ca.uhn.fhir.rest.server.util.CompositeInterceptorBroadcaster;
import ca.uhn.fhir.jpa.util.MemoryCacheService;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.model.dstu2.resource.ListResource;
@ -91,6 +91,7 @@ import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.rest.server.util.CompositeInterceptorBroadcaster;
import ca.uhn.fhir.util.ObjectUtil;
import ca.uhn.fhir.util.OperationOutcomeUtil;
import ca.uhn.fhir.util.ReflectionUtil;
@ -257,9 +258,10 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
entity.setResourceType(toResourceName(theResource));
entity.setPartitionId(theRequestPartitionId);
entity.setCreatedByMatchUrl(theIfNoneExist);
entity.setVersion(1);
if (isNotBlank(theIfNoneExist)) {
Set<ResourcePersistentId> match = myMatchResourceUrlService.processMatchUrl(theIfNoneExist, myResourceType, theRequest);
Set<ResourcePersistentId> match = myMatchResourceUrlService.processMatchUrl(theIfNoneExist, myResourceType, theTransactionDetails, theRequest);
if (match.size() > 1) {
String msg = getContext().getLocalizer().getMessageSanitized(BaseHapiFhirDao.class, "transactionOperationWithMultipleMatchFailure", "CREATE", theIfNoneExist, match.size());
throw new PreconditionFailedException(msg);
@ -338,9 +340,17 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
doCallHooks(theTransactionDetails, theRequest, Pointcut.STORAGE_PRESTORAGE_RESOURCE_CREATED, hookParams);
// Perform actual DB update
ResourceTable updatedEntity = updateEntity(theRequest, theResource, entity, null, thePerformIndexing, thePerformIndexing, theTransactionDetails, false, thePerformIndexing);
ResourceTable updatedEntity = updateEntity(theRequest, theResource, entity, null, thePerformIndexing, false, theTransactionDetails, false, thePerformIndexing);
IIdType id = myFhirContext.getVersion().newIdType().setValue(updatedEntity.getIdDt().toUnqualifiedVersionless().getValue());
ResourcePersistentId persistentId = new ResourcePersistentId(updatedEntity.getResourceId());
theTransactionDetails.addResolvedResourceId(id, persistentId);
if (entity.getForcedId() != null) {
myIdHelperService.addResolvedPidToForcedId(persistentId, theRequestPartitionId, updatedEntity.getResourceType(), updatedEntity.getForcedId().getForcedId());
}
theResource.setId(entity.getIdDt());
if (serverAssignedId) {
switch (getConfig().getResourceClientIdStrategy()) {
case NOT_ALLOWED:
@ -357,16 +367,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
if (theIfNoneExist != null) {
// Pre-cache the match URL
myMatchResourceUrlService.matchUrlResolved(theIfNoneExist, new ResourcePersistentId(entity.getResourceId()));
}
/*
* If we aren't indexing (meaning we're probably executing a sub-operation within a transaction),
* we'll manually increase the version. This is important because we want the updated version number
* to be reflected in the resource shared with interceptors
*/
if (!thePerformIndexing) {
incrementId(theResource, entity, theResource.getIdElement());
myMatchResourceUrlService.matchUrlResolved(theTransactionDetails, getResourceName(), theIfNoneExist, new ResourcePersistentId(entity.getResourceId()));
}
// Update the version/last updated in the resource so that interceptors get
@ -399,9 +400,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
if (updatedEntity.getForcedId() != null) {
forcedId = updatedEntity.getForcedId().getForcedId();
}
if (myIdHelperService != null) {
myIdHelperService.addResolvedPidToForcedId(new ResourcePersistentId(updatedEntity.getResourceId()), theRequestPartitionId, getResourceName(), forcedId);
}
myIdHelperService.addResolvedPidToForcedId(persistentId, theRequestPartitionId, getResourceName(), forcedId);
ourLog.debug(msg);
return outcome;
@ -443,7 +442,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
validateIdPresentForDelete(theId);
validateDeleteEnabled();
final ResourceTable entity = readEntityLatestVersion(theId, theRequestDetails);
final ResourceTable entity = readEntityLatestVersion(theId, theRequestDetails, theTransactionDetails);
if (theId.hasVersionIdPart() && Long.parseLong(theId.getVersionIdPart()) != entity.getVersion()) {
throw new ResourceVersionConflictException("Trying to delete " + theId + " but this is not the current version");
}
@ -913,7 +912,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
throw new ResourceNotFoundException(theResourceId);
}
ResourceTable latestVersion = readEntityLatestVersion(theResourceId, theRequest);
ResourceTable latestVersion = readEntityLatestVersion(theResourceId, theRequest, transactionDetails);
if (latestVersion.getVersion() != entity.getVersion()) {
doMetaAdd(theMetaAdd, entity, theRequest, transactionDetails);
} else {
@ -948,7 +947,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
throw new ResourceNotFoundException(theResourceId);
}
ResourceTable latestVersion = readEntityLatestVersion(theResourceId, theRequest);
ResourceTable latestVersion = readEntityLatestVersion(theResourceId, theRequest, transactionDetails);
if (latestVersion.getVersion() != entity.getVersion()) {
doMetaDelete(theMetaDel, entity, theRequest, transactionDetails);
} else {
@ -1007,14 +1006,14 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
@Override
public DaoMethodOutcome patch(IIdType theId, String theConditionalUrl, PatchTypeEnum thePatchType, String thePatchBody, IBaseParameters theFhirPatchBody, RequestDetails theRequest) {
return myTransactionService.execute(theRequest, tx -> doPatch(theId, theConditionalUrl, thePatchType, thePatchBody, theFhirPatchBody, theRequest));
return myTransactionService.execute(theRequest, tx -> doPatch(theId, theConditionalUrl, thePatchType, thePatchBody, theFhirPatchBody, theRequest, new TransactionDetails()));
}
private DaoMethodOutcome doPatch(IIdType theId, String theConditionalUrl, PatchTypeEnum thePatchType, String thePatchBody, IBaseParameters theFhirPatchBody, RequestDetails theRequest) {
private DaoMethodOutcome doPatch(IIdType theId, String theConditionalUrl, PatchTypeEnum thePatchType, String thePatchBody, IBaseParameters theFhirPatchBody, RequestDetails theRequest, TransactionDetails theTransactionDetails) {
ResourceTable entityToUpdate;
if (isNotBlank(theConditionalUrl)) {
Set<ResourcePersistentId> match = myMatchResourceUrlService.processMatchUrl(theConditionalUrl, myResourceType, theRequest);
Set<ResourcePersistentId> match = myMatchResourceUrlService.processMatchUrl(theConditionalUrl, myResourceType, theTransactionDetails, theRequest);
if (match.size() > 1) {
String msg = getContext().getLocalizer().getMessageSanitized(BaseHapiFhirDao.class, "transactionOperationWithMultipleMatchFailure", "PATCH", theConditionalUrl, match.size());
throw new PreconditionFailedException(msg);
@ -1027,7 +1026,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
}
} else {
entityToUpdate = readEntityLatestVersion(theId, theRequest);
entityToUpdate = readEntityLatestVersion(theId, theRequest, theTransactionDetails);
if (theId.hasVersionIdPart()) {
if (theId.getVersionIdPartAsLong() != entityToUpdate.getVersion()) {
throw new ResourceVersionConflictException("Version " + theId.getVersionIdPart() + " is not the most recent version of this resource, unable to apply patch");
@ -1064,7 +1063,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
@Override
public void start() {
assert getConfig() != null;
ourLog.debug("Starting resource DAO for type: {}", getResourceName());
myInstanceValidator = getApplicationContext().getBean(IInstanceValidatorModule.class);
myTxTemplate = new TransactionTemplate(myPlatformTransactionManager);
@ -1252,15 +1251,19 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
}
@Nonnull
protected ResourceTable readEntityLatestVersion(IIdType theId, RequestDetails theRequestDetails) {
protected ResourceTable readEntityLatestVersion(IIdType theId, RequestDetails theRequestDetails, TransactionDetails theTransactionDetails) {
RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineReadPartitionForRequest(theRequestDetails, getResourceName());
return readEntityLatestVersion(theId, requestPartitionId);
return readEntityLatestVersion(theId, requestPartitionId, theTransactionDetails);
}
@Nonnull
private ResourceTable readEntityLatestVersion(IIdType theId, @Nullable RequestPartitionId theRequestPartitionId) {
private ResourceTable readEntityLatestVersion(IIdType theId, @Nullable RequestPartitionId theRequestPartitionId, TransactionDetails theTransactionDetails) {
validateResourceTypeAndThrowInvalidRequestException(theId);
if (theTransactionDetails.isResolvedResourceIdEmpty(theId.toUnqualifiedVersionless())) {
throw new ResourceNotFoundException(theId);
}
ResourcePersistentId persistentId = myIdHelperService.resolveResourcePersistentIds(theRequestPartitionId, getResourceName(), theId.getIdPart());
ResourceTable entity = myEntityManager.find(ResourceTable.class, persistentId.getId());
if (entity == null) {
@ -1569,7 +1572,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
IIdType resourceId;
if (isNotBlank(theMatchUrl)) {
Set<ResourcePersistentId> match = myMatchResourceUrlService.processMatchUrl(theMatchUrl, myResourceType, theRequest);
Set<ResourcePersistentId> match = myMatchResourceUrlService.processMatchUrl(theMatchUrl, myResourceType, theTransactionDetails, theRequest);
if (match.size() > 1) {
String msg = getContext().getLocalizer().getMessageSanitized(BaseHapiFhirDao.class, "transactionOperationWithMultipleMatchFailure", "UPDATE", theMatchUrl, match.size());
throw new PreconditionFailedException(msg);
@ -1582,7 +1585,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
// Pre-cache the match URL
if (outcome.getPersistentId() != null) {
myMatchResourceUrlService.matchUrlResolved(theMatchUrl, outcome.getPersistentId());
myMatchResourceUrlService.matchUrlResolved(theTransactionDetails, getResourceName(), theMatchUrl, outcome.getPersistentId());
}
return outcome;
@ -1610,7 +1613,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
if (!create) {
try {
entity = readEntityLatestVersion(resourceId, requestPartitionId);
entity = readEntityLatestVersion(resourceId, requestPartitionId, theTransactionDetails);
} catch (ResourceNotFoundException e) {
create = true;
}
@ -1692,6 +1695,8 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
@Override
@Transactional(propagation = Propagation.SUPPORTS)
public MethodOutcome validate(T theResource, IIdType theId, String theRawResource, EncodingEnum theEncoding, ValidationModeEnum theMode, String theProfile, RequestDetails theRequest) {
TransactionDetails transactionDetails = new TransactionDetails();
if (theRequest != null) {
ActionRequestDetails requestDetails = new ActionRequestDetails(theRequest, theResource, null, theId);
notifyInterceptors(RestOperationTypeEnum.VALIDATE, requestDetails);
@ -1701,7 +1706,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
if (theId == null || theId.hasIdPart() == false) {
throw new InvalidRequestException("No ID supplied. ID is required when validating with mode=DELETE");
}
final ResourceTable entity = readEntityLatestVersion(theId, theRequest);
final ResourceTable entity = readEntityLatestVersion(theId, theRequest, transactionDetails);
// Validate that there are no resources pointing to the candidate that
// would prevent deletion
@ -1799,6 +1804,11 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
}
}
@VisibleForTesting
public void setIdHelperSvcForUnitTest(IdHelperService theIdHelperService) {
myIdHelperService = theIdHelperService;
}
private static class IdChecker implements IValidatorModule {
private final ValidationModeEnum myMode;

View File

@ -145,6 +145,20 @@ public abstract class BaseTransactionProcessor {
@Autowired
private InMemoryResourceMatcher myInMemoryResourceMatcher;
@VisibleForTesting
public void setDaoConfig(DaoConfig theDaoConfig) {
myDaoConfig = theDaoConfig;
}
public ITransactionProcessorVersionAdapter getVersionAdapter() {
return myVersionAdapter;
}
@VisibleForTesting
public void setVersionAdapter(ITransactionProcessorVersionAdapter theVersionAdapter) {
myVersionAdapter = theVersionAdapter;
}
@PostConstruct
public void start() {
ourLog.trace("Starting transaction processor");
@ -287,11 +301,6 @@ public abstract class BaseTransactionProcessor {
}
}
@VisibleForTesting
public void setVersionAdapter(ITransactionProcessorVersionAdapter theVersionAdapter) {
myVersionAdapter = theVersionAdapter;
}
@VisibleForTesting
public void setTxManager(PlatformTransactionManager theTxManager) {
myTxManager = theTxManager;
@ -582,8 +591,8 @@ public abstract class BaseTransactionProcessor {
myModelConfig = theModelConfig;
}
private Map<IBase, IIdType> doTransactionWriteOperations(final RequestDetails theRequest, String theActionName, TransactionDetails theTransactionDetails, Set<IIdType> theAllIds,
Map<IIdType, IIdType> theIdSubstitutions, Map<IIdType, DaoMethodOutcome> theIdToPersistedOutcome, IBaseBundle theResponse, IdentityHashMap<IBase, Integer> theOriginalRequestOrder, List<IBase> theEntries, StopWatch theTransactionStopWatch) {
protected Map<IBase, IIdType> doTransactionWriteOperations(final RequestDetails theRequest, String theActionName, TransactionDetails theTransactionDetails, Set<IIdType> theAllIds,
Map<IIdType, IIdType> theIdSubstitutions, Map<IIdType, DaoMethodOutcome> theIdToPersistedOutcome, IBaseBundle theResponse, IdentityHashMap<IBase, Integer> theOriginalRequestOrder, List<IBase> theEntries, StopWatch theTransactionStopWatch) {
theTransactionDetails.beginAcceptingDeferredInterceptorBroadcasts(
Pointcut.STORAGE_PRECOMMIT_RESOURCE_CREATED,
@ -1067,7 +1076,7 @@ public abstract class BaseTransactionProcessor {
if (!nextId.hasIdPart()) {
if (resourceReference.getResource() != null) {
IIdType targetId = resourceReference.getResource().getIdElement();
if (targetId.getValue() == null) {
if (targetId.getValue() == null || targetId.getValue().startsWith("#")) {
// This means it's a contained resource
continue;
} else if (theIdSubstitutions.containsValue(targetId)) {
@ -1258,7 +1267,6 @@ public abstract class BaseTransactionProcessor {
return dao;
}
private String toResourceName(Class<? extends IBaseResource> theResourceType) {
return myContext.getResourceType(theResourceType);
}
@ -1318,11 +1326,6 @@ public abstract class BaseTransactionProcessor {
return null;
}
@VisibleForTesting
public void setDaoConfig(DaoConfig theDaoConfig) {
myDaoConfig = theDaoConfig;
}
public interface ITransactionProcessorVersionAdapter<BUNDLE extends IBaseBundle, BUNDLEENTRY extends IBase> {
void setResponseStatus(BUNDLEENTRY theBundleEntry, String theStatus);

View File

@ -51,8 +51,8 @@ public class FhirResourceDaoSubscriptionDstu2 extends BaseHapiFhirResourceDao<Su
}
@Override
public Long getSubscriptionTablePidForSubscriptionResource(IIdType theId, RequestDetails theRequest) {
ResourceTable entity = readEntityLatestVersion(theId, theRequest);
public Long getSubscriptionTablePidForSubscriptionResource(IIdType theId, RequestDetails theRequest, TransactionDetails theTransactionDetails) {
ResourceTable entity = readEntityLatestVersion(theId, theRequest, theTransactionDetails);
SubscriptionTable table = mySubscriptionTableDao.findOneByResourcePid(entity.getId());
if (table == null) {
return null;

View File

@ -31,22 +31,26 @@ import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage;
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.rest.server.util.CompositeInterceptorBroadcaster;
import ca.uhn.fhir.jpa.util.MemoryCacheService;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.rest.server.util.CompositeInterceptorBroadcaster;
import ca.uhn.fhir.util.StopWatch;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.Nullable;
import java.util.Collections;
import java.util.Set;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
@Service
public class MatchResourceUrlService {
@Autowired
@ -65,31 +69,63 @@ public class MatchResourceUrlService {
/**
* Note that this will only return a maximum of 2 results!!
*/
public <R extends IBaseResource> Set<ResourcePersistentId> processMatchUrl(String theMatchUrl, Class<R> theResourceType, RequestDetails theRequest) {
if (myDaoConfig.getMatchUrlCache()) {
ResourcePersistentId existing = myMemoryCacheService.getIfPresent(MemoryCacheService.CacheEnum.MATCH_URL, theMatchUrl);
if (existing != null) {
return Collections.singleton(existing);
public <R extends IBaseResource> Set<ResourcePersistentId> processMatchUrl(String theMatchUrl, Class<R> theResourceType, TransactionDetails theTransactionDetails, RequestDetails theRequest) {
String resourceType = myContext.getResourceType(theResourceType);
String matchUrl = massageForStorage(resourceType, theMatchUrl);
ResourcePersistentId resolvedInTransaction = theTransactionDetails.getResolvedMatchUrls().get(matchUrl);
if (resolvedInTransaction != null) {
if (resolvedInTransaction == TransactionDetails.NOT_FOUND) {
return Collections.emptySet();
} else {
return Collections.singleton(resolvedInTransaction);
}
}
ResourcePersistentId resolvedInCache = processMatchUrlUsingCacheOnly(resourceType, matchUrl);
if (resolvedInCache != null) {
return Collections.singleton(resolvedInCache);
}
RuntimeResourceDefinition resourceDef = myContext.getResourceDefinition(theResourceType);
SearchParameterMap paramMap = myMatchUrlService.translateMatchUrl(theMatchUrl, resourceDef);
SearchParameterMap paramMap = myMatchUrlService.translateMatchUrl(matchUrl, resourceDef);
if (paramMap.isEmpty() && paramMap.getLastUpdated() == null) {
throw new InvalidRequestException("Invalid match URL[" + theMatchUrl + "] - URL has no search parameters");
throw new InvalidRequestException("Invalid match URL[" + matchUrl + "] - URL has no search parameters");
}
paramMap.setLoadSynchronousUpTo(2);
Set<ResourcePersistentId> retVal = search(paramMap, theResourceType, theRequest);
if (myDaoConfig.getMatchUrlCache() && retVal.size() == 1) {
if (myDaoConfig.isMatchUrlCacheEnabled() && retVal.size() == 1) {
ResourcePersistentId pid = retVal.iterator().next();
myMemoryCacheService.putAfterCommit(MemoryCacheService.CacheEnum.MATCH_URL, theMatchUrl, pid);
myMemoryCacheService.putAfterCommit(MemoryCacheService.CacheEnum.MATCH_URL, matchUrl, pid);
}
return retVal;
}
private String massageForStorage(String theResourceType, String theMatchUrl) {
Validate.notBlank(theMatchUrl, "theMatchUrl must not be null or blank");
int questionMarkIdx = theMatchUrl.indexOf("?");
if (questionMarkIdx > 0) {
return theMatchUrl;
}
if (questionMarkIdx == 0) {
return theResourceType + theMatchUrl;
}
return theResourceType + "?" + theMatchUrl;
}
@Nullable
public ResourcePersistentId processMatchUrlUsingCacheOnly(String theResourceType, String theMatchUrl) {
ResourcePersistentId existing = null;
if (myDaoConfig.getMatchUrlCache()) {
String matchUrl = massageForStorage(theResourceType, theMatchUrl);
existing = myMemoryCacheService.getIfPresent(MemoryCacheService.CacheEnum.MATCH_URL, matchUrl);
}
return existing;
}
public <R extends IBaseResource> Set<ResourcePersistentId> search(SearchParameterMap theParamMap, Class<R> theResourceType, RequestDetails theRequest) {
StopWatch sw = new StopWatch();
IFhirResourceDao<R> dao = myDaoRegistry.getResourceDao(theResourceType);
@ -113,11 +149,14 @@ public class MatchResourceUrlService {
}
public void matchUrlResolved(String theMatchUrl, ResourcePersistentId theResourcePersistentId) {
public void matchUrlResolved(TransactionDetails theTransactionDetails, String theResourceType, String theMatchUrl, ResourcePersistentId theResourcePersistentId) {
Validate.notBlank(theMatchUrl);
Validate.notNull(theResourcePersistentId);
if (myDaoConfig.getMatchUrlCache()) {
myMemoryCacheService.putAfterCommit(MemoryCacheService.CacheEnum.MATCH_URL, theMatchUrl, theResourcePersistentId);
String matchUrl = massageForStorage(theResourceType, theMatchUrl);
theTransactionDetails.addResolvedMatchUrl(matchUrl, theResourcePersistentId);
if (myDaoConfig.isMatchUrlCacheEnabled()) {
myMemoryCacheService.putAfterCommit(MemoryCacheService.CacheEnum.MATCH_URL, matchUrl, theResourcePersistentId);
}
}
}

View File

@ -20,12 +20,31 @@ package ca.uhn.fhir.jpa.dao;
* #L%
*/
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome;
import ca.uhn.fhir.jpa.config.HapiFhirHibernateJpaDialect;
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc;
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.util.StopWatch;
import com.google.common.annotations.VisibleForTesting;
import org.apache.commons.lang3.Validate;
import org.hibernate.Session;
import org.hibernate.internal.SessionImpl;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -35,17 +54,48 @@ import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceContextType;
import javax.persistence.PersistenceException;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static ca.uhn.fhir.jpa.dao.index.IdHelperService.EMPTY_PREDICATE_ARRAY;
import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class TransactionProcessor extends BaseTransactionProcessor {
public static final Pattern SINGLE_PARAMETER_MATCH_URL_PATTERN = Pattern.compile("^[^?]+[?][a-z0-9-]+=[^&,]+$");
private static final Logger ourLog = LoggerFactory.getLogger(TransactionProcessor.class);
@PersistenceContext(type = PersistenceContextType.TRANSACTION)
private EntityManager myEntityManager;
@Autowired(required = false)
private HapiFhirHibernateJpaDialect myHapiFhirHibernateJpaDialect;
@Autowired
private IdHelperService myIdHelperService;
@Autowired
private PartitionSettings myPartitionSettings;
@Autowired
private DaoConfig myDaoConfig;
@Autowired
private FhirContext myFhirContext;
@Autowired
private MatchResourceUrlService myMatchResourceUrlService;
@Autowired
private MatchUrlService myMatchUrlService;
@Autowired
private IRequestPartitionHelperSvc myRequestPartitionSvc;
public void setEntityManagerForUnitTest(EntityManager theEntityManager) {
myEntityManager = theEntityManager;
@ -58,6 +108,225 @@ public class TransactionProcessor extends BaseTransactionProcessor {
Validate.notNull(myEntityManager);
}
@VisibleForTesting
public void setFhirContextForUnitTest(FhirContext theFhirContext) {
myFhirContext = theFhirContext;
}
@Override
protected Map<IBase, IIdType> doTransactionWriteOperations(final RequestDetails theRequest, String theActionName, TransactionDetails theTransactionDetails, Set<IIdType> theAllIds,
Map<IIdType, IIdType> theIdSubstitutions, Map<IIdType, DaoMethodOutcome> theIdToPersistedOutcome, IBaseBundle theResponse, IdentityHashMap<IBase, Integer> theOriginalRequestOrder, List<IBase> theEntries, StopWatch theTransactionStopWatch) {
ITransactionProcessorVersionAdapter versionAdapter = getVersionAdapter();
RequestPartitionId requestPartitionId = null;
if (!myPartitionSettings.isPartitioningEnabled()) {
requestPartitionId = RequestPartitionId.allPartitions();
} else {
// If all entries in the transaction point to the exact same partition, we'll try and do a pre-fetch
Set<RequestPartitionId> requestPartitionIdsForAllEntries = new HashSet<>();
for (IBase nextEntry : theEntries) {
IBaseResource resource = versionAdapter.getResource(nextEntry);
if (resource != null) {
RequestPartitionId requestPartition = myRequestPartitionSvc.determineReadPartitionForRequest(theRequest, myFhirContext.getResourceType(resource));
requestPartitionIdsForAllEntries.add(requestPartition);
}
}
if (requestPartitionIdsForAllEntries.size() == 1) {
requestPartitionId = requestPartitionIdsForAllEntries.iterator().next();
}
}
if (requestPartitionId != null) {
Set<String> foundIds = new HashSet<>();
List<Long> idsToPreFetch = new ArrayList<>();
/*
* Pre-Fetch any resources that are referred to normally by ID, e.g.
* regular FHIR updates within the transaction.
*/
List<IIdType> idsToPreResolve = new ArrayList<>();
for (IBase nextEntry : theEntries) {
IBaseResource resource = versionAdapter.getResource(nextEntry);
if (resource != null) {
String fullUrl = versionAdapter.getFullUrl(nextEntry);
boolean isPlaceholder = defaultString(fullUrl).startsWith("urn:");
if (!isPlaceholder) {
if (resource.getIdElement().hasIdPart() && resource.getIdElement().hasResourceType()) {
idsToPreResolve.add(resource.getIdElement());
}
}
}
}
List<ResourcePersistentId> outcome = myIdHelperService.resolveResourcePersistentIdsWithCache(requestPartitionId, idsToPreResolve);
for (ResourcePersistentId next : outcome) {
foundIds.add(next.getAssociatedResourceId().toUnqualifiedVersionless().getValue());
theTransactionDetails.addResolvedResourceId(next.getAssociatedResourceId(), next);
if (myDaoConfig.getResourceClientIdStrategy() != DaoConfig.ClientIdStrategyEnum.ANY || !next.getAssociatedResourceId().isIdPartValidLong()) {
idsToPreFetch.add(next.getIdAsLong());
}
}
for (IIdType next : idsToPreResolve) {
if (!foundIds.contains(next.toUnqualifiedVersionless().getValue())) {
theTransactionDetails.addResolvedResourceId(next.toUnqualifiedVersionless(), null);
}
}
/*
* Pre-resolve any conditional URLs we can
*/
List<MatchUrlToResolve> searchParameterMapsToResolve = new ArrayList<>();
for (IBase nextEntry : theEntries) {
IBaseResource resource = versionAdapter.getResource(nextEntry);
if (resource != null) {
String verb = versionAdapter.getEntryRequestVerb(myFhirContext, nextEntry);
String requestUrl = versionAdapter.getEntryRequestUrl(nextEntry);
String requestIfNoneExist = versionAdapter.getEntryIfNoneExist(nextEntry);
String resourceType = myFhirContext.getResourceType(resource);
if ("PUT".equals(verb) && requestUrl != null && requestUrl.contains("?")) {
ResourcePersistentId cachedId = myMatchResourceUrlService.processMatchUrlUsingCacheOnly(resourceType, requestUrl);
if (cachedId != null) {
idsToPreFetch.add(cachedId.getIdAsLong());
} else if (SINGLE_PARAMETER_MATCH_URL_PATTERN.matcher(requestUrl).matches()) {
RuntimeResourceDefinition resourceDefinition = myFhirContext.getResourceDefinition(resource);
SearchParameterMap matchUrlSearchMap = myMatchUrlService.translateMatchUrl(requestUrl, resourceDefinition);
searchParameterMapsToResolve.add(new MatchUrlToResolve(requestUrl, matchUrlSearchMap, resourceDefinition));
}
} else if ("POST".equals(verb) && requestIfNoneExist != null && requestIfNoneExist.contains("?")) {
ResourcePersistentId cachedId = myMatchResourceUrlService.processMatchUrlUsingCacheOnly(resourceType, requestIfNoneExist);
if (cachedId != null) {
idsToPreFetch.add(cachedId.getIdAsLong());
} else if (SINGLE_PARAMETER_MATCH_URL_PATTERN.matcher(requestIfNoneExist).matches()) {
RuntimeResourceDefinition resourceDefinition = myFhirContext.getResourceDefinition(resource);
SearchParameterMap matchUrlSearchMap = myMatchUrlService.translateMatchUrl(requestIfNoneExist, resourceDefinition);
searchParameterMapsToResolve.add(new MatchUrlToResolve(requestIfNoneExist, matchUrlSearchMap, resourceDefinition));
}
}
}
}
if (searchParameterMapsToResolve.size() > 0) {
CriteriaBuilder cb = myEntityManager.getCriteriaBuilder();
CriteriaQuery<ResourceIndexedSearchParamToken> cq = cb.createQuery(ResourceIndexedSearchParamToken.class);
Root<ResourceIndexedSearchParamToken> from = cq.from(ResourceIndexedSearchParamToken.class);
List<Predicate> orPredicates = new ArrayList<>();
for (MatchUrlToResolve next : searchParameterMapsToResolve) {
Collection<List<List<IQueryParameterType>>> values = next.myMatchUrlSearchMap.values();
if (values.size() == 1) {
List<List<IQueryParameterType>> andList = values.iterator().next();
IQueryParameterType param = andList.get(0).get(0);
if (param instanceof TokenParam) {
TokenParam tokenParam = (TokenParam) param;
Predicate hashPredicate = null;
if (isNotBlank(tokenParam.getValue()) && isNotBlank(tokenParam.getSystem())) {
next.myHashSystemAndValue = ResourceIndexedSearchParamToken.calculateHashSystemAndValue(myPartitionSettings, requestPartitionId, next.myResourceDefinition.getName(), next.myMatchUrlSearchMap.keySet().iterator().next(), tokenParam.getSystem(), tokenParam.getValue());
hashPredicate = cb.equal(from.get("myHashSystemAndValue").as(Long.class), next.myHashSystemAndValue);
} else if (isNotBlank(tokenParam.getValue())) {
next.myHashValue = ResourceIndexedSearchParamToken.calculateHashValue(myPartitionSettings, requestPartitionId, next.myResourceDefinition.getName(), next.myMatchUrlSearchMap.keySet().iterator().next(), tokenParam.getValue());
hashPredicate = cb.equal(from.get("myHashValue").as(Long.class), next.myHashValue);
}
if (hashPredicate != null) {
if (myPartitionSettings.isPartitioningEnabled() && !myPartitionSettings.isIncludePartitionInSearchHashes()) {
if (requestPartitionId.isDefaultPartition()) {
Predicate partitionIdCriteria = cb.isNull(from.get("myPartitionIdValue").as(Integer.class));
hashPredicate = cb.and(hashPredicate, partitionIdCriteria);
} else if (!requestPartitionId.isAllPartitions()) {
Predicate partitionIdCriteria = from.get("myPartitionIdValue").as(Integer.class).in(requestPartitionId.getPartitionIds());
hashPredicate = cb.and(hashPredicate, partitionIdCriteria);
}
}
orPredicates.add(hashPredicate);
}
}
}
}
if (orPredicates.size() > 1) {
cq.where(cb.or(orPredicates.toArray(EMPTY_PREDICATE_ARRAY)));
TypedQuery<ResourceIndexedSearchParamToken> query = myEntityManager.createQuery(cq);
List<ResourceIndexedSearchParamToken> results = query.getResultList();
for (ResourceIndexedSearchParamToken nextResult : results) {
for (MatchUrlToResolve nextSearchParameterMap : searchParameterMapsToResolve) {
if (nextSearchParameterMap.myHashSystemAndValue != null && nextSearchParameterMap.myHashSystemAndValue.equals(nextResult.getHashSystemAndValue())) {
idsToPreFetch.add(nextResult.getResourcePid());
myMatchResourceUrlService.matchUrlResolved(theTransactionDetails, nextSearchParameterMap.myResourceDefinition.getName(), nextSearchParameterMap.myRequestUrl, new ResourcePersistentId(nextResult.getResourcePid()));
theTransactionDetails.addResolvedMatchUrl(nextSearchParameterMap.myRequestUrl, new ResourcePersistentId(nextResult.getResourcePid()));
nextSearchParameterMap.myResolved = true;
}
if (nextSearchParameterMap.myHashValue != null && nextSearchParameterMap.myHashValue.equals(nextResult.getHashValue())) {
idsToPreFetch.add(nextResult.getResourcePid());
myMatchResourceUrlService.matchUrlResolved(theTransactionDetails, nextSearchParameterMap.myResourceDefinition.getName(), nextSearchParameterMap.myRequestUrl, new ResourcePersistentId(nextResult.getResourcePid()));
theTransactionDetails.addResolvedMatchUrl(nextSearchParameterMap.myRequestUrl, new ResourcePersistentId(nextResult.getResourcePid()));
nextSearchParameterMap.myResolved = true;
}
}
}
for (MatchUrlToResolve nextSearchParameterMap : searchParameterMapsToResolve) {
// No matches
if (!nextSearchParameterMap.myResolved) {
theTransactionDetails.addResolvedMatchUrl(nextSearchParameterMap.myRequestUrl, TransactionDetails.NOT_FOUND);
}
}
}
}
/*
* Pre-fetch the resources we're touching in this transaction in mass - this reduced the
* number of database round trips.
*
* The thresholds below are kind of arbitrary. It's not
* actually guaranteed that this pre-fetching will help (e.g. if a Bundle contains
* a bundle of NOP conditional creates for example, the pre-fetching is actually loading
* more data than would otherwise be loaded).
*
* However, for realistic average workloads, this should reduce the number of round trips.
*/
if (idsToPreFetch.size() > 2) {
List<ResourceTable> loadedResourceTableEntries = preFetchIndexes(idsToPreFetch, "forcedId", "myForcedId");
if (loadedResourceTableEntries.stream().filter(t -> t.isParamsStringPopulated()).count() > 1) {
preFetchIndexes(idsToPreFetch, "string", "myParamsString");
}
if (loadedResourceTableEntries.stream().filter(t -> t.isParamsTokenPopulated()).count() > 1) {
preFetchIndexes(idsToPreFetch, "token", "myParamsToken");
}
if (loadedResourceTableEntries.stream().filter(t -> t.isParamsDatePopulated()).count() > 1) {
preFetchIndexes(idsToPreFetch, "date", "myParamsDate");
}
if (loadedResourceTableEntries.stream().filter(t -> t.isParamsDatePopulated()).count() > 1) {
preFetchIndexes(idsToPreFetch, "quantity", "myParamsQuantity");
}
if (loadedResourceTableEntries.stream().filter(t -> t.isHasLinks()).count() > 1) {
preFetchIndexes(idsToPreFetch, "resourceLinks", "myResourceLinks");
}
}
}
return super.doTransactionWriteOperations(theRequest, theActionName, theTransactionDetails, theAllIds, theIdSubstitutions, theIdToPersistedOutcome, theResponse, theOriginalRequestOrder, theEntries, theTransactionStopWatch);
}
private List<ResourceTable> preFetchIndexes(List<Long> ids, String typeDesc, String fieldName) {
TypedQuery<ResourceTable> query = myEntityManager.createQuery("FROM ResourceTable r LEFT JOIN FETCH r." + fieldName + " WHERE r.myId IN ( :IDS )", ResourceTable.class);
query.setParameter("IDS", ids);
List<ResourceTable> indexFetchOutcome = query.getResultList();
ourLog.debug("Pre-fetched {} {}} indexes", indexFetchOutcome.size(), typeDesc);
return indexFetchOutcome;
}
@Override
protected void flushSession(Map<IIdType, DaoMethodOutcome> theIdToPersistedOutcome) {
@ -86,5 +355,29 @@ public class TransactionProcessor extends BaseTransactionProcessor {
}
}
@VisibleForTesting
public void setPartitionSettingsForUnitTest(PartitionSettings thePartitionSettings) {
myPartitionSettings = thePartitionSettings;
}
@VisibleForTesting
public void setIdHelperServiceForUnitTest(IdHelperService theIdHelperService) {
myIdHelperService = theIdHelperService;
}
private static class MatchUrlToResolve {
private final String myRequestUrl;
private final SearchParameterMap myMatchUrlSearchMap;
private final RuntimeResourceDefinition myResourceDefinition;
public boolean myResolved;
private Long myHashValue;
private Long myHashSystemAndValue;
public MatchUrlToResolve(String theRequestUrl, SearchParameterMap theMatchUrlSearchMap, RuntimeResourceDefinition theResourceDefinition) {
myRequestUrl = theRequestUrl;
myMatchUrlSearchMap = theMatchUrlSearchMap;
myResourceDefinition = theResourceDefinition;
}
}
}

View File

@ -48,8 +48,8 @@ public class FhirResourceDaoSubscriptionDstu3 extends BaseHapiFhirResourceDao<Su
}
@Override
public Long getSubscriptionTablePidForSubscriptionResource(IIdType theId, RequestDetails theRequest) {
ResourceTable entity = readEntityLatestVersion(theId, theRequest);
public Long getSubscriptionTablePidForSubscriptionResource(IIdType theId, RequestDetails theRequest, TransactionDetails theTransactionDetails) {
ResourceTable entity = readEntityLatestVersion(theId, theRequest, theTransactionDetails);
SubscriptionTable table = mySubscriptionTableDao.findOneByResourcePid(entity.getId());
if (table == null) {
return null;

View File

@ -28,6 +28,7 @@ import ca.uhn.fhir.jpa.dao.data.IResourceTableDao;
import ca.uhn.fhir.jpa.model.cross.IResourceLookup;
import ca.uhn.fhir.jpa.model.cross.ResourceLookup;
import ca.uhn.fhir.jpa.model.entity.ForcedId;
import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.util.MemoryCacheService;
import ca.uhn.fhir.jpa.util.QueryChunker;
@ -50,11 +51,20 @@ import org.springframework.stereotype.Service;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceContextType;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@ -86,6 +96,7 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class IdHelperService {
private static final String RESOURCE_PID = "RESOURCE_PID";
private static final Logger ourLog = LoggerFactory.getLogger(IdHelperService.class);
public static final Predicate[] EMPTY_PREDICATE_ARRAY = new Predicate[0];
@Autowired
protected IForcedIdDao myForcedIdDao;
@Autowired
@ -167,6 +178,9 @@ public class IdHelperService {
return RequestPartitionId.stringifyForKey(theRequestPartitionId) + "/" + theResourceType + "/" + theId;
}
@PersistenceContext(type = PersistenceContextType.TRANSACTION)
private EntityManager myEntityManager;
/**
* Given a collection of resource IDs (resource type + id), resolves the internal persistent IDs.
* <p>
@ -181,71 +195,80 @@ public class IdHelperService {
return Collections.emptyList();
}
List<ResourcePersistentId> retVal = new ArrayList<>();
List<ResourcePersistentId> retVal = new ArrayList<>(theIds.size());
if (myDaoConfig.getResourceClientIdStrategy() != DaoConfig.ClientIdStrategyEnum.ANY) {
theIds
.stream()
.filter(IdHelperService::isValidPid)
.map(IIdType::getIdPartAsLong)
.map(ResourcePersistentId::new)
.forEach(retVal::add);
Set<IIdType> idsToCheck = new HashSet<>(theIds.size());
for (IIdType nextId : theIds) {
if (myDaoConfig.getResourceClientIdStrategy() != DaoConfig.ClientIdStrategyEnum.ANY) {
if (nextId.isIdPartValidLong()) {
retVal.add(new ResourcePersistentId(nextId.getIdPartAsLong()).setAssociatedResourceId(nextId));
continue;
}
}
String key = toForcedIdToPidKey(theRequestPartitionId, nextId.getResourceType(), nextId.getIdPart());
ResourcePersistentId cachedId = myMemoryCacheService.getIfPresent(MemoryCacheService.CacheEnum.FORCED_ID_TO_PID, key);
if (cachedId != null) {
retVal.add(cachedId);
continue;
}
idsToCheck.add(nextId);
}
ListMultimap<String, String> typeToIds = organizeIdsByResourceType(theIds);
if (idsToCheck.size() > 0) {
CriteriaBuilder cb = myEntityManager.getCriteriaBuilder();
CriteriaQuery<ForcedId> criteriaQuery = cb.createQuery(ForcedId.class);
Root<ForcedId> from = criteriaQuery.from(ForcedId.class);
for (Map.Entry<String, Collection<String>> nextEntry : typeToIds.asMap().entrySet()) {
String nextResourceType = nextEntry.getKey();
Collection<String> nextIds = nextEntry.getValue();
if (isBlank(nextResourceType)) {
List<Predicate> predicates = new ArrayList<>(idsToCheck.size());
for (IIdType next : idsToCheck) {
List<Long> views = myForcedIdDao.findByForcedId(nextIds);
views.forEach(t -> retVal.add(new ResourcePersistentId(t)));
List<Predicate> andPredicates = new ArrayList<>(3);
} else {
// String partitionIdStringForKey = RequestPartitionId.stringifyForKey(theRequestPartitionId);
for (Iterator<String> idIterator = nextIds.iterator(); idIterator.hasNext(); ) {
String nextId = idIterator.next();
String key = toForcedIdToPidKey(theRequestPartitionId, nextResourceType, nextId);
ResourcePersistentId nextCachedPid = myMemoryCacheService.getIfPresent(MemoryCacheService.CacheEnum.FORCED_ID_TO_PID, key);
if (nextCachedPid != null) {
idIterator.remove();
retVal.add(nextCachedPid);
}
if (isNotBlank(next.getResourceType())) {
Predicate typeCriteria = cb.equal(from.get("myResourceType").as(String.class), next.getResourceType());
andPredicates.add(typeCriteria);
}
if (nextIds.size() > 0) {
Predicate idCriteria = cb.equal(from.get("myForcedId").as(String.class), next.getIdPart());
andPredicates.add(idCriteria);
Collection<Object[]> views;
if (theRequestPartitionId.isAllPartitions()) {
views = myForcedIdDao.findByTypeAndForcedId(nextResourceType, nextIds);
} else {
if (theRequestPartitionId.isDefaultPartition()) {
views = myForcedIdDao.findByTypeAndForcedIdInPartitionNull(nextResourceType, nextIds);
} else if (theRequestPartitionId.hasDefaultPartitionId()) {
views = myForcedIdDao.findByTypeAndForcedIdInPartitionIdsOrNullPartition(nextResourceType, nextIds, theRequestPartitionId.getPartitionIds());
} else {
views = myForcedIdDao.findByTypeAndForcedIdInPartitionIds(nextResourceType, nextIds, theRequestPartitionId.getPartitionIds());
}
}
for (Object[] nextView : views) {
String forcedId = (String) nextView[0];
Long pid = (Long) nextView[1];
ResourcePersistentId persistentId = new ResourcePersistentId(pid);
retVal.add(persistentId);
String key = toForcedIdToPidKey(theRequestPartitionId, nextResourceType, forcedId);
myMemoryCacheService.put(MemoryCacheService.CacheEnum.FORCED_ID_TO_PID, key, persistentId);
}
if (theRequestPartitionId.isDefaultPartition()) {
Predicate partitionIdCriteria = cb.isNull(from.get("myPartitionIdValue").as(Integer.class));
andPredicates.add(partitionIdCriteria);
} else if (!theRequestPartitionId.isAllPartitions()) {
Predicate partitionIdCriteria = from.get("myPartitionIdValue").as(Integer.class).in(theRequestPartitionId.getPartitionIds());
andPredicates.add(partitionIdCriteria);
}
predicates.add(cb.and(andPredicates.toArray(EMPTY_PREDICATE_ARRAY)));
}
criteriaQuery.where(cb.or(predicates.toArray(EMPTY_PREDICATE_ARRAY)));
TypedQuery<ForcedId> query = myEntityManager.createQuery(criteriaQuery);
List<ForcedId> results = query.getResultList();
for (ForcedId nextId : results) {
ResourcePersistentId persistentId = new ResourcePersistentId(nextId.getResourceId());
populateAssociatedResourceId(nextId.getResourceType(), nextId.getForcedId(), persistentId);
retVal.add(persistentId);
String key = toForcedIdToPidKey(theRequestPartitionId, nextId.getResourceType(), nextId.getForcedId());
myMemoryCacheService.putAfterCommit(MemoryCacheService.CacheEnum.FORCED_ID_TO_PID, key, persistentId);
}
}
return retVal;
}
private void populateAssociatedResourceId(String nextResourceType, String forcedId, ResourcePersistentId persistentId) {
IIdType resourceId = myFhirCtx.getVersion().newIdType();
resourceId.setValue(nextResourceType + "/" + forcedId);
persistentId.setAssociatedResourceId(resourceId);
}
/**
* Given a persistent ID, returns the associated resource ID
*/
@ -501,6 +524,10 @@ public class IdHelperService {
*/
public void addResolvedPidToForcedId(ResourcePersistentId theResourcePersistentId, @Nonnull RequestPartitionId theRequestPartitionId, String theResourceType, @Nullable String theForcedId) {
if (theForcedId != null) {
if (theResourcePersistentId.getAssociatedResourceId() == null) {
populateAssociatedResourceId(theResourceType, theForcedId, theResourcePersistentId);
}
myMemoryCacheService.putAfterCommit(MemoryCacheService.CacheEnum.PID_TO_FORCED_ID, theResourcePersistentId.getIdAsLong(), Optional.of(theForcedId));
String key = toForcedIdToPidKey(theRequestPartitionId, theResourceType, theForcedId);
myMemoryCacheService.putAfterCommit(MemoryCacheService.CacheEnum.FORCED_ID_TO_PID, key, theResourcePersistentId);

View File

@ -111,8 +111,8 @@ public class SearchParamWithInlineReferencesExtractor {
mySearchParamRegistry = theSearchParamRegistry;
}
public void populateFromResource(ResourceIndexedSearchParams theParams, TransactionDetails theTransactionDetails, ResourceTable theEntity, IBaseResource theResource, ResourceIndexedSearchParams theExistingParams, RequestDetails theRequest) {
extractInlineReferences(theResource, theRequest);
public void populateFromResource(ResourceIndexedSearchParams theParams, TransactionDetails theTransactionDetails, ResourceTable theEntity, IBaseResource theResource, ResourceIndexedSearchParams theExistingParams, RequestDetails theRequest, boolean theFailOnInvalidReference) {
extractInlineReferences(theResource, theTransactionDetails, theRequest);
RequestPartitionId partitionId;
if (myPartitionSettings.isPartitioningEnabled()) {
@ -121,7 +121,7 @@ public class SearchParamWithInlineReferencesExtractor {
partitionId = RequestPartitionId.allPartitions();
}
mySearchParamExtractorService.extractFromResource(partitionId, theRequest, theParams, theEntity, theResource, theTransactionDetails, true);
mySearchParamExtractorService.extractFromResource(partitionId, theRequest, theParams, theEntity, theResource, theTransactionDetails, theFailOnInvalidReference);
Set<Map.Entry<String, RuntimeSearchParam>> activeSearchParams = mySearchParamRegistry.getActiveSearchParams(theEntity.getResourceType()).entrySet();
if (myDaoConfig.getIndexMissingFields() == DaoConfig.IndexEnabledEnum.ENABLED) {
@ -245,7 +245,7 @@ public class SearchParamWithInlineReferencesExtractor {
* Handle references within the resource that are match URLs, for example references like "Patient?identifier=foo". These match URLs are resolved and replaced with the ID of the
* matching resource.
*/
public void extractInlineReferences(IBaseResource theResource, RequestDetails theRequest) {
public void extractInlineReferences(IBaseResource theResource, TransactionDetails theTransactionDetails, RequestDetails theRequest) {
if (!myDaoConfig.isAllowInlineMatchUrlReferences()) {
return;
}
@ -277,7 +277,7 @@ public class SearchParamWithInlineReferencesExtractor {
}
Class<? extends IBaseResource> matchResourceType = matchResourceDef.getImplementingClass();
//Attempt to find the target reference before creating a placeholder
Set<ResourcePersistentId> matches = myMatchResourceUrlService.processMatchUrl(nextIdText, matchResourceType, theRequest);
Set<ResourcePersistentId> matches = myMatchResourceUrlService.processMatchUrl(nextIdText, matchResourceType, theTransactionDetails, theRequest);
ResourcePersistentId match;
if (matches.isEmpty()) {

View File

@ -48,8 +48,8 @@ public class FhirResourceDaoSubscriptionR4 extends BaseHapiFhirResourceDao<Subsc
}
@Override
public Long getSubscriptionTablePidForSubscriptionResource(IIdType theId, RequestDetails theRequest) {
ResourceTable entity = readEntityLatestVersion(theId, theRequest);
public Long getSubscriptionTablePidForSubscriptionResource(IIdType theId, RequestDetails theRequest, TransactionDetails theTransactionDetails) {
ResourceTable entity = readEntityLatestVersion(theId, theRequest, theTransactionDetails);
SubscriptionTable table = mySubscriptionTableDao.findOneByResourcePid(entity.getId());
if (table == null) {
return null;
@ -72,7 +72,7 @@ public class FhirResourceDaoSubscriptionR4 extends BaseHapiFhirResourceDao<Subsc
ResourceTable retVal = super.updateEntity(theRequest, theResource, theEntity, theDeletedTimestampOrNull, thePerformIndexing, theUpdateVersion, theTransactionDetails, theForceUpdate, theCreateNewHistoryEntry);
if (theDeletedTimestampOrNull != null) {
Long subscriptionId = getSubscriptionTablePidForSubscriptionResource(theEntity.getIdDt(), theRequest);
Long subscriptionId = getSubscriptionTablePidForSubscriptionResource(theEntity.getIdDt(), theRequest, theTransactionDetails);
if (subscriptionId != null) {
mySubscriptionTableDao.deleteAllForSubscription(retVal);
}

View File

@ -48,8 +48,8 @@ public class FhirResourceDaoSubscriptionR5 extends BaseHapiFhirResourceDao<Subsc
}
@Override
public Long getSubscriptionTablePidForSubscriptionResource(IIdType theId, RequestDetails theRequest) {
ResourceTable entity = readEntityLatestVersion(theId, theRequest);
public Long getSubscriptionTablePidForSubscriptionResource(IIdType theId, RequestDetails theRequest, TransactionDetails theTransactionDetails) {
ResourceTable entity = readEntityLatestVersion(theId, theRequest, theTransactionDetails);
SubscriptionTable table = mySubscriptionTableDao.findOneByResourcePid(entity.getId());
if (table == null) {
return null;
@ -72,7 +72,7 @@ public class FhirResourceDaoSubscriptionR5 extends BaseHapiFhirResourceDao<Subsc
ResourceTable retVal = super.updateEntity(theRequest, theResource, theEntity, theDeletedTimestampOrNull, thePerformIndexing, theUpdateVersion, theTransactionDetails, theForceUpdate, theCreateNewHistoryEntry);
if (theDeletedTimestampOrNull != null) {
Long subscriptionId = getSubscriptionTablePidForSubscriptionResource(theEntity.getIdDt(), theRequest);
Long subscriptionId = getSubscriptionTablePidForSubscriptionResource(theEntity.getIdDt(), theRequest, theTransactionDetails);
if (subscriptionId != null) {
mySubscriptionTableDao.deleteAllForSubscription(retVal);
}

View File

@ -54,7 +54,8 @@ public class SearchParamPresenceSvcImpl implements ISearchParamPresenceSvc {
}
@Override
public AddRemoveCount updatePresence(ResourceTable theResource, Map<String, Boolean> theParamNameToPresence) {
public AddRemoveCount
updatePresence(ResourceTable theResource, Map<String, Boolean> theParamNameToPresence) {
AddRemoveCount retVal = new AddRemoveCount();
if (myDaoConfig.getIndexMissingFields() == DaoConfig.IndexEnabledEnum.DISABLED) {
return retVal;

View File

@ -378,6 +378,9 @@ public class CircularQueueCaptureQueriesListener extends BaseCaptureQueriesListe
b.append(new InstantType(new Date(theQuery.getQueryTimestamp())).getValueAsString());
b.append(" took ").append(StopWatch.formatMillis(theQuery.getElapsedTime()));
b.append(" on Thread: ").append(theQuery.getThreadName());
if (theQuery.getSize() > 1) {
b.append("\nExecution Count: ").append(theQuery.getSize()).append(" (parameters shown are for first execution)");
}
b.append("\nSQL:\n").append(formattedSql);
if (theQuery.getStackTrace() != null) {
b.append("\nStack:\n ");

View File

@ -26,7 +26,6 @@ import org.hibernate.engine.jdbc.internal.BasicFormatterImpl;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;
import static org.apache.commons.lang3.StringUtils.trim;
@ -93,11 +92,7 @@ public class SqlQuery {
}
}
if (mySize > 1) {
retVal += "\nsize: " + mySize + "\n";
}
return trim(retVal);
}
public StackTraceElement[] getStackTrace() {

View File

@ -11,14 +11,17 @@ import ca.uhn.fhir.jpa.api.model.ExpungeOptions;
import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc;
import ca.uhn.fhir.jpa.bulk.export.api.IBulkDataExportSvc;
import ca.uhn.fhir.jpa.config.BaseConfig;
import ca.uhn.fhir.jpa.dao.data.IResourceHistoryTableDao;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamDateDao;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamTokenDao;
import ca.uhn.fhir.jpa.dao.data.IResourceLinkDao;
import ca.uhn.fhir.jpa.dao.data.IResourceTableDao;
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
import ca.uhn.fhir.jpa.entity.TermConcept;
import ca.uhn.fhir.jpa.entity.TermValueSet;
import ca.uhn.fhir.jpa.entity.TermValueSetConcept;
import ca.uhn.fhir.jpa.entity.TermValueSetConceptDesignation;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.partition.IPartitionLookupSvc;
import ca.uhn.fhir.jpa.provider.SystemProviderDstu2Test;
@ -162,6 +165,10 @@ public abstract class BaseJpaTest extends BaseTest {
private IValidationSupport myJpaPersistedValidationSupport;
@Autowired
private FhirInstanceValidator myFhirInstanceValidator;
@Autowired
private IResourceTableDao myResourceTableDao;
@Autowired
private IResourceHistoryTableDao myResourceHistoryTableDao;
@AfterEach
public void afterPerformCleanup() {
@ -242,6 +249,22 @@ public abstract class BaseJpaTest extends BaseTest {
});
}
protected int logAllResources() {
return runInTransaction(() -> {
List<ResourceTable> resources = myResourceTableDao.findAll();
ourLog.info("Resources:\n * {}", resources.stream().map(t -> t.toString()).collect(Collectors.joining("\n * ")));
return resources.size();
});
}
protected int logAllResourceVersions() {
return runInTransaction(() -> {
List<ResourceTable> resources = myResourceTableDao.findAll();
ourLog.info("Resources Versions:\n * {}", resources.stream().map(t -> t.toString()).collect(Collectors.joining("\n * ")));
return resources.size();
});
}
protected void logAllDateIndexes() {
runInTransaction(() -> {
ourLog.info("Date indexes:\n * {}", myResourceIndexedSearchParamDateDao.findAll().stream().map(t -> t.toString()).collect(Collectors.joining("\n * ")));
@ -630,7 +653,7 @@ public abstract class BaseJpaTest extends BaseTest {
throw new Error(theE);
}
}
if (sw.getMillis() >= 16000) {
if (sw.getMillis() >= 16000 || theList.size() > theTarget) {
String describeResults = theList
.stream()
.map(t -> {

View File

@ -5,9 +5,13 @@ import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.interceptor.executor.InterceptorService;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
import ca.uhn.fhir.jpa.dao.r4.TransactionProcessorVersionAdapterR4;
import ca.uhn.fhir.jpa.dao.tx.HapiTransactionService;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc;
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
import ca.uhn.fhir.jpa.searchparam.matcher.InMemoryResourceMatcher;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import org.hibernate.Session;
@ -56,6 +60,14 @@ public class TransactionProcessorTest {
private ModelConfig myModelConfig;
@MockBean
private InMemoryResourceMatcher myInMemoryResourceMatcher;
@MockBean
private IdHelperService myIdHelperService;
@MockBean
private PartitionSettings myPartitionSettings;
@MockBean
private MatchUrlService myMatchUrlService;
@MockBean
private IRequestPartitionHelperSvc myRequestPartitionHelperSvc;
@MockBean(answer = Answers.RETURNS_DEEP_STUBS)
private SessionImpl mySession;

View File

@ -429,19 +429,11 @@ public class FhirSystemDaoDstu2Test extends BaseJpaDstu2SystemTest {
p.addIdentifier().setSystem("urn:system").setValue(methodName);
request.addEntry().setResource(p).getRequest().setMethod(HTTPVerbEnum.POST).setIfNoneExist("Patient?identifier=urn%3Asystem%7C" + methodName);
try {
myCaptureQueriesListener.clear();
mySystemDao.transaction(mySrd, request);
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
myCaptureQueriesListener.clear();
mySystemDao.transaction(mySrd, request);
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
runInTransaction(()->{
ourLog.info("Tokens:\n * {}", myResourceIndexedSearchParamTokenDao.findAll().stream().map(t->t.toString()).collect(Collectors.joining("\n * ")));
});
fail();
} catch (InvalidRequestException e) {
assertEquals(e.getMessage(), "Unable to process Transaction - Request would cause multiple resources to match URL: \"Patient?identifier=urn%3Asystem%7CtestTransactionCreateWithDuplicateMatchUrl01\". Does transaction request contain duplicates?");
}
assertEquals(1, logAllResources());
}
@Test

View File

@ -1008,29 +1008,6 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest {
assertThat(oo.getIssue().get(0).getDiagnostics(), containsString("Unknown search parameter"));
}
@Test
public void testTransactionCreateWithDuplicateMatchUrl01() {
String methodName = "testTransactionCreateWithDuplicateMatchUrl01";
Bundle request = new Bundle();
Patient p;
p = new Patient();
p.addIdentifier().setSystem("urn:system").setValue(methodName);
request.addEntry().setResource(p).getRequest().setMethod(HTTPVerb.POST).setIfNoneExist("Patient?identifier=urn%3Asystem%7C" + methodName);
p = new Patient();
p.addIdentifier().setSystem("urn:system").setValue(methodName);
request.addEntry().setResource(p).getRequest().setMethod(HTTPVerb.POST).setIfNoneExist("Patient?identifier=urn%3Asystem%7C" + methodName);
try {
mySystemDao.transaction(mySrd, request);
fail();
} catch (InvalidRequestException e) {
assertEquals(e.getMessage(),
"Unable to process Transaction - Request would cause multiple resources to match URL: \"Patient?identifier=urn%3Asystem%7CtestTransactionCreateWithDuplicateMatchUrl01\". Does transaction request contain duplicates?");
}
}
@Test
public void testTransactionCreateWithDuplicateMatchUrl02() {
String methodName = "testTransactionCreateWithDuplicateMatchUrl02";
@ -1127,27 +1104,6 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest {
}
}
@Test
public void testTransactionCreateWithLinks() {
Bundle request = new Bundle();
request.setType(BundleType.TRANSACTION);
Observation o = new Observation();
o.setId("A");
o.setStatus(ObservationStatus.AMENDED);
request.addEntry()
.setResource(o)
.getRequest().setUrl("A").setMethod(HTTPVerb.PUT);
try {
mySystemDao.transaction(mySrd, request);
fail();
} catch (InvalidRequestException e) {
assertEquals("Invalid match URL[A] - URL has no search parameters", e.getMessage());
}
}
@Test
public void testTransactionCreateWithPutUsingAbsoluteUrl() {
String methodName = "testTransactionCreateWithPutUsingAbsoluteUrl";
@ -1699,69 +1655,6 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest {
}
@Test
public void testTransactionDoubleConditionalCreateOnlyCreatesOne() {
Bundle inputBundle = new Bundle();
inputBundle.setType(Bundle.BundleType.TRANSACTION);
Encounter enc1 = new Encounter();
enc1.addIdentifier().setSystem("urn:foo").setValue("12345");
inputBundle
.addEntry()
.setResource(enc1)
.getRequest()
.setMethod(HTTPVerb.POST)
.setIfNoneExist("Encounter?identifier=urn:foo|12345");
Encounter enc2 = new Encounter();
enc2.addIdentifier().setSystem("urn:foo").setValue("12345");
inputBundle
.addEntry()
.setResource(enc2)
.getRequest()
.setMethod(HTTPVerb.POST)
.setIfNoneExist("Encounter?identifier=urn:foo|12345");
try {
mySystemDao.transaction(mySrd, inputBundle);
fail();
} catch (InvalidRequestException e) {
assertEquals("Unable to process Transaction - Request would cause multiple resources to match URL: \"Encounter?identifier=urn:foo|12345\". Does transaction request contain duplicates?",
e.getMessage());
}
}
@Test
public void testTransactionDoubleConditionalUpdateOnlyCreatesOne() {
Bundle inputBundle = new Bundle();
inputBundle.setType(Bundle.BundleType.TRANSACTION);
Encounter enc1 = new Encounter();
enc1.addIdentifier().setSystem("urn:foo").setValue("12345");
inputBundle
.addEntry()
.setResource(enc1)
.getRequest()
.setMethod(HTTPVerb.PUT)
.setUrl("Encounter?identifier=urn:foo|12345");
Encounter enc2 = new Encounter();
enc2.addIdentifier().setSystem("urn:foo").setValue("12345");
inputBundle
.addEntry()
.setResource(enc2)
.getRequest()
.setMethod(HTTPVerb.PUT)
.setUrl("Encounter?identifier=urn:foo|12345");
try {
mySystemDao.transaction(mySrd, inputBundle);
fail();
} catch (InvalidRequestException e) {
assertEquals("Unable to process Transaction - Request would cause multiple resources to match URL: \"Encounter?identifier=urn:foo|12345\". Does transaction request contain duplicates?",
e.getMessage());
}
}
@Test
public void testTransactionFailsWithDuplicateIds() {
Bundle request = new Bundle();

View File

@ -68,6 +68,8 @@ public abstract class BasePartitioningR4Test extends BaseJpaR4SystemTest {
myDaoConfig.setIndexMissingFields(new DaoConfig().getIndexMissingFields());
myDaoConfig.setAutoCreatePlaceholderReferenceTargets(new DaoConfig().isAutoCreatePlaceholderReferenceTargets());
myDaoConfig.setMassIngestionMode(new DaoConfig().isMassIngestionMode());
myDaoConfig.setMatchUrlCacheEnabled(new DaoConfig().getMatchUrlCache());
}
@BeforeEach

View File

@ -231,7 +231,7 @@ public class FhirResourceDaoR4CreateTest extends BaseJpaR4Test {
p = myPatientDao.read(new IdType("Patient/" + firstClientAssignedId));
assertEquals(true, p.getActive());
// Not create a client assigned numeric ID
// Now create a client assigned numeric ID
p = new Patient();
p.setId("Patient/" + newId);
p.addName().setFamily("FAM");

View File

@ -1,6 +1,5 @@
package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.api.model.HistoryCountModeEnum;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
@ -8,7 +7,6 @@ import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.partition.SystemRequestDetails;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.util.SqlQuery;
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
import ca.uhn.fhir.rest.api.SortSpec;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.ReferenceParam;
@ -28,6 +26,7 @@ import org.hl7.fhir.r4.model.Narrative;
import org.hl7.fhir.r4.model.Observation;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Practitioner;
import org.hl7.fhir.r4.model.Quantity;
import org.hl7.fhir.r4.model.Reference;
import org.hl7.fhir.r4.model.ServiceRequest;
import org.hl7.fhir.r4.model.StringType;
@ -36,9 +35,8 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.util.Iterator;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
import java.util.stream.Collectors;
@ -65,6 +63,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
myModelConfig.setAutoVersionReferenceAtPaths(new ModelConfig().getAutoVersionReferenceAtPaths());
myModelConfig.setRespectVersionsForSearchIncludes(new ModelConfig().isRespectVersionsForSearchIncludes());
myFhirCtx.getParserOptions().setStripVersionsFromReferences(true);
myDaoConfig.setTagStorageMode(new DaoConfig().getTagStorageMode());
}
@BeforeEach
@ -553,7 +552,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
myCaptureQueriesListener.clear();
assertEquals(1, myObservationDao.search(map).size().intValue());
// Resolve forced ID, Perform search, load result
// (not resolve forced ID), Perform search, load result
assertEquals(2, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
assertNoPartitionSelectors();
assertEquals(0, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
@ -567,7 +566,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
myCaptureQueriesListener.clear();
assertEquals(1, myObservationDao.search(map).size().intValue());
myCaptureQueriesListener.logAllQueriesForCurrentThread();
// Resolve forced ID, Perform search, load result (this time we reuse the cached forced-id resolution)
// (not resolve forced ID), Perform search, load result (this time we reuse the cached forced-id resolution)
assertEquals(2, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
assertEquals(0, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
assertEquals(0, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
@ -595,7 +594,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
myCaptureQueriesListener.clear();
assertEquals(1, myObservationDao.search(map).size().intValue());
myCaptureQueriesListener.logAllQueriesForCurrentThread();
// Resolve forced ID, Perform search, load result
// (not Resolve forced ID), Perform search, load result
assertEquals(2, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
assertEquals(0, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
assertEquals(0, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
@ -691,11 +690,235 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
assertEquals(0, myCaptureQueriesListener.getDeleteQueriesForCurrentThread().size());
}
@Test
public void testTransactionWithTwoCreates() {
BundleBuilder bb = new BundleBuilder(myFhirCtx);
Patient pt = new Patient();
pt.setId(IdType.newRandomUuid());
pt.addIdentifier().setSystem("http://foo").setValue("123");
bb.addTransactionCreateEntry(pt);
Patient pt2 = new Patient();
pt2.setId(IdType.newRandomUuid());
pt2.addIdentifier().setSystem("http://foo").setValue("456");
bb.addTransactionCreateEntry(pt2);
runInTransaction(() -> {
assertEquals(0, myResourceTableDao.count());
});
ourLog.info("About to start transaction");
myCaptureQueriesListener.clear();
Bundle outcome = mySystemDao.transaction(mySrd, (Bundle) bb.getBundle());
ourLog.info("Resp: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
myCaptureQueriesListener.logSelectQueries();
assertEquals(0, myCaptureQueriesListener.countSelectQueries());
myCaptureQueriesListener.logInsertQueries();
assertEquals(3, myCaptureQueriesListener.countInsertQueries());
myCaptureQueriesListener.logUpdateQueries();
assertEquals(0, myCaptureQueriesListener.countUpdateQueries());
assertEquals(0, myCaptureQueriesListener.countDeleteQueries());
runInTransaction(() -> {
assertEquals(2, myResourceTableDao.count());
});
}
@Test
public void testTransactionWithMultipleUpdates() {
AtomicInteger counter = new AtomicInteger(0);
Supplier<Bundle> input = () -> {
BundleBuilder bb = new BundleBuilder(myFhirCtx);
Patient pt = new Patient();
pt.setId("Patient/A");
pt.addIdentifier().setSystem("http://foo").setValue("123");
bb.addTransactionUpdateEntry(pt);
Observation obsA = new Observation();
obsA.setId("Observation/A");
obsA.getCode().addCoding().setSystem("http://foo").setCode("bar");
obsA.setValue(new Quantity(null, 1, "http://unitsofmeasure.org", "kg", "kg"));
obsA.setEffective(new DateTimeType(new Date()));
obsA.addNote().setText("Foo " + counter.incrementAndGet()); // changes every time
bb.addTransactionUpdateEntry(obsA);
Observation obsB = new Observation();
obsB.setId("Observation/B");
obsB.getCode().addCoding().setSystem("http://foo").setCode("bar");
obsB.setValue(new Quantity(null, 1, "http://unitsofmeasure.org", "kg", "kg"));
obsB.setEffective(new DateTimeType(new Date()));
obsB.addNote().setText("Foo " + counter.incrementAndGet()); // changes every time
bb.addTransactionUpdateEntry(obsB);
return (Bundle) bb.getBundle();
};
ourLog.info("About to start transaction");
myCaptureQueriesListener.clear();
Bundle outcome = mySystemDao.transaction(mySrd, input.get());
ourLog.info("Resp: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
myCaptureQueriesListener.logSelectQueries();
assertEquals(1, myCaptureQueriesListener.countSelectQueries());
myCaptureQueriesListener.logInsertQueries();
assertEquals(6, myCaptureQueriesListener.countInsertQueries());
myCaptureQueriesListener.logUpdateQueries();
assertEquals(0, myCaptureQueriesListener.countUpdateQueries());
assertEquals(0, myCaptureQueriesListener.countDeleteQueries());
/*
* Run a second time
*/
myCaptureQueriesListener.clear();
outcome = mySystemDao.transaction(mySrd, input.get());
ourLog.info("Resp: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
myCaptureQueriesListener.logSelectQueries();
assertEquals(10, myCaptureQueriesListener.countSelectQueries());
myCaptureQueriesListener.logInsertQueries();
assertEquals(1, myCaptureQueriesListener.countInsertQueries());
myCaptureQueriesListener.logUpdateQueries();
assertEquals(2, myCaptureQueriesListener.countUpdateQueries());
assertEquals(0, myCaptureQueriesListener.countDeleteQueries());
/*
* Third time with mass ingestion mode enabled
*/
myDaoConfig.setMassIngestionMode(true);
myCaptureQueriesListener.clear();
outcome = mySystemDao.transaction(mySrd, input.get());
ourLog.info("Resp: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
myCaptureQueriesListener.logSelectQueries();
assertEquals(7, myCaptureQueriesListener.countSelectQueries());
myCaptureQueriesListener.logInsertQueries();
assertEquals(1, myCaptureQueriesListener.countInsertQueries());
myCaptureQueriesListener.logUpdateQueries();
assertEquals(2, myCaptureQueriesListener.countUpdateQueries());
assertEquals(0, myCaptureQueriesListener.countDeleteQueries());
}
@Test
public void testTransactionWithMultipleConditionalUpdates() {
AtomicInteger counter = new AtomicInteger(0);
Supplier<Bundle> input = () -> {
BundleBuilder bb = new BundleBuilder(myFhirCtx);
Patient pt = new Patient();
pt.setId(IdType.newRandomUuid());
pt.addIdentifier().setSystem("http://foo").setValue("123");
bb.addTransactionCreateEntry(pt).conditional("Patient?identifier=http://foo|123");
Observation obsA = new Observation();
obsA.getSubject().setReference(pt.getId());
obsA.getCode().addCoding().setSystem("http://foo").setCode("bar1");
obsA.setValue(new Quantity(null, 1, "http://unitsofmeasure.org", "kg", "kg"));
obsA.setEffective(new DateTimeType(new Date()));
obsA.addNote().setText("Foo " + counter.incrementAndGet()); // changes every time
bb.addTransactionUpdateEntry(obsA).conditional("Observation?code=http://foo|bar1");
Observation obsB = new Observation();
obsB.getSubject().setReference(pt.getId());
obsB.getCode().addCoding().setSystem("http://foo").setCode("bar2");
obsB.setValue(new Quantity(null, 1, "http://unitsofmeasure.org", "kg", "kg"));
obsB.setEffective(new DateTimeType(new Date()));
obsB.addNote().setText("Foo " + counter.incrementAndGet()); // changes every time
bb.addTransactionUpdateEntry(obsB).conditional("Observation?code=http://foo|bar2");
Observation obsC = new Observation();
obsC.getSubject().setReference(pt.getId());
obsC.getCode().addCoding().setSystem("http://foo").setCode("bar3");
obsC.setValue(new Quantity(null, 1, "http://unitsofmeasure.org", "kg", "kg"));
obsC.setEffective(new DateTimeType(new Date()));
obsC.addNote().setText("Foo " + counter.incrementAndGet()); // changes every time
bb.addTransactionUpdateEntry(obsC).conditional("Observation?code=bar3");
Observation obsD = new Observation();
obsD.getSubject().setReference(pt.getId());
obsD.getCode().addCoding().setSystem("http://foo").setCode("bar4");
obsD.setValue(new Quantity(null, 1, "http://unitsofmeasure.org", "kg", "kg"));
obsD.setEffective(new DateTimeType(new Date()));
obsD.addNote().setText("Foo " + counter.incrementAndGet()); // changes every time
bb.addTransactionUpdateEntry(obsD).conditional("Observation?code=bar4");
return (Bundle) bb.getBundle();
};
ourLog.info("About to start transaction");
myCaptureQueriesListener.clear();
Bundle outcome = mySystemDao.transaction(mySrd, input.get());
ourLog.info("Resp: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
myCaptureQueriesListener.logSelectQueries();
assertEquals(1, myCaptureQueriesListener.countSelectQueries());
myCaptureQueriesListener.logInsertQueries();
assertEquals(6, myCaptureQueriesListener.countInsertQueries());
myCaptureQueriesListener.logUpdateQueries();
assertEquals(1, myCaptureQueriesListener.countUpdateQueries());
assertEquals(0, myCaptureQueriesListener.countDeleteQueries());
/*
* Run a second time
*/
myCaptureQueriesListener.clear();
outcome = mySystemDao.transaction(mySrd, input.get());
ourLog.info("Resp: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
myCaptureQueriesListener.logSelectQueries();
assertEquals(11, myCaptureQueriesListener.countSelectQueries());
myCaptureQueriesListener.logInsertQueries();
assertEquals(1, myCaptureQueriesListener.countInsertQueries());
myCaptureQueriesListener.logUpdateQueries();
assertEquals(2, myCaptureQueriesListener.countUpdateQueries());
assertEquals(0, myCaptureQueriesListener.countDeleteQueries());
/*
* Third time with mass ingestion mode enabled
*/
myDaoConfig.setMassIngestionMode(true);
myDaoConfig.setMatchUrlCache(true);
myCaptureQueriesListener.clear();
outcome = mySystemDao.transaction(mySrd, input.get());
ourLog.info("Resp: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
myCaptureQueriesListener.logSelectQueries();
assertEquals(6, myCaptureQueriesListener.countSelectQueries());
myCaptureQueriesListener.logInsertQueries();
assertEquals(1, myCaptureQueriesListener.countInsertQueries());
myCaptureQueriesListener.logUpdateQueries();
assertEquals(2, myCaptureQueriesListener.countUpdateQueries());
assertEquals(0, myCaptureQueriesListener.countDeleteQueries());
/*
* Fourth time with mass ingestion mode enabled
*/
myCaptureQueriesListener.clear();
outcome = mySystemDao.transaction(mySrd, input.get());
ourLog.info("Resp: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
myCaptureQueriesListener.logSelectQueries();
assertEquals(5, myCaptureQueriesListener.countSelectQueries());
myCaptureQueriesListener.logInsertQueries();
assertEquals(1, myCaptureQueriesListener.countInsertQueries());
myCaptureQueriesListener.logUpdateQueries();
assertEquals(2, myCaptureQueriesListener.countUpdateQueries());
assertEquals(0, myCaptureQueriesListener.countDeleteQueries());
}
@Test
public void testTransactionWithConditionalCreate_MatchUrlCacheEnabled() {
myDaoConfig.setMatchUrlCache(true);
Supplier<Bundle> bundleCreator = ()-> {
Supplier<Bundle> bundleCreator = () -> {
BundleBuilder bb = new BundleBuilder(myFhirCtx);
Patient pt = new Patient();
@ -719,7 +942,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
assertEquals(5, myCaptureQueriesListener.countInsertQueries());
assertEquals(0, myCaptureQueriesListener.countDeleteQueries());
runInTransaction(()->{
runInTransaction(() -> {
List<String> types = myResourceTableDao.findAll().stream().map(t -> t.getResourceType()).collect(Collectors.toList());
assertThat(types, containsInAnyOrder("Patient", "Observation"));
});
@ -733,7 +956,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
assertEquals(3, myCaptureQueriesListener.countInsertQueries());
assertEquals(0, myCaptureQueriesListener.countDeleteQueries());
runInTransaction(()->{
runInTransaction(() -> {
List<String> types = myResourceTableDao.findAll().stream().map(t -> t.getResourceType()).collect(Collectors.toList());
assertThat(types, containsInAnyOrder("Patient", "Observation", "Observation"));
});
@ -746,7 +969,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
assertEquals(3, myCaptureQueriesListener.countInsertQueries());
assertEquals(0, myCaptureQueriesListener.countDeleteQueries());
runInTransaction(()->{
runInTransaction(() -> {
List<String> types = myResourceTableDao.findAll().stream().map(t -> t.getResourceType()).collect(Collectors.toList());
assertThat(types, containsInAnyOrder("Patient", "Observation", "Observation", "Observation"));
});
@ -756,7 +979,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
@Test
public void testTransactionWithConditionalCreate_MatchUrlCacheNotEnabled() {
Supplier<Bundle> bundleCreator = ()-> {
Supplier<Bundle> bundleCreator = () -> {
BundleBuilder bb = new BundleBuilder(myFhirCtx);
Patient pt = new Patient();
@ -781,7 +1004,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
assertEquals(5, myCaptureQueriesListener.countInsertQueries());
assertEquals(0, myCaptureQueriesListener.countDeleteQueries());
runInTransaction(()->{
runInTransaction(() -> {
List<String> types = myResourceTableDao.findAll().stream().map(t -> t.getResourceType()).collect(Collectors.toList());
assertThat(types, containsInAnyOrder("Patient", "Observation"));
});
@ -800,7 +1023,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
assertThat(matchUrlQuery, containsString("t0.HASH_SYS_AND_VALUE = '-4132452001562191669'"));
assertThat(matchUrlQuery, containsString("limit '2'"));
runInTransaction(()->{
runInTransaction(() -> {
List<String> types = myResourceTableDao.findAll().stream().map(t -> t.getResourceType()).collect(Collectors.toList());
assertThat(types, containsInAnyOrder("Patient", "Observation", "Observation"));
});
@ -813,7 +1036,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
assertEquals(3, myCaptureQueriesListener.countInsertQueries());
assertEquals(0, myCaptureQueriesListener.countDeleteQueries());
runInTransaction(()->{
runInTransaction(() -> {
List<String> types = myResourceTableDao.findAll().stream().map(t -> t.getResourceType()).collect(Collectors.toList());
assertThat(types, containsInAnyOrder("Patient", "Observation", "Observation", "Observation"));
});
@ -855,14 +1078,14 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
myCaptureQueriesListener.logInsertQueriesForCurrentThread();
assertEquals(4, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
myCaptureQueriesListener.logUpdateQueriesForCurrentThread();
assertEquals(1, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
assertEquals(0, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread());
// Pass 2
input = new Bundle();
patient = new Patient();
patient = new Patient();
patient.setId("Patient/A");
patient.setActive(true);
input.addEntry()
@ -872,7 +1095,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
.setMethod(Bundle.HTTPVerb.PUT)
.setUrl("Patient/A");
observation = new Observation();
observation = new Observation();
observation.setId(IdType.newRandomUuid());
observation.addReferenceRange().setText("A");
input.addEntry()
@ -883,7 +1106,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
.setUrl("Observation");
myCaptureQueriesListener.clear();
output = mySystemDao.transaction(mySrd, input);
output = mySystemDao.transaction(mySrd, input);
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(output));
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
@ -891,7 +1114,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
myCaptureQueriesListener.logInsertQueriesForCurrentThread();
assertEquals(2, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
myCaptureQueriesListener.logUpdateQueriesForCurrentThread();
assertEquals(1, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
assertEquals(0, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread());
@ -1013,7 +1236,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertEquals(2, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
assertEquals(3, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
assertEquals(2, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
assertEquals(0, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread());
// Do the same a second time - Deletes are enabled so we expect to have to resolve the
@ -1051,7 +1274,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertEquals(2, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
assertEquals(3, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
assertEquals(2, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
assertEquals(0, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread());
}
@ -1102,7 +1325,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
myCaptureQueriesListener.logUpdateQueriesForCurrentThread();
assertEquals(2, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
assertEquals(3, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
assertEquals(2, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
assertEquals(0, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread());
// Do the same a second time - Deletes are enabled so we expect to have to resolve the
@ -1140,7 +1363,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertEquals(2, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
assertEquals(3, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
assertEquals(2, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
assertEquals(0, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread());
}
@ -1193,9 +1416,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertEquals(2, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
assertEquals(3, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
// See notes in testTransactionWithMultiplePreExistingReferences_Numeric_DeletesDisabled below
myCaptureQueriesListener.logUpdateQueriesForCurrentThread();
assertEquals(2, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
assertEquals(0, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread());
// Do the same a second time - Deletes are enabled so we expect to have to resolve the
@ -1233,7 +1454,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
assertEquals(0, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
assertEquals(3, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
myCaptureQueriesListener.logUpdateQueriesForCurrentThread();
assertEquals(1, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
assertEquals(0, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread());
}
@ -1283,10 +1504,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertEquals(2, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
assertEquals(3, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
// TODO: We have 2 updates here that are caused by Hibernate deciding to flush its action queue half way through
// the transaction because a read is about to take place. I think these are unnecessary but I don't see a simple
// way of getting rid of them. Hopefully these can be optimized out later
assertEquals(2, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
assertEquals(0, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread());
// Do the same a second time - Deletes are enabled so we expect to have to resolve the
@ -1324,8 +1542,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertEquals(0, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
assertEquals(3, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
// Similar to the note above - No idea why this update is here, it's basically a NO-OP
assertEquals(1, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
assertEquals(0, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread());
}
@ -1398,9 +1615,9 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
// Lookup the two existing IDs to make sure they are legit
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertEquals(4, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
assertEquals(3, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
assertEquals(3, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
assertEquals(2, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
assertEquals(1, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread());
// Do the same a second time
@ -1457,9 +1674,9 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
// Lookup the two existing IDs to make sure they are legit
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertEquals(2, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
assertEquals(1, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
assertEquals(3, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
assertEquals(2, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
assertEquals(1, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread());
}
@ -1491,17 +1708,30 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
assertEquals(3, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
assertEquals(8, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
myCaptureQueriesListener.logUpdateQueriesForCurrentThread();
assertEquals(1, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
assertEquals(0, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread());
// Do the same a second time
input = new Bundle();
for (int i = 0; i < 5; i++) {
Patient patient = new Patient();
patient.getMeta().addProfile("http://example.com/profile");
patient.getMeta().addTag().setSystem("http://example.com/tags").setCode("tag-1");
patient.getMeta().addTag().setSystem("http://example.com/tags").setCode("tag-2");
input.addEntry()
.setResource(patient)
.getRequest()
.setMethod(Bundle.HTTPVerb.POST)
.setUrl("Patient");
}
myCaptureQueriesListener.clear();
mySystemDao.transaction(mySrd, input);
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertEquals(0, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
assertEquals(5, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
assertEquals(1, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
assertEquals(0, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread());
}
@ -1556,24 +1786,63 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
myCaptureQueriesListener.clear();
mySystemDao.transaction(new SystemRequestDetails(), supplier.get());
// myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertEquals(3, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
assertEquals(13, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
assertEquals(3, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
assertEquals(2, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
assertEquals(9, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
assertEquals(1, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread());
myCaptureQueriesListener.clear();
mySystemDao.transaction(new SystemRequestDetails(), supplier.get());
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertEquals(11, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
assertEquals(7, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
assertEquals(2, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
assertEquals(3, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread());
// assertEquals(15, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
// assertEquals(1, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
// assertEquals(3, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
// assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread());
}
@Test
public void testMassIngestionMode_TransactionWithChanges_2() throws IOException {
myDaoConfig.setDeleteEnabled(false);
myDaoConfig.setMatchUrlCache(true);
myDaoConfig.setMassIngestionMode(true);
myFhirCtx.getParserOptions().setStripVersionsFromReferences(false);
myModelConfig.setRespectVersionsForSearchIncludes(true);
myDaoConfig.setTagStorageMode(DaoConfig.TagStorageModeEnum.NON_VERSIONED);
myModelConfig.setAutoVersionReferenceAtPaths(
"ExplanationOfBenefit.patient",
"ExplanationOfBenefit.insurance.coverage"
);
// Pre-cache tag definitions
Patient patient = new Patient();
patient.getMeta().addProfile("http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient");
patient.getMeta().addProfile("http://hl7.org/fhir/us/carin-bb/StructureDefinition/C4BB-Organization");
patient.getMeta().addProfile("http://hl7.org/fhir/us/core/StructureDefinition/us-core-practitioner");
patient.getMeta().addProfile("http://hl7.org/fhir/us/carin-bb/StructureDefinition/C4BB-ExplanationOfBenefit-Professional-NonClinician");
patient.getMeta().addProfile("http://hl7.org/fhir/us/carin-bb/StructureDefinition/C4BB-Coverage");
patient.setActive(true);
myPatientDao.create(patient);
myCaptureQueriesListener.clear();
mySystemDao.transaction(new SystemRequestDetails(), loadResourceFromClasspath(Bundle.class, "r4/transaction-perf-bundle.json"));
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertEquals(2, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
assertEquals(10, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
myCaptureQueriesListener.logUpdateQueriesForCurrentThread();
assertEquals(1, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread());
// Now a copy that has differences in the EOB and Patient resources
myCaptureQueriesListener.clear();
mySystemDao.transaction(new SystemRequestDetails(), loadResourceFromClasspath(Bundle.class, "r4/transaction-perf-bundle-smallchanges.json"));
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertEquals(6, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
assertEquals(1, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
assertEquals(3, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread());
}
}

View File

@ -402,9 +402,11 @@ public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test
SearchParameterMap params = new SearchParameterMap();
params.add("myDoctor", new ReferenceParam("A"));
myCaptureQueriesListener.clear();
IBundleProvider outcome = myPatientDao.search(params);
List<String> ids = toUnqualifiedVersionlessIdValues(outcome);
ourLog.info("IDS: " + ids);
myCaptureQueriesListener.logSelectQueries();
assertThat(ids, Matchers.contains(pid.getValue()));
}

View File

@ -38,6 +38,7 @@ import org.springframework.scheduling.concurrent.ThreadPoolExecutorFactoryBean;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.stream.Collectors;
@ -1196,12 +1197,12 @@ public class FhirResourceDaoR4SearchOptimizedTest extends BaseJpaR4Test {
// Forced ID resolution
resultingQueryNotFormatted = queries.get(0);
assertThat(resultingQueryNotFormatted, containsString("RESOURCE_TYPE='Organization'"));
assertThat(resultingQueryNotFormatted, containsString("FORCED_ID in ('ORG0' , 'ORG1' , 'ORG2' , 'ORG3' , 'ORG4')"));
assertThat(resultingQueryNotFormatted, containsString("forcedid0_.RESOURCE_TYPE='Organization' and forcedid0_.FORCED_ID='ORG1' or forcedid0_.RESOURCE_TYPE='Organization' and forcedid0_.FORCED_ID='ORG2'"));
// The search itself
resultingQueryNotFormatted = queries.get(1);
assertEquals(1, StringUtils.countMatches(resultingQueryNotFormatted, "Patient.managingOrganization"), resultingQueryNotFormatted);
assertThat(resultingQueryNotFormatted, matchesPattern(".*TARGET_RESOURCE_ID IN \\('[0-9]+','[0-9]+','[0-9]+','[0-9]+','[0-9]+'\\).*"));
assertThat(resultingQueryNotFormatted.toUpperCase(Locale.US), matchesPattern(".*TARGET_RESOURCE_ID IN \\('[0-9]+','[0-9]+','[0-9]+','[0-9]+','[0-9]+'\\).*"));
// Ensure that the search actually worked
assertEquals(5, search.size().intValue());

View File

@ -0,0 +1,192 @@
package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Patient;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import javax.annotation.Nonnull;
import java.util.List;
import java.util.stream.Collectors;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.junit.jupiter.api.Assertions.assertEquals;
@SuppressWarnings({"unchecked", "deprecation", "Duplicates"})
public class FhirResourceDaoR4TagsTest extends BaseJpaR4Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4TagsTest.class);
@AfterEach
public final void after() {
myDaoConfig.setTagStorageMode(DaoConfig.DEFAULT_TAG_STORAGE_MODE);
}
@Test
public void testStoreAndRetrieveNonVersionedTags_Read() {
initializeNonVersioned();
// Read
Patient patient;
patient = myPatientDao.read(new IdType("Patient/A"), mySrd);
assertThat(toProfiles(patient).toString(), toProfiles(patient), contains("http://profile2"));
assertThat(toTags(patient).toString(), toTags(patient), containsInAnyOrder("http://tag1|vtag1|dtag1", "http://tag2|vtag2|dtag2"));
}
@Test
public void testStoreAndRetrieveVersionedTags_Read() {
initializeVersioned();
// Read
Patient patient;
patient = myPatientDao.read(new IdType("Patient/A"), mySrd);
assertThat(toProfiles(patient).toString(), toProfiles(patient), contains("http://profile2"));
assertThat(toTags(patient).toString(), toTags(patient), containsInAnyOrder("http://tag1|vtag1|dtag1", "http://tag2|vtag2|dtag2"));
}
@Test
public void testStoreAndRetrieveVersionedTags_VRead() {
initializeVersioned();
Patient patient = myPatientDao.read(new IdType("Patient/A/_history/1"), mySrd);
assertThat(toProfiles(patient).toString(), toProfiles(patient), contains("http://profile1"));
assertThat(toTags(patient).toString(), toTags(patient), contains("http://tag1|vtag1|dtag1"));
patient = myPatientDao.read(new IdType("Patient/A/_history/2"), mySrd);
assertThat(toProfiles(patient).toString(), toProfiles(patient), contains("http://profile2"));
assertThat(toTags(patient).toString(), toTags(patient), containsInAnyOrder("http://tag1|vtag1|dtag1", "http://tag2|vtag2|dtag2"));
}
@Test
public void testStoreAndRetrieveNonVersionedTags_VRead() {
initializeNonVersioned();
Patient patient = myPatientDao.read(new IdType("Patient/A/_history/1"), mySrd);
assertThat(toProfiles(patient).toString(), toProfiles(patient), contains("http://profile2"));
assertThat(toTags(patient).toString(), toTags(patient), containsInAnyOrder("http://tag1|vtag1|dtag1", "http://tag2|vtag2|dtag2"));
patient = myPatientDao.read(new IdType("Patient/A/_history/2"), mySrd);
assertThat(toProfiles(patient).toString(), toProfiles(patient), contains("http://profile2"));
assertThat(toTags(patient).toString(), toTags(patient), containsInAnyOrder("http://tag1|vtag1|dtag1", "http://tag2|vtag2|dtag2"));
}
@Test
public void testStoreAndRetrieveVersionedTags_History() {
initializeVersioned();
IBundleProvider history = myPatientDao.history(null, null, mySrd);
// Version 1
Patient patient = (Patient) history.getResources(0, 999).get(1);
assertThat(toProfiles(patient).toString(), toProfiles(patient), contains("http://profile1"));
assertThat(toTags(patient).toString(), toTags(patient), contains("http://tag1|vtag1|dtag1"));
// Version 2
patient = (Patient) history.getResources(0, 999).get(0);
assertThat(toProfiles(patient).toString(), toProfiles(patient), contains("http://profile2"));
assertThat(toTags(patient).toString(), toTags(patient), containsInAnyOrder("http://tag1|vtag1|dtag1", "http://tag2|vtag2|dtag2"));
}
@Test
public void testStoreAndRetrieveNonVersionedTags_History() {
initializeNonVersioned();
IBundleProvider history = myPatientDao.history(null, null, mySrd);
// Version 1
Patient patient = (Patient) history.getResources(0, 999).get(1);
assertThat(toProfiles(patient).toString(), toProfiles(patient), contains("http://profile2"));
assertThat(toTags(patient).toString(), toTags(patient), containsInAnyOrder("http://tag1|vtag1|dtag1", "http://tag2|vtag2|dtag2"));
// Version 2
patient = (Patient) history.getResources(0, 999).get(0);
assertThat(toProfiles(patient).toString(), toProfiles(patient), contains("http://profile2"));
assertThat(toTags(patient).toString(), toTags(patient), containsInAnyOrder("http://tag1|vtag1|dtag1", "http://tag2|vtag2|dtag2"));
}
@Test
public void testStoreAndRetrieveVersionedTags_Search() {
initializeVersioned();
IBundleProvider search = myPatientDao.search(new SearchParameterMap());
Patient patient = (Patient) search.getResources(0, 999).get(0);
assertThat(toProfiles(patient).toString(), toProfiles(patient), contains("http://profile2"));
assertThat(toTags(patient).toString(), toTags(patient), containsInAnyOrder("http://tag1|vtag1|dtag1", "http://tag2|vtag2|dtag2"));
}
@Test
public void testStoreAndRetrieveNonVersionedTags_Search() {
initializeNonVersioned();
IBundleProvider search = myPatientDao.search(new SearchParameterMap());
Patient patient = (Patient) search.getResources(0, 999).get(0);
assertThat(toProfiles(patient).toString(), toProfiles(patient), contains("http://profile2"));
assertThat(toTags(patient).toString(), toTags(patient), containsInAnyOrder("http://tag1|vtag1|dtag1", "http://tag2|vtag2|dtag2"));
}
private void initializeNonVersioned() {
myDaoConfig.setTagStorageMode(DaoConfig.TagStorageModeEnum.NON_VERSIONED);
Patient patient = new Patient();
patient.setId("Patient/A");
patient.getMeta().addProfile("http://profile1");
patient.getMeta().addTag("http://tag1", "vtag1", "dtag1");
patient.setActive(true);
myPatientDao.update(patient, mySrd);
patient = new Patient();
patient.setId("Patient/A");
patient.getMeta().addProfile("http://profile2");
patient.getMeta().addTag("http://tag2", "vtag2", "dtag2");
patient.setActive(false);
assertEquals("2", myPatientDao.update(patient, mySrd).getId().getVersionIdPart());
}
private void initializeVersioned() {
myDaoConfig.setTagStorageMode(DaoConfig.TagStorageModeEnum.VERSIONED);
Patient patient = new Patient();
patient.setId("Patient/A");
patient.getMeta().addProfile("http://profile1");
patient.getMeta().addTag("http://tag1", "vtag1", "dtag1");
patient.setActive(true);
myPatientDao.update(patient, mySrd);
patient = new Patient();
patient.setId("Patient/A");
patient.getMeta().addProfile("http://profile2");
patient.getMeta().addTag("http://tag2", "vtag2", "dtag2");
patient.setActive(false);
assertEquals("2", myPatientDao.update(patient, mySrd).getId().getVersionIdPart());
}
@Nonnull
private List<String> toTags(Patient patient) {
return patient.getMeta().getTag().stream().map(t -> t.getSystem() + "|" + t.getCode() + "|" + t.getDisplay()).collect(Collectors.toList());
}
@Nonnull
private List<String> toProfiles(Patient patient) {
return patient.getMeta().getProfile().stream().map(t -> t.getValue()).collect(Collectors.toList());
}
}

View File

@ -279,7 +279,7 @@ public class FhirResourceDaoR4Test extends BaseJpaR4Test {
table.setIndexStatus(null);
table.setDeleted(new Date());
table = myResourceTableDao.saveAndFlush(table);
ResourceHistoryTable newHistory = table.toHistory();
ResourceHistoryTable newHistory = table.toHistory(true);
ResourceHistoryTable currentHistory = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(table.getId(), 1L);
newHistory.setEncoding(currentHistory.getEncoding());
newHistory.setResource(currentHistory.getResource());

View File

@ -1706,13 +1706,9 @@ public class FhirSystemDaoR4Test extends BaseJpaR4SystemTest {
p.addIdentifier().setSystem("urn:system").setValue(methodName);
request.addEntry().setResource(p).getRequest().setMethod(HTTPVerb.POST).setIfNoneExist("Patient?identifier=urn%3Asystem%7C" + methodName);
try {
mySystemDao.transaction(mySrd, request);
fail();
} catch (InvalidRequestException e) {
assertEquals(e.getMessage(),
"Unable to process Transaction - Request would cause multiple resources to match URL: \"Patient?identifier=urn%3Asystem%7CtestTransactionCreateWithDuplicateMatchUrl01\". Does transaction request contain duplicates?");
}
mySystemDao.transaction(mySrd, request);
assertEquals(1, logAllResources());
assertEquals(1, logAllResourceVersions());
}
@Test
@ -1828,7 +1824,7 @@ public class FhirSystemDaoR4Test extends BaseJpaR4SystemTest {
mySystemDao.transaction(mySrd, request);
fail();
} catch (InvalidRequestException e) {
assertEquals("Invalid match URL[A] - URL has no search parameters", e.getMessage());
assertEquals("Invalid match URL[Observation?A] - URL has no search parameters", e.getMessage());
}
}
@ -2406,13 +2402,9 @@ public class FhirSystemDaoR4Test extends BaseJpaR4SystemTest {
.setMethod(HTTPVerb.POST)
.setIfNoneExist("Encounter?identifier=urn:foo|12345");
try {
mySystemDao.transaction(mySrd, inputBundle);
fail();
} catch (InvalidRequestException e) {
assertEquals("Unable to process Transaction - Request would cause multiple resources to match URL: \"Encounter?identifier=urn:foo|12345\". Does transaction request contain duplicates?",
e.getMessage());
}
mySystemDao.transaction(mySrd, inputBundle);
assertEquals(1, logAllResources());
assertEquals(1, logAllResourceVersions());
}
@Test
@ -2437,14 +2429,10 @@ public class FhirSystemDaoR4Test extends BaseJpaR4SystemTest {
.setMethod(HTTPVerb.PUT)
.setUrl("Encounter?identifier=urn:foo|12345");
try {
mySystemDao.transaction(mySrd, inputBundle);
fail();
} catch (InvalidRequestException e) {
assertEquals("Unable to process Transaction - Request would cause multiple resources to match URL: \"Encounter?identifier=urn:foo|12345\". Does transaction request contain duplicates?",
e.getMessage());
}
mySystemDao.transaction(mySrd, inputBundle);
assertEquals(1, logAllResources());
assertEquals(1, logAllResourceVersions());
}
@Test

View File

@ -39,11 +39,13 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.util.BundleBuilder;
import org.apache.commons.lang3.StringUtils;
import org.hamcrest.Matchers;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.DateTimeType;
import org.hl7.fhir.r4.model.Enumerations;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Observation;
@ -51,6 +53,7 @@ import org.hl7.fhir.r4.model.Organization;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Practitioner;
import org.hl7.fhir.r4.model.PractitionerRole;
import org.hl7.fhir.r4.model.Quantity;
import org.hl7.fhir.r4.model.SearchParameter;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
@ -60,6 +63,9 @@ import org.slf4j.LoggerFactory;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import static ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast;
@ -609,6 +615,8 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
createUniqueCompositeSp();
createRequestId();
addReadPartition(myPartitionId);
addReadPartition(myPartitionId);
addCreatePartition(myPartitionId, myPartitionDate);
addCreatePartition(myPartitionId, myPartitionDate);
@ -2548,7 +2556,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
ourLog.info("Search SQL:\n{}", myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true));
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false);
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID IN ('1')"), searchSql);
assertEquals(1, StringUtils.countMatches(searchSql.toUpperCase(Locale.US), "PARTITION_ID IN ('1')"), searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"), searchSql);
// Same query, different partition
@ -2583,7 +2591,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID IS NULL"), searchSql);
assertEquals(1, StringUtils.countMatches(searchSql.toUpperCase(Locale.US), "PARTITION_ID IS NULL"), searchSql);
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"), searchSql);
// Same query, different partition
@ -2599,6 +2607,127 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
}
@Test
public void testTransaction_MultipleConditionalUpdates() {
myDaoConfig.setIndexMissingFields(DaoConfig.IndexEnabledEnum.DISABLED);
AtomicInteger counter = new AtomicInteger(0);
Supplier<Bundle> input = () -> {
BundleBuilder bb = new BundleBuilder(myFhirCtx);
Patient pt = new Patient();
pt.setId(IdType.newRandomUuid());
pt.addIdentifier().setSystem("http://foo").setValue("123");
bb.addTransactionCreateEntry(pt).conditional("Patient?identifier=http://foo|123");
Observation obsA = new Observation();
obsA.getSubject().setReference(pt.getId());
obsA.getCode().addCoding().setSystem("http://foo").setCode("bar1");
obsA.setValue(new Quantity(null, 1, "http://unitsofmeasure.org", "kg", "kg"));
obsA.setEffective(new DateTimeType(new Date()));
obsA.addNote().setText("Foo " + counter.incrementAndGet()); // changes every time
bb.addTransactionUpdateEntry(obsA).conditional("Observation?code=http://foo|bar1");
Observation obsB = new Observation();
obsB.getSubject().setReference(pt.getId());
obsB.getCode().addCoding().setSystem("http://foo").setCode("bar2");
obsB.setValue(new Quantity(null, 1, "http://unitsofmeasure.org", "kg", "kg"));
obsB.setEffective(new DateTimeType(new Date()));
obsB.addNote().setText("Foo " + counter.incrementAndGet()); // changes every time
bb.addTransactionUpdateEntry(obsB).conditional("Observation?code=http://foo|bar2");
Observation obsC = new Observation();
obsC.getSubject().setReference(pt.getId());
obsC.getCode().addCoding().setSystem("http://foo").setCode("bar3");
obsC.setValue(new Quantity(null, 1, "http://unitsofmeasure.org", "kg", "kg"));
obsC.setEffective(new DateTimeType(new Date()));
obsC.addNote().setText("Foo " + counter.incrementAndGet()); // changes every time
bb.addTransactionUpdateEntry(obsC).conditional("Observation?code=bar3");
Observation obsD = new Observation();
obsD.getSubject().setReference(pt.getId());
obsD.getCode().addCoding().setSystem("http://foo").setCode("bar4");
obsD.setValue(new Quantity(null, 1, "http://unitsofmeasure.org", "kg", "kg"));
obsD.setEffective(new DateTimeType(new Date()));
obsD.addNote().setText("Foo " + counter.incrementAndGet()); // changes every time
bb.addTransactionUpdateEntry(obsD).conditional("Observation?code=bar4");
return (Bundle)bb.getBundle();
};
ourLog.info("About to start transaction");
for (int i = 0; i < 20; i++) {
addReadPartition(1);
}
for (int i = 0; i < 8; i++) {
addCreatePartition(1, null);
}
// Pre-fetch the partition ID from the partition lookup table
createPatient(withPartition(1), withActiveTrue());
myCaptureQueriesListener.clear();
Bundle outcome = mySystemDao.transaction(mySrd, input.get());
ourLog.info("Resp: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
myCaptureQueriesListener.logSelectQueries();
assertEquals(1, myCaptureQueriesListener.countSelectQueries());
assertThat(myCaptureQueriesListener.getSelectQueries().get(0).getSql(true, false), containsString("resourcein0_.HASH_SYS_AND_VALUE='-4132452001562191669' and (resourcein0_.PARTITION_ID in ('1'))"));
myCaptureQueriesListener.logInsertQueries();
assertEquals(6, myCaptureQueriesListener.countInsertQueries());
myCaptureQueriesListener.logUpdateQueries();
assertEquals(1, myCaptureQueriesListener.countUpdateQueries());
assertEquals(0, myCaptureQueriesListener.countDeleteQueries());
/*
* Run a second time
*/
myCaptureQueriesListener.clear();
outcome = mySystemDao.transaction(mySrd, input.get());
ourLog.info("Resp: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
myCaptureQueriesListener.logSelectQueries();
assertEquals(11, myCaptureQueriesListener.countSelectQueries());
myCaptureQueriesListener.logInsertQueries();
assertEquals(1, myCaptureQueriesListener.countInsertQueries());
myCaptureQueriesListener.logUpdateQueries();
assertEquals(2, myCaptureQueriesListener.countUpdateQueries());
assertEquals(0, myCaptureQueriesListener.countDeleteQueries());
/*
* Third time with mass ingestion mode enabled
*/
myDaoConfig.setMassIngestionMode(true);
myDaoConfig.setMatchUrlCache(true);
myCaptureQueriesListener.clear();
outcome = mySystemDao.transaction(mySrd, input.get());
ourLog.info("Resp: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
myCaptureQueriesListener.logSelectQueries();
assertEquals(6, myCaptureQueriesListener.countSelectQueries());
myCaptureQueriesListener.logInsertQueries();
assertEquals(1, myCaptureQueriesListener.countInsertQueries());
myCaptureQueriesListener.logUpdateQueries();
assertEquals(2, myCaptureQueriesListener.countUpdateQueries());
assertEquals(0, myCaptureQueriesListener.countDeleteQueries());
/*
* Fourth time with mass ingestion mode enabled
*/
myCaptureQueriesListener.clear();
outcome = mySystemDao.transaction(mySrd, input.get());
ourLog.info("Resp: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
myCaptureQueriesListener.logSelectQueries();
assertEquals(5, myCaptureQueriesListener.countSelectQueries());
myCaptureQueriesListener.logInsertQueries();
assertEquals(1, myCaptureQueriesListener.countInsertQueries());
myCaptureQueriesListener.logUpdateQueries();
assertEquals(2, myCaptureQueriesListener.countUpdateQueries());
assertEquals(0, myCaptureQueriesListener.countDeleteQueries());
}
@Test
public void testUpdate_ResourcePreExistsInWrongPartition() {
IIdType patientId = createPatient(withPutPartition(null), withId("ONE"), withBirthdate("2020-01-01"));

View File

@ -18,6 +18,7 @@ import ca.uhn.fhir.jpa.dao.JpaResourceDao;
import ca.uhn.fhir.jpa.dao.TransactionProcessor;
import ca.uhn.fhir.jpa.dao.data.IResourceHistoryTableDao;
import ca.uhn.fhir.jpa.dao.index.DaoSearchParamSynchronizer;
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
import ca.uhn.fhir.jpa.dao.index.SearchParamWithInlineReferencesExtractor;
import ca.uhn.fhir.jpa.dao.r4.FhirSystemDaoR4;
import ca.uhn.fhir.jpa.dao.r4.TransactionProcessorVersionAdapterR4;
@ -131,6 +132,8 @@ public class GiantTransactionPerfTest {
private MockResourceHistoryTableDao myResourceHistoryTableDao;
private SearchParamPresenceSvcImpl mySearchParamPresenceSvc;
private DaoSearchParamSynchronizer myDaoSearchParamSynchronizer;
@Mock
private IdHelperService myIdHelperService;
@AfterEach
public void afterEach() {
@ -172,6 +175,9 @@ public class GiantTransactionPerfTest {
myTransactionProcessor.setModelConfig(myDaoConfig.getModelConfig());
myTransactionProcessor.setHapiTransactionService(myHapiTransactionService);
myTransactionProcessor.setDaoRegistry(myDaoRegistry);
myTransactionProcessor.setPartitionSettingsForUnitTest(myPartitionSettings);
myTransactionProcessor.setIdHelperServiceForUnitTest(myIdHelperService);
myTransactionProcessor.setFhirContextForUnitTest(myCtx);
myTransactionProcessor.start();
mySystemDao = new FhirSystemDaoR4();
@ -248,6 +254,7 @@ public class GiantTransactionPerfTest {
myEobDao.setSearchParamPresenceSvc(mySearchParamPresenceSvc);
myEobDao.setDaoSearchParamSynchronizer(myDaoSearchParamSynchronizer);
myEobDao.setDaoConfigForUnitTest(myDaoConfig);
myEobDao.setIdHelperSvcForUnitTest(myIdHelperService);
myEobDao.start();
myDaoRegistry.setResourceDaos(Lists.newArrayList(myEobDao));

View File

@ -0,0 +1,904 @@
{
"resourceType": "Bundle",
"type": "transaction",
"entry": [
{
"resource": {
"resourceType": "ExplanationOfBenefit",
"meta": {
"lastUpdated": "2021-06-07",
"profile": [
"http://hl7.org/fhir/us/carin-bb/StructureDefinition/C4BB-ExplanationOfBenefit-Professional-NonClinician"
]
},
"identifier": [
{
"type": {
"coding": [
{
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBIdentifierType",
"code": "payerid"
}
]
},
"system": "https://hl7.org/fhir/sid/payerid",
"value": "37525500673"
},
{
"type": {
"coding": [
{
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBIdentifierType",
"code": "uc"
}
]
},
"system": "https://hl7.org/fhir/sid/claimid",
"value": "26723516"
}
],
"status": "active",
"type": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/claim-type",
"code": "professional"
}
]
},
"use": "claim",
"patient": {
"reference": "Patient/d16f4424-9703-23bf-8331-3fc4bceb0c21"
},
"billablePeriod": {
"start": "2018-01-09",
"end": "2018-01-09"
},
"created": "2018-01-08T00:00:00-08:00",
"insurer": {
"reference": "Organization/b77d3b98-03d8-1f0a-07b7-30b636c6ea9b"
},
"provider": {
"reference": "Organization/e03b46ec-94df-0849-49eb-f5bba0c024c2"
},
"payee": {
"type": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/payeetype",
"code": "provider"
}
],
"text": "Claim paid to VENDOR"
}
},
"facility": {
"reference": "Location/11651884-37d2-eede-e1b9-059afd90811a"
},
"outcome": "complete",
"disposition": "DENIED",
"careTeam": [
{
"sequence": 1,
"provider": {
"reference": "Practitioner/d2fc93e1-e1f8-c6d3-6c2c-9301f0e02c7c"
},
"responsible": true,
"role": {
"coding": [
{
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBClaimCareTeamRole",
"code": "performing"
}
]
}
}
],
"supportingInfo": [
{
"sequence": 1,
"category": {
"coding": [
{
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBSupportingInfoType",
"code": "clmrecvddate"
}
]
},
"timingDate": "2018-01-08"
},
{
"sequence": 2,
"category": {
"coding": [
{
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBSupportingInfoType",
"code": "billingnetworkcontractingstatus"
}
]
},
"code": {
"coding": [
{
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBPayerAdjudicationStatus",
"code": "contracted"
}
]
}
}
],
"diagnosis": [
{
"sequence": 1,
"diagnosisCodeableConcept": {
"coding": [
{
"system": "http://hl7.org/fhir/sid/icd-10-cm",
"code": "M47.012",
"display": "ANT SPINAL ART COMPRESSION SYND CERVICAL REGION"
}
],
"text": "ANT SPINAL ART COMPRESSION SYND CERVICAL REGION"
},
"type": [
{
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/ex-diagnosistype",
"code": "principal"
}
]
}
]
}
],
"procedure": [
{
"sequence": 1,
"date": "2018-01-08T00:00:00-08:00",
"procedureCodeableConcept": {
"coding": [
{
"system": "http://www.ama-assn.org/go/cpt",
"code": "L0454",
"display": "TLSO FLEX PREFAB SACROCOC-T9"
}
],
"text": "TLSO FLEXIBLE SC JUNCT TO T-9 PREFAB CUSTOM FIT"
}
}
],
"insurance": [
{
"focal": true,
"coverage": {
"reference": "urn:uuid:a8430b1b-1f26-44ea-8866-a605ebb48f21"
}
}
],
"item": [
{
"sequence": 1,
"diagnosisSequence": [
1
],
"procedureSequence": [
1
],
"productOrService": {
"coding": [
{
"system": "http://www.ama-assn.org/go/cpt",
"code": "L0454",
"display": "TLSO FLEX PREFAB SACROCOC-T9"
}
],
"text": "TLSO FLEXIBLE SC JUNCT TO T-9 PREFAB CUSTOM FIT"
},
"modifier": [
{
"coding": [
{
"system": "http://www.ama-assn.org/go/cpt",
"code": "NU",
"display": "NEW EQUIPMENT"
}
],
"text": "NEW EQUIPMENT"
}
],
"servicedPeriod": {
"start": "2018-01-08",
"end": "2018-01-08"
},
"locationCodeableConcept": {
"coding": [
{
"system": "https://www.cms.gov/Medicare/Coding/place-of-service-codes/Place_of_Service_Code_Set",
"code": "11"
}
]
},
"quantity": {
"value": 1,
"unit": "Units",
"system": "http://unitsofmeasure.org",
"code": "[arb'U]"
},
"net": {
"value": 704.26,
"currency": "USD"
},
"noteNumber": [
1,
2
],
"adjudication": [
{
"category": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/adjudication",
"code": "submitted"
}
]
},
"amount": {
"value": 704.26,
"currency": "USD"
}
},
{
"category": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/adjudication",
"code": "benefit"
}
]
},
"amount": {
"value": 0,
"currency": "USD"
}
},
{
"category": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/adjudication",
"code": "copay"
}
]
},
"amount": {
"value": 0,
"currency": "USD"
}
},
{
"category": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/adjudication",
"code": "deductible"
}
]
},
"amount": {
"value": 0,
"currency": "USD"
}
},
{
"category": {
"coding": [
{
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBAdjudication",
"code": "coinsurance"
}
]
},
"amount": {
"value": 0,
"currency": "USD"
}
},
{
"category": {
"coding": [
{
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBAdjudication",
"code": "memberliability"
}
]
},
"amount": {
"value": 0,
"currency": "USD"
}
},
{
"category": {
"coding": [
{
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBAdjudication",
"code": "noncovered"
}
]
},
"amount": {
"value": 0,
"currency": "USD"
}
},
{
"category": {
"coding": [
{
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBAdjudication",
"code": "priorpayerpaid"
}
]
},
"amount": {
"value": 0,
"currency": "USD"
}
},
{
"category": {
"coding": [
{
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBAdjudication",
"code": "paidtoprovider"
}
]
},
"amount": {
"value": 0,
"currency": "USD"
}
},
{
"category": {
"coding": [
{
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBPayerAdjudicationStatus",
"code": "outofnetwork"
}
]
},
"amount": {
"value": 0,
"currency": "USD"
}
}
]
}
],
"total": [
{
"category": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/adjudication",
"code": "submitted"
}
]
},
"amount": {
"value": 704.26,
"currency": "USD"
}
},
{
"category": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/adjudication",
"code": "benefit"
}
]
},
"amount": {
"value": 0,
"currency": "USD"
}
},
{
"category": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/adjudication",
"code": "copay"
}
]
},
"amount": {
"value": 0,
"currency": "USD"
}
},
{
"category": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/adjudication",
"code": "deductible"
}
]
},
"amount": {
"value": 0,
"currency": "USD"
}
},
{
"category": {
"coding": [
{
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBAdjudication",
"code": "coinsurance"
}
]
},
"amount": {
"value": 0,
"currency": "USD"
}
},
{
"category": {
"coding": [
{
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBAdjudication",
"code": "memberliability"
}
]
},
"amount": {
"value": 0,
"currency": "USD"
}
},
{
"category": {
"coding": [
{
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBAdjudication",
"code": "noncovered"
}
]
},
"amount": {
"value": 0,
"currency": "USD"
}
},
{
"category": {
"coding": [
{
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBAdjudication",
"code": "priorpayerpaid"
}
]
},
"amount": {
"value": 0,
"currency": "USD"
}
},
{
"category": {
"coding": [
{
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBAdjudication",
"code": "paidtoprovider"
}
]
},
"amount": {
"value": 0,
"currency": "USD"
}
}
],
"payment": {
"date": "2021-01-22",
"amount": {
"value": 0,
"currency": "USD"
}
},
"processNote": [
{
"number": 1,
"type": "display",
"text": "AUD02: DENY, NOT AUTHORIZED, PROVIDER LIABILITY"
},
{
"number": 2,
"type": "display",
"text": "BED08: DENY, PROCEDURE NOT COVERED"
}
]
},
"request": {
"method": "PUT",
"url": "ExplanationOfBenefit?identifier=37525500673"
}
},
{
"resource": {
"resourceType": "Patient",
"id": "d16f4424-9703-23bf-8331-3fc4bceb0c21",
"meta": {
"lastUpdated": "2021-06-07",
"profile": [
"http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient"
]
},
"identifier": [
{
"type": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/v2-0203",
"code": "MR"
}
]
},
"system": "https://healthy.kaiserpermanente.org/front-door",
"value": "1000116-GA"
}
],
"name": [
{
"use": "usual",
"text": "Gaabcseven Testing",
"family": "Testing",
"given": [
"Gaabcsix"
]
}
],
"telecom": [
{
"system": "phone",
"value": "662-123-3456",
"use": "home"
}
],
"gender": "male",
"birthDate": "1961-01-01",
"address": [
{
"use": "home",
"type": "postal",
"line": [
"TEST ADDRESS AVE, APT 234"
],
"city": "ATLANTA",
"state": "GA",
"postalCode": "30301"
}
]
},
"request": {
"method": "PUT",
"url": "Patient/d16f4424-9703-23bf-8331-3fc4bceb0c21"
}
},
{
"fullUrl": "urn:uuid:a8430b1b-1f26-44ea-8866-a605ebb48f21",
"resource": {
"resourceType": "Coverage",
"meta": {
"lastUpdated": "2021-06-07",
"profile": [
"http://hl7.org/fhir/us/carin-bb/StructureDefinition/C4BB-Coverage"
]
},
"identifier": [
{
"type": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/v2-0203",
"code": "FILL"
}
]
},
"system": "https://hl7.org/fhir/sid/coverageid",
"value": "1000116-GA-10159"
}
],
"status": "active",
"type": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/v3-ActCode",
"code": "HMO",
"display": "health maintenance organization policy"
}
],
"text": "COMMERCIAL HMO-HMO-Amb Accum"
},
"subscriberId": "1000116",
"beneficiary": {
"reference": "Patient/d16f4424-9703-23bf-8331-3fc4bceb0c21"
},
"relationship": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/subscriber-relationship",
"code": "self",
"display": "Self"
}
],
"text": "The Beneficiary is the Subscriber"
},
"period": {
"start": "2017-01-01"
},
"payor": [
{
"reference": "Organization/b77d3b98-03d8-1f0a-07b7-30b636c6ea9b"
}
],
"class": [
{
"type": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/coverage-class",
"code": "group",
"display": "Group"
}
],
"text": "An employee group"
},
"value": "10159",
"name": "10159-100 STATE DEPTS, DFACS, HEALTH-NON-MEDICARE"
}
]
},
"request": {
"method": "PUT",
"url": "Coverage?identifier=1000116-GA-10159"
}
},
{
"resource": {
"resourceType": "Organization",
"id": "e03b46ec-94df-0849-49eb-f5bba0c024c2",
"meta": {
"lastUpdated": "2021-06-07",
"profile": [
"http://hl7.org/fhir/us/carin-bb/StructureDefinition/C4BB-Organization"
]
},
"identifier": [
{
"type": {
"coding": [
{
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBIdentifierType",
"code": "npi"
}
]
},
"system": "http://hl7.org/fhir/sid/us-npi"
},
{
"type": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/v2-0203",
"code": "TAX"
}
]
},
"system": "urn:oid:2.16.840.1.113883.4.4",
"value": "330057155"
}
],
"active": true,
"type": [
{
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/organization-type",
"code": "prov"
}
]
}
],
"name": "APRIA HEALTHCARE LLC",
"address": [
{
"use": "work",
"type": "physical",
"line": [
"2508 SOLUTIONS CENTER"
],
"city": "CHICAGO",
"state": "IL",
"postalCode": "60677-2005",
"country": "USA"
}
]
},
"request": {
"method": "PUT",
"url": "Organization/e03b46ec-94df-0849-49eb-f5bba0c024c2"
}
},
{
"resource": {
"resourceType": "Organization",
"id": "b77d3b98-03d8-1f0a-07b7-30b636c6ea9b",
"meta": {
"lastUpdated": "2021-06-07",
"profile": [
"http://hl7.org/fhir/us/carin-bb/StructureDefinition/C4BB-Organization"
]
},
"identifier": [
{
"use": "usual",
"type": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/v2-0203",
"code": "FILL"
}
]
},
"system": "https://hl7.org/fhir/sid/organizationid",
"value": "NATLTAP GA-KFHP-GA"
}
],
"active": true,
"type": [
{
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/organization-type",
"code": "pay",
"display": "Payer"
}
]
}
],
"name": "KAISER FOUNDATION HEALTHPLAN, INC",
"telecom": [
{
"system": "phone",
"value": "1-888-865-5813",
"use": "work"
}
],
"address": [
{
"use": "work",
"type": "postal",
"line": [
"NATIONAL CLAIMS ADMINISTRATION GEORGIA",
"PO Box 629028"
],
"city": "El Dorado Hills",
"state": "CA",
"postalCode": "95762-9028"
}
]
},
"request": {
"method": "PUT",
"url": "Organization/b77d3b98-03d8-1f0a-07b7-30b636c6ea9b"
}
},
{
"resource": {
"resourceType": "Practitioner",
"id": "d2fc93e1-e1f8-c6d3-6c2c-9301f0e02c7c",
"meta": {
"lastUpdated": "2021-06-07",
"profile": [
"http://hl7.org/fhir/us/core/StructureDefinition/us-core-practitioner"
]
},
"identifier": [
{
"use": "usual",
"type": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/v2-0203",
"code": "NPI"
}
]
},
"system": "http://hl7.org/fhir/sid/us-npi",
"value": "PIN2001487498"
}
],
"name": [
{
"use": "usual",
"text": "APRIA HEALTHCARE LLC",
"family": "APRIA HEALTHCARE LLC"
}
],
"address": [
{
"use": "work",
"line": [
"805 MARATHON PARKWAY",
"SUITE 160"
],
"city": "LAWRENCEVILLE",
"state": "GA",
"postalCode": "30046"
}
]
},
"request": {
"method": "PUT",
"url": "Practitioner/d2fc93e1-e1f8-c6d3-6c2c-9301f0e02c7c"
}
},
{
"resource": {
"resourceType": "Location",
"id": "11651884-37d2-eede-e1b9-059afd90811a",
"meta": {
"lastUpdated": "2021-06-07"
},
"identifier": [
{
"use": "usual",
"type": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/v2-0203",
"code": "NPI"
}
]
},
"value": "PIN12120678"
}
],
"status": "active",
"name": "APRIA HEALTHCARE INC-30013",
"mode": "kind",
"type": [
{
"coding": [
{
"system": "https://www.cms.gov/Medicare/Coding/place-of-service-codes/Place_of_Service_Code_Set",
"code": "99"
}
]
}
],
"address": {
"use": "work",
"type": "physical",
"line": [
"594 SIGMAN RD STE 100"
],
"city": "CONYERS",
"state": "GA",
"postalCode": "30013-1365"
}
},
"request": {
"method": "PUT",
"url": "Location/11651884-37d2-eede-e1b9-059afd90811a"
}
}
]
}

View File

@ -0,0 +1,904 @@
{
"resourceType": "Bundle",
"type": "transaction",
"entry": [
{
"resource": {
"resourceType": "ExplanationOfBenefit",
"meta": {
"lastUpdated": "2021-06-07",
"profile": [
"http://hl7.org/fhir/us/carin-bb/StructureDefinition/C4BB-ExplanationOfBenefit-Professional-NonClinician"
]
},
"identifier": [
{
"type": {
"coding": [
{
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBIdentifierType",
"code": "payerid"
}
]
},
"system": "https://hl7.org/fhir/sid/payerid",
"value": "37525500673"
},
{
"type": {
"coding": [
{
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBIdentifierType",
"code": "uc"
}
]
},
"system": "https://hl7.org/fhir/sid/claimid",
"value": "26723516"
}
],
"status": "active",
"type": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/claim-type",
"code": "professional"
}
]
},
"use": "claim",
"patient": {
"reference": "Patient/d16f4424-9703-23bf-8331-3fc4bceb0c21"
},
"billablePeriod": {
"start": "2018-01-08",
"end": "2018-01-08"
},
"created": "2018-01-08T00:00:00-08:00",
"insurer": {
"reference": "Organization/b77d3b98-03d8-1f0a-07b7-30b636c6ea9b"
},
"provider": {
"reference": "Organization/e03b46ec-94df-0849-49eb-f5bba0c024c2"
},
"payee": {
"type": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/payeetype",
"code": "provider"
}
],
"text": "Claim paid to VENDOR"
}
},
"facility": {
"reference": "Location/11651884-37d2-eede-e1b9-059afd90811a"
},
"outcome": "complete",
"disposition": "DENIED",
"careTeam": [
{
"sequence": 1,
"provider": {
"reference": "Practitioner/d2fc93e1-e1f8-c6d3-6c2c-9301f0e02c7c"
},
"responsible": true,
"role": {
"coding": [
{
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBClaimCareTeamRole",
"code": "performing"
}
]
}
}
],
"supportingInfo": [
{
"sequence": 1,
"category": {
"coding": [
{
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBSupportingInfoType",
"code": "clmrecvddate"
}
]
},
"timingDate": "2018-01-08"
},
{
"sequence": 2,
"category": {
"coding": [
{
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBSupportingInfoType",
"code": "billingnetworkcontractingstatus"
}
]
},
"code": {
"coding": [
{
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBPayerAdjudicationStatus",
"code": "contracted"
}
]
}
}
],
"diagnosis": [
{
"sequence": 1,
"diagnosisCodeableConcept": {
"coding": [
{
"system": "http://hl7.org/fhir/sid/icd-10-cm",
"code": "M47.012",
"display": "ANT SPINAL ART COMPRESSION SYND CERVICAL REGION"
}
],
"text": "ANT SPINAL ART COMPRESSION SYND CERVICAL REGION"
},
"type": [
{
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/ex-diagnosistype",
"code": "principal"
}
]
}
]
}
],
"procedure": [
{
"sequence": 1,
"date": "2018-01-08T00:00:00-08:00",
"procedureCodeableConcept": {
"coding": [
{
"system": "http://www.ama-assn.org/go/cpt",
"code": "L0454",
"display": "TLSO FLEX PREFAB SACROCOC-T9"
}
],
"text": "TLSO FLEXIBLE SC JUNCT TO T-9 PREFAB CUSTOM FIT"
}
}
],
"insurance": [
{
"focal": true,
"coverage": {
"reference": "urn:uuid:a8430b1b-1f26-44ea-8866-a605ebb48f21"
}
}
],
"item": [
{
"sequence": 1,
"diagnosisSequence": [
1
],
"procedureSequence": [
1
],
"productOrService": {
"coding": [
{
"system": "http://www.ama-assn.org/go/cpt",
"code": "L0454",
"display": "TLSO FLEX PREFAB SACROCOC-T9"
}
],
"text": "TLSO FLEXIBLE SC JUNCT TO T-9 PREFAB CUSTOM FIT"
},
"modifier": [
{
"coding": [
{
"system": "http://www.ama-assn.org/go/cpt",
"code": "NU",
"display": "NEW EQUIPMENT"
}
],
"text": "NEW EQUIPMENT"
}
],
"servicedPeriod": {
"start": "2018-01-08",
"end": "2018-01-08"
},
"locationCodeableConcept": {
"coding": [
{
"system": "https://www.cms.gov/Medicare/Coding/place-of-service-codes/Place_of_Service_Code_Set",
"code": "11"
}
]
},
"quantity": {
"value": 1,
"unit": "Units",
"system": "http://unitsofmeasure.org",
"code": "[arb'U]"
},
"net": {
"value": 704.26,
"currency": "USD"
},
"noteNumber": [
1,
2
],
"adjudication": [
{
"category": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/adjudication",
"code": "submitted"
}
]
},
"amount": {
"value": 704.26,
"currency": "USD"
}
},
{
"category": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/adjudication",
"code": "benefit"
}
]
},
"amount": {
"value": 0,
"currency": "USD"
}
},
{
"category": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/adjudication",
"code": "copay"
}
]
},
"amount": {
"value": 0,
"currency": "USD"
}
},
{
"category": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/adjudication",
"code": "deductible"
}
]
},
"amount": {
"value": 0,
"currency": "USD"
}
},
{
"category": {
"coding": [
{
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBAdjudication",
"code": "coinsurance"
}
]
},
"amount": {
"value": 0,
"currency": "USD"
}
},
{
"category": {
"coding": [
{
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBAdjudication",
"code": "memberliability"
}
]
},
"amount": {
"value": 0,
"currency": "USD"
}
},
{
"category": {
"coding": [
{
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBAdjudication",
"code": "noncovered"
}
]
},
"amount": {
"value": 0,
"currency": "USD"
}
},
{
"category": {
"coding": [
{
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBAdjudication",
"code": "priorpayerpaid"
}
]
},
"amount": {
"value": 0,
"currency": "USD"
}
},
{
"category": {
"coding": [
{
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBAdjudication",
"code": "paidtoprovider"
}
]
},
"amount": {
"value": 0,
"currency": "USD"
}
},
{
"category": {
"coding": [
{
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBPayerAdjudicationStatus",
"code": "outofnetwork"
}
]
},
"amount": {
"value": 0,
"currency": "USD"
}
}
]
}
],
"total": [
{
"category": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/adjudication",
"code": "submitted"
}
]
},
"amount": {
"value": 704.26,
"currency": "USD"
}
},
{
"category": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/adjudication",
"code": "benefit"
}
]
},
"amount": {
"value": 0,
"currency": "USD"
}
},
{
"category": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/adjudication",
"code": "copay"
}
]
},
"amount": {
"value": 0,
"currency": "USD"
}
},
{
"category": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/adjudication",
"code": "deductible"
}
]
},
"amount": {
"value": 0,
"currency": "USD"
}
},
{
"category": {
"coding": [
{
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBAdjudication",
"code": "coinsurance"
}
]
},
"amount": {
"value": 0,
"currency": "USD"
}
},
{
"category": {
"coding": [
{
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBAdjudication",
"code": "memberliability"
}
]
},
"amount": {
"value": 0,
"currency": "USD"
}
},
{
"category": {
"coding": [
{
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBAdjudication",
"code": "noncovered"
}
]
},
"amount": {
"value": 0,
"currency": "USD"
}
},
{
"category": {
"coding": [
{
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBAdjudication",
"code": "priorpayerpaid"
}
]
},
"amount": {
"value": 0,
"currency": "USD"
}
},
{
"category": {
"coding": [
{
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBAdjudication",
"code": "paidtoprovider"
}
]
},
"amount": {
"value": 0,
"currency": "USD"
}
}
],
"payment": {
"date": "2021-01-22",
"amount": {
"value": 0,
"currency": "USD"
}
},
"processNote": [
{
"number": 1,
"type": "display",
"text": "AUD02: DENY, NOT AUTHORIZED, PROVIDER LIABILITY"
},
{
"number": 2,
"type": "display",
"text": "BED08: DENY, PROCEDURE NOT COVERED"
}
]
},
"request": {
"method": "PUT",
"url": "ExplanationOfBenefit?identifier=37525500673"
}
},
{
"resource": {
"resourceType": "Patient",
"id": "d16f4424-9703-23bf-8331-3fc4bceb0c21",
"meta": {
"lastUpdated": "2021-06-07",
"profile": [
"http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient"
]
},
"identifier": [
{
"type": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/v2-0203",
"code": "MR"
}
]
},
"system": "https://healthy.kaiserpermanente.org/front-door",
"value": "1000116-GA"
}
],
"name": [
{
"use": "usual",
"text": "Gaabcsix Testing",
"family": "Testing",
"given": [
"Gaabcsix"
]
}
],
"telecom": [
{
"system": "phone",
"value": "662-123-3456",
"use": "home"
}
],
"gender": "male",
"birthDate": "1961-01-01",
"address": [
{
"use": "home",
"type": "postal",
"line": [
"TEST ADDRESS AVE, APT 234"
],
"city": "ATLANTA",
"state": "GA",
"postalCode": "30301"
}
]
},
"request": {
"method": "PUT",
"url": "Patient/d16f4424-9703-23bf-8331-3fc4bceb0c21"
}
},
{
"fullUrl": "urn:uuid:a8430b1b-1f26-44ea-8866-a605ebb48f21",
"resource": {
"resourceType": "Coverage",
"meta": {
"lastUpdated": "2021-06-07",
"profile": [
"http://hl7.org/fhir/us/carin-bb/StructureDefinition/C4BB-Coverage"
]
},
"identifier": [
{
"type": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/v2-0203",
"code": "FILL"
}
]
},
"system": "https://hl7.org/fhir/sid/coverageid",
"value": "1000116-GA-10159"
}
],
"status": "active",
"type": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/v3-ActCode",
"code": "HMO",
"display": "health maintenance organization policy"
}
],
"text": "COMMERCIAL HMO-HMO-Amb Accum"
},
"subscriberId": "1000116",
"beneficiary": {
"reference": "Patient/d16f4424-9703-23bf-8331-3fc4bceb0c21"
},
"relationship": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/subscriber-relationship",
"code": "self",
"display": "Self"
}
],
"text": "The Beneficiary is the Subscriber"
},
"period": {
"start": "2017-01-01"
},
"payor": [
{
"reference": "Organization/b77d3b98-03d8-1f0a-07b7-30b636c6ea9b"
}
],
"class": [
{
"type": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/coverage-class",
"code": "group",
"display": "Group"
}
],
"text": "An employee group"
},
"value": "10159",
"name": "10159-100 STATE DEPTS, DFACS, HEALTH-NON-MEDICARE"
}
]
},
"request": {
"method": "PUT",
"url": "Coverage?identifier=1000116-GA-10159"
}
},
{
"resource": {
"resourceType": "Organization",
"id": "e03b46ec-94df-0849-49eb-f5bba0c024c2",
"meta": {
"lastUpdated": "2021-06-07",
"profile": [
"http://hl7.org/fhir/us/carin-bb/StructureDefinition/C4BB-Organization"
]
},
"identifier": [
{
"type": {
"coding": [
{
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBIdentifierType",
"code": "npi"
}
]
},
"system": "http://hl7.org/fhir/sid/us-npi"
},
{
"type": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/v2-0203",
"code": "TAX"
}
]
},
"system": "urn:oid:2.16.840.1.113883.4.4",
"value": "330057155"
}
],
"active": true,
"type": [
{
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/organization-type",
"code": "prov"
}
]
}
],
"name": "APRIA HEALTHCARE LLC",
"address": [
{
"use": "work",
"type": "physical",
"line": [
"2508 SOLUTIONS CENTER"
],
"city": "CHICAGO",
"state": "IL",
"postalCode": "60677-2005",
"country": "USA"
}
]
},
"request": {
"method": "PUT",
"url": "Organization/e03b46ec-94df-0849-49eb-f5bba0c024c2"
}
},
{
"resource": {
"resourceType": "Organization",
"id": "b77d3b98-03d8-1f0a-07b7-30b636c6ea9b",
"meta": {
"lastUpdated": "2021-06-07",
"profile": [
"http://hl7.org/fhir/us/carin-bb/StructureDefinition/C4BB-Organization"
]
},
"identifier": [
{
"use": "usual",
"type": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/v2-0203",
"code": "FILL"
}
]
},
"system": "https://hl7.org/fhir/sid/organizationid",
"value": "NATLTAP GA-KFHP-GA"
}
],
"active": true,
"type": [
{
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/organization-type",
"code": "pay",
"display": "Payer"
}
]
}
],
"name": "KAISER FOUNDATION HEALTHPLAN, INC",
"telecom": [
{
"system": "phone",
"value": "1-888-865-5813",
"use": "work"
}
],
"address": [
{
"use": "work",
"type": "postal",
"line": [
"NATIONAL CLAIMS ADMINISTRATION GEORGIA",
"PO Box 629028"
],
"city": "El Dorado Hills",
"state": "CA",
"postalCode": "95762-9028"
}
]
},
"request": {
"method": "PUT",
"url": "Organization/b77d3b98-03d8-1f0a-07b7-30b636c6ea9b"
}
},
{
"resource": {
"resourceType": "Practitioner",
"id": "d2fc93e1-e1f8-c6d3-6c2c-9301f0e02c7c",
"meta": {
"lastUpdated": "2021-06-07",
"profile": [
"http://hl7.org/fhir/us/core/StructureDefinition/us-core-practitioner"
]
},
"identifier": [
{
"use": "usual",
"type": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/v2-0203",
"code": "NPI"
}
]
},
"system": "http://hl7.org/fhir/sid/us-npi",
"value": "PIN2001487498"
}
],
"name": [
{
"use": "usual",
"text": "APRIA HEALTHCARE LLC",
"family": "APRIA HEALTHCARE LLC"
}
],
"address": [
{
"use": "work",
"line": [
"805 MARATHON PARKWAY",
"SUITE 160"
],
"city": "LAWRENCEVILLE",
"state": "GA",
"postalCode": "30046"
}
]
},
"request": {
"method": "PUT",
"url": "Practitioner/d2fc93e1-e1f8-c6d3-6c2c-9301f0e02c7c"
}
},
{
"resource": {
"resourceType": "Location",
"id": "11651884-37d2-eede-e1b9-059afd90811a",
"meta": {
"lastUpdated": "2021-06-07"
},
"identifier": [
{
"use": "usual",
"type": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/v2-0203",
"code": "NPI"
}
]
},
"value": "PIN12120678"
}
],
"status": "active",
"name": "APRIA HEALTHCARE INC-30013",
"mode": "kind",
"type": [
{
"coding": [
{
"system": "https://www.cms.gov/Medicare/Coding/place-of-service-codes/Place_of_Service_Code_Set",
"code": "99"
}
]
}
],
"address": {
"use": "work",
"type": "physical",
"line": [
"594 SIGMAN RD STE 100"
],
"city": "CONYERS",
"state": "GA",
"postalCode": "30013-1365"
}
},
"request": {
"method": "PUT",
"url": "Location/11651884-37d2-eede-e1b9-059afd90811a"
}
}
]
}

View File

@ -184,7 +184,7 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa
myHashIdentity = theHashIdentity;
}
Long getHashSystemAndValue() {
public Long getHashSystemAndValue() {
return myHashSystemAndValue;
}
@ -192,7 +192,7 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa
myHashSystemAndValue = theHashSystemAndValue;
}
Long getHashValue() {
public Long getHashValue() {
return myHashValue;
}

View File

@ -604,7 +604,7 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas
myNarrativeText = theNarrativeText;
}
public ResourceHistoryTable toHistory() {
public ResourceHistoryTable toHistory(boolean theCreateVersionTags) {
ResourceHistoryTable retVal = new ResourceHistoryTable();
retVal.setResourceId(myId);
@ -623,7 +623,7 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas
retVal.getTags().clear();
retVal.setHasTags(isHasTags());
if (isHasTags()) {
if (isHasTags() && theCreateVersionTags) {
for (ResourceTag next : getTags()) {
retVal.addTag(next);
}

View File

@ -21,6 +21,7 @@ package ca.uhn.fhir.rest.api.server.storage;
*/
import ca.uhn.fhir.util.ObjectUtil;
import org.hl7.fhir.instance.model.api.IIdType;
import java.util.ArrayList;
import java.util.Collection;
@ -32,9 +33,9 @@ import java.util.Optional;
* a Long, a String, or something else.
*/
public class ResourcePersistentId {
private Object myId;
private Long myVersion;
private IIdType myAssociatedResourceId;
public ResourcePersistentId(Object theId) {
this(theId, null);
@ -50,6 +51,15 @@ public class ResourcePersistentId {
myVersion = theVersion;
}
public IIdType getAssociatedResourceId() {
return myAssociatedResourceId;
}
public ResourcePersistentId setAssociatedResourceId(IIdType theAssociatedResourceId) {
myAssociatedResourceId = theAssociatedResourceId;
return this;
}
@Override
public boolean equals(Object theO) {
if (!(theO instanceof ResourcePersistentId)) {

View File

@ -28,6 +28,7 @@ import com.google.common.collect.ListMultimap;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IIdType;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Collections;
import java.util.Date;
@ -50,8 +51,11 @@ import java.util.function.Supplier;
*/
public class TransactionDetails {
public static final ResourcePersistentId NOT_FOUND = new ResourcePersistentId(-1L);
private final Date myTransactionDate;
private Map<String, ResourcePersistentId> myResolvedResourceIds = Collections.emptyMap();
private Map<String, ResourcePersistentId> myResolvedMatchUrls = Collections.emptyMap();
private Map<String, Object> myUserData;
private ListMultimap<Pointcut, HookParams> myDeferredInterceptorBroadcasts;
private EnumSet<Pointcut> myDeferredInterceptorBroadcastPointcuts;
@ -82,14 +86,28 @@ public class TransactionDetails {
return myResolvedResourceIds.get(idValue);
}
/**
* Was the given resource ID resolved previously in this transaction as not existing
*/
public boolean isResolvedResourceIdEmpty(IIdType theId) {
if (myResolvedResourceIds != null) {
if (myResolvedResourceIds.containsKey(theId.toVersionless().getValue())) {
if (myResolvedResourceIds.get(theId.toVersionless().getValue()) == null) {
return true;
}
}
}
return false;
}
/**
* A <b>Resolved Resource ID</b> is a mapping between a resource ID (e.g. "<code>Patient/ABC</code>" or
* "<code>Observation/123</code>") and a storage ID for that resource. Resources should only be placed within
* the TransactionDetails if they are known to exist and be valid targets for other resources to link to.
*/
public void addResolvedResourceId(IIdType theResourceId, ResourcePersistentId thePersistentId) {
public void addResolvedResourceId(IIdType theResourceId, @Nullable ResourcePersistentId thePersistentId) {
assert theResourceId != null;
assert thePersistentId != null;
if (myResolvedResourceIds.isEmpty()) {
myResolvedResourceIds = new HashMap<>();
@ -97,6 +115,25 @@ public class TransactionDetails {
myResolvedResourceIds.put(theResourceId.toVersionless().getValue(), thePersistentId);
}
public Map<String, ResourcePersistentId> getResolvedMatchUrls() {
return myResolvedMatchUrls;
}
/**
* A <b>Resolved Conditional URL</b> is a mapping between a conditional URL (e.g. "<code>Patient?identifier=foo|bar</code>" or
* "<code>Observation/123</code>") and a storage ID for that resource. Resources should only be placed within
* the TransactionDetails if they are known to exist and be valid targets for other resources to link to.
*/
public void addResolvedMatchUrl(String theConditionalUrl, @Nonnull ResourcePersistentId thePersistentId) {
Validate.notBlank(theConditionalUrl);
Validate.notNull(thePersistentId);
if (myResolvedMatchUrls.isEmpty()) {
myResolvedMatchUrls = new HashMap<>();
}
myResolvedMatchUrls.put(theConditionalUrl, thePersistentId);
}
/**
* This is the wall-clock time that a given transaction started.
*/