Avoid redundant PID lookups in transaction processing (#3061)

* Avoid redundant PID lookups in transaction processing

* Add changelog

* Test fix
This commit is contained in:
James Agnew 2021-10-11 13:45:57 -04:00 committed by GitHub
parent 6ad0dffd00
commit aed718bd6f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 220 additions and 27 deletions

View File

@ -0,0 +1,5 @@
---
type: perf
issue: 3061
title: "When ingesting Synthea data (or simnilarly structured transaction bundles) into a JPA server, a redundant
resource ID lookup has been optimized out. This should particularly speed up larger sized transactions."

View File

@ -336,7 +336,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
ResourcePersistentId persistentId = new ResourcePersistentId(updatedEntity.getResourceId()); ResourcePersistentId persistentId = new ResourcePersistentId(updatedEntity.getResourceId());
theTransactionDetails.addResolvedResourceId(id, persistentId); theTransactionDetails.addResolvedResourceId(id, persistentId);
if (entity.getForcedId() != null) { if (entity.getForcedId() != null) {
myIdHelperService.addResolvedPidToForcedId(persistentId, theRequestPartitionId, updatedEntity.getResourceType(), updatedEntity.getForcedId().getForcedId()); myIdHelperService.addResolvedPidToForcedId(persistentId, theRequestPartitionId, updatedEntity.getResourceType(), updatedEntity.getForcedId().getForcedId(), updatedEntity.getDeleted());
} }
theResource.setId(entity.getIdDt()); theResource.setId(entity.getIdDt());
@ -355,9 +355,14 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
} }
} }
ResourcePersistentId resourcePersistentId = new ResourcePersistentId(entity.getResourceId());
resourcePersistentId.setAssociatedResourceId(entity.getIdType(myFhirContext));
theTransactionDetails.addResolvedResourceId(resourcePersistentId.getAssociatedResourceId(), resourcePersistentId);
// Pre-cache the match URL
if (theIfNoneExist != null) { if (theIfNoneExist != null) {
// Pre-cache the match URL myMatchResourceUrlService.matchUrlResolved(theTransactionDetails, getResourceName(), theIfNoneExist, resourcePersistentId);
myMatchResourceUrlService.matchUrlResolved(theTransactionDetails, getResourceName(), theIfNoneExist, new ResourcePersistentId(entity.getResourceId()));
} }
// Update the version/last updated in the resource so that interceptors get // Update the version/last updated in the resource so that interceptors get
@ -390,7 +395,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
if (updatedEntity.getForcedId() != null) { if (updatedEntity.getForcedId() != null) {
forcedId = updatedEntity.getForcedId().getForcedId(); forcedId = updatedEntity.getForcedId().getForcedId();
} }
myIdHelperService.addResolvedPidToForcedId(persistentId, theRequestPartitionId, getResourceName(), forcedId); myIdHelperService.addResolvedPidToForcedId(persistentId, theRequestPartitionId, getResourceName(), forcedId, null);
ourLog.debug(msg); ourLog.debug(msg);
return outcome; return outcome;

View File

@ -33,8 +33,11 @@ import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.model.cross.IResourceLookup; import ca.uhn.fhir.jpa.model.cross.IResourceLookup;
import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.searchparam.extractor.IResourceLinkResolver; import ca.uhn.fhir.jpa.searchparam.extractor.IResourceLinkResolver;
import ca.uhn.fhir.jpa.util.MemoryCacheService;
import ca.uhn.fhir.mdm.util.CanonicalIdentifier; import ca.uhn.fhir.mdm.util.CanonicalIdentifier;
import ca.uhn.fhir.rest.api.server.RequestDetails; 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.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
@ -74,7 +77,7 @@ public class DaoResourceLinkResolver implements IResourceLinkResolver {
private DaoRegistry myDaoRegistry; private DaoRegistry myDaoRegistry;
@Override @Override
public IResourceLookup findTargetResource(@Nonnull RequestPartitionId theRequestPartitionId, RuntimeSearchParam theSearchParam, String theSourcePath, IIdType theSourceResourceId, String theResourceType, Class<? extends IBaseResource> theType, IBaseReference theReference, RequestDetails theRequest) { public IResourceLookup findTargetResource(@Nonnull RequestPartitionId theRequestPartitionId, RuntimeSearchParam theSearchParam, String theSourcePath, IIdType theSourceResourceId, String theResourceType, Class<? extends IBaseResource> theType, IBaseReference theReference, RequestDetails theRequest, TransactionDetails theTransactionDetails) {
IResourceLookup resolvedResource; IResourceLookup resolvedResource;
String idPart = theSourceResourceId.getIdPart(); String idPart = theSourceResourceId.getIdPart();
try { try {
@ -82,7 +85,7 @@ public class DaoResourceLinkResolver implements IResourceLinkResolver {
ourLog.trace("Translated {}/{} to resource PID {}", theType, idPart, resolvedResource); ourLog.trace("Translated {}/{} to resource PID {}", theType, idPart, resolvedResource);
} catch (ResourceNotFoundException e) { } catch (ResourceNotFoundException e) {
Optional<ResourceTable> createdTableOpt = createPlaceholderTargetIfConfiguredToDoSo(theType, theReference, idPart, theRequest); Optional<ResourceTable> createdTableOpt = createPlaceholderTargetIfConfiguredToDoSo(theType, theReference, idPart, theRequest, theTransactionDetails);
if (!createdTableOpt.isPresent()) { if (!createdTableOpt.isPresent()) {
if (myDaoConfig.isEnforceReferentialIntegrityOnWrite() == false) { if (myDaoConfig.isEnforceReferentialIntegrityOnWrite() == false) {
@ -118,7 +121,7 @@ public class DaoResourceLinkResolver implements IResourceLinkResolver {
/** /**
* @param theIdToAssignToPlaceholder If specified, the placeholder resource created will be given a specific ID * @param theIdToAssignToPlaceholder If specified, the placeholder resource created will be given a specific ID
*/ */
public <T extends IBaseResource> Optional<ResourceTable> createPlaceholderTargetIfConfiguredToDoSo(Class<T> theType, IBaseReference theReference, @Nullable String theIdToAssignToPlaceholder, RequestDetails theRequest) { public <T extends IBaseResource> Optional<ResourceTable> createPlaceholderTargetIfConfiguredToDoSo(Class<T> theType, IBaseReference theReference, @Nullable String theIdToAssignToPlaceholder, RequestDetails theRequest, TransactionDetails theTransactionDetails) {
ResourceTable valueOf = null; ResourceTable valueOf = null;
if (myDaoConfig.isAutoCreatePlaceholderReferenceTargets()) { if (myDaoConfig.isAutoCreatePlaceholderReferenceTargets()) {
@ -143,6 +146,10 @@ public class DaoResourceLinkResolver implements IResourceLinkResolver {
} else { } else {
valueOf = ((ResourceTable) placeholderResourceDao.create(newResource, theRequest).getEntity()); valueOf = ((ResourceTable) placeholderResourceDao.create(newResource, theRequest).getEntity());
} }
ResourcePersistentId persistentId = new ResourcePersistentId(valueOf.getResourceId(), 1L);
persistentId.setAssociatedResourceId(valueOf.getIdType(myContext));
theTransactionDetails.addResolvedResourceId(persistentId.getAssociatedResourceId(), persistentId);
} }
return Optional.ofNullable(valueOf); return Optional.ofNullable(valueOf);

View File

@ -40,6 +40,7 @@ import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ListMultimap; import com.google.common.collect.ListMultimap;
import com.google.common.collect.MultimapBuilder; import com.google.common.collect.MultimapBuilder;
import org.apache.commons.lang3.Functions;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IAnyResource;
@ -339,6 +340,10 @@ public class IdHelperService {
*/ */
@Nonnull @Nonnull
public IIdType translatePidIdToForcedId(FhirContext theCtx, String theResourceType, ResourcePersistentId theId) { public IIdType translatePidIdToForcedId(FhirContext theCtx, String theResourceType, ResourcePersistentId theId) {
if (theId.getAssociatedResourceId() != null) {
return theId.getAssociatedResourceId();
}
IIdType retVal = theCtx.getVersion().newIdType(); IIdType retVal = theCtx.getVersion().newIdType();
Optional<String> forcedId = translatePidIdToForcedIdWithCache(theId); Optional<String> forcedId = translatePidIdToForcedIdWithCache(theId);
@ -630,7 +635,7 @@ public class IdHelperService {
/** /**
* Pre-cache a PID-to-Resource-ID mapping for later retrieval by {@link #translatePidsToForcedIds(Set)} and related methods * Pre-cache a PID-to-Resource-ID mapping for later retrieval by {@link #translatePidsToForcedIds(Set)} and related methods
*/ */
public void addResolvedPidToForcedId(ResourcePersistentId theResourcePersistentId, @Nonnull RequestPartitionId theRequestPartitionId, String theResourceType, @Nullable String theForcedId) { public void addResolvedPidToForcedId(ResourcePersistentId theResourcePersistentId, @Nonnull RequestPartitionId theRequestPartitionId, String theResourceType, @Nullable String theForcedId, @Nullable Date theDeletedAt) {
if (theForcedId != null) { if (theForcedId != null) {
if (theResourcePersistentId.getAssociatedResourceId() == null) { if (theResourcePersistentId.getAssociatedResourceId() == null) {
populateAssociatedResourceId(theResourceType, theForcedId, theResourcePersistentId); populateAssociatedResourceId(theResourceType, theForcedId, theResourcePersistentId);
@ -642,6 +647,13 @@ public class IdHelperService {
} else { } else {
myMemoryCacheService.putAfterCommit(MemoryCacheService.CacheEnum.PID_TO_FORCED_ID, theResourcePersistentId.getIdAsLong(), Optional.empty()); myMemoryCacheService.putAfterCommit(MemoryCacheService.CacheEnum.PID_TO_FORCED_ID, theResourcePersistentId.getIdAsLong(), Optional.empty());
} }
if (!myDaoConfig.isDeleteEnabled()) {
ResourceLookup lookup = new ResourceLookup(theResourceType, theResourcePersistentId.getIdAsLong(), theDeletedAt);
String nextKey = theResourcePersistentId.toString();
myMemoryCacheService.putAfterCommit(MemoryCacheService.CacheEnum.RESOURCE_LOOKUP, nextKey, lookup);
}
} }
@VisibleForTesting @VisibleForTesting

View File

@ -39,6 +39,7 @@ import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.searchparam.extractor.ResourceIndexedSearchParams; import ca.uhn.fhir.jpa.searchparam.extractor.ResourceIndexedSearchParams;
import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorService; import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorService;
import ca.uhn.fhir.jpa.searchparam.util.JpaParamUtil; import ca.uhn.fhir.jpa.searchparam.util.JpaParamUtil;
import ca.uhn.fhir.jpa.util.MemoryCacheService;
import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum; import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.api.server.RequestDetails;
@ -315,6 +316,12 @@ public class SearchParamWithInlineReferencesExtractor {
myContext = theContext; myContext = theContext;
} }
@Autowired
private MemoryCacheService myMemoryCacheService;
/** /**
* 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 * 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. * matching resource.
@ -350,15 +357,19 @@ public class SearchParamWithInlineReferencesExtractor {
throw new InvalidRequestException(msg); throw new InvalidRequestException(msg);
} }
Class<? extends IBaseResource> matchResourceType = matchResourceDef.getImplementingClass(); Class<? extends IBaseResource> matchResourceType = matchResourceDef.getImplementingClass();
//Attempt to find the target reference before creating a placeholder //Attempt to find the target reference before creating a placeholder
Set<ResourcePersistentId> matches = myMatchResourceUrlService.processMatchUrl(nextIdText, matchResourceType, theTransactionDetails, theRequest); Set<ResourcePersistentId> matches = myMatchResourceUrlService.processMatchUrl(nextIdText, matchResourceType, theTransactionDetails, theRequest);
ResourcePersistentId match; ResourcePersistentId match;
if (matches.isEmpty()) { if (matches.isEmpty()) {
Optional<ResourceTable> placeholderOpt = myDaoResourceLinkResolver.createPlaceholderTargetIfConfiguredToDoSo(matchResourceType, nextRef, null, theRequest); Optional<ResourceTable> placeholderOpt = myDaoResourceLinkResolver.createPlaceholderTargetIfConfiguredToDoSo(matchResourceType, nextRef, null, theRequest, theTransactionDetails);
if (placeholderOpt.isPresent()) { if (placeholderOpt.isPresent()) {
match = new ResourcePersistentId(placeholderOpt.get().getResourceId()); match = new ResourcePersistentId(placeholderOpt.get().getResourceId());
match.setAssociatedResourceId(placeholderOpt.get().getIdType(myContext));
theTransactionDetails.addResolvedMatchUrl(nextIdText, match);
myMemoryCacheService.putAfterCommit(MemoryCacheService.CacheEnum.MATCH_URL, nextIdText, match);
} else { } else {
String msg = myContext.getLocalizer().getMessage(BaseHapiFhirDao.class, "invalidMatchUrlNoMatches", nextId.getValue()); String msg = myContext.getLocalizer().getMessage(BaseHapiFhirDao.class, "invalidMatchUrlNoMatches", nextId.getValue());
throw new ResourceNotFoundException(msg); throw new ResourceNotFoundException(msg);
@ -373,6 +384,7 @@ public class SearchParamWithInlineReferencesExtractor {
IIdType newId = myIdHelperService.translatePidIdToForcedId(myContext, resourceTypeString, match); IIdType newId = myIdHelperService.translatePidIdToForcedId(myContext, resourceTypeString, match);
ourLog.debug("Replacing inline match URL[{}] with ID[{}}", nextId.getValue(), newId); ourLog.debug("Replacing inline match URL[{}] with ID[{}}", nextId.getValue(), newId);
nextRef.setReference(newId.getValue()); nextRef.setReference(newId.getValue());
} }
} }

View File

@ -38,17 +38,23 @@ import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import javax.annotation.Nonnull;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.StringTokenizer;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.in;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@ -68,6 +74,8 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test
myModelConfig.setRespectVersionsForSearchIncludes(new ModelConfig().isRespectVersionsForSearchIncludes()); myModelConfig.setRespectVersionsForSearchIncludes(new ModelConfig().isRespectVersionsForSearchIncludes());
myFhirCtx.getParserOptions().setStripVersionsFromReferences(true); myFhirCtx.getParserOptions().setStripVersionsFromReferences(true);
myDaoConfig.setTagStorageMode(new DaoConfig().getTagStorageMode()); myDaoConfig.setTagStorageMode(new DaoConfig().getTagStorageMode());
myDaoConfig.setAutoCreatePlaceholderReferenceTargets(new DaoConfig().isAutoCreatePlaceholderReferenceTargets());
myDaoConfig.setPopulateIdentifierInAutoCreatedPlaceholderReferenceTargets(new DaoConfig().isPopulateIdentifierInAutoCreatedPlaceholderReferenceTargets());
} }
@Override @Override
@ -782,6 +790,121 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test
assertEquals(0, myCaptureQueriesListener.getDeleteQueriesForCurrentThread().size()); assertEquals(0, myCaptureQueriesListener.getDeleteQueriesForCurrentThread().size());
} }
@Test
public void testTransactionWithMultipleCreates() {
myDaoConfig.setMassIngestionMode(true);
myDaoConfig.setMatchUrlCacheEnabled(true);
myDaoConfig.setDeleteEnabled(false);
myDaoConfig.setAutoCreatePlaceholderReferenceTargets(true);
myDaoConfig.setPopulateIdentifierInAutoCreatedPlaceholderReferenceTargets(true);
// First pass
myCaptureQueriesListener.clear();
mySystemDao.transaction(mySrd, createTransactionWithCreatesAndOneMatchUrl());
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
// 1 lookup for the match URL only
assertEquals(1, myCaptureQueriesListener.countSelectQueries());
assertEquals(5, myCaptureQueriesListener.countInsertQueries());
assertEquals(1, myCaptureQueriesListener.countUpdateQueries());
assertEquals(0, myCaptureQueriesListener.countDeleteQueries());
runInTransaction(()->assertEquals(4, myResourceTableDao.count()));
// Run it again - This time even the match URL should be cached
myCaptureQueriesListener.clear();
mySystemDao.transaction(mySrd, createTransactionWithCreatesAndOneMatchUrl());
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertEquals(0, myCaptureQueriesListener.countSelectQueries());
assertEquals(4, myCaptureQueriesListener.countInsertQueries());
assertEquals(1, myCaptureQueriesListener.countUpdateQueries());
assertEquals(0, myCaptureQueriesListener.countDeleteQueries());
runInTransaction(()->assertEquals(7, myResourceTableDao.count()));
// Once more for good measure
myCaptureQueriesListener.clear();
mySystemDao.transaction(mySrd, createTransactionWithCreatesAndOneMatchUrl());
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertEquals(0, myCaptureQueriesListener.countSelectQueries());
assertEquals(4, myCaptureQueriesListener.countInsertQueries());
assertEquals(1, myCaptureQueriesListener.countUpdateQueries());
assertEquals(0, myCaptureQueriesListener.countDeleteQueries());
runInTransaction(()->assertEquals(10, myResourceTableDao.count()));
}
@Nonnull
private Bundle createTransactionWithCreatesAndOneMatchUrl() {
BundleBuilder bb = new BundleBuilder(myFhirCtx);
Patient p = new Patient();
p.setId(IdType.newRandomUuid());
p.setActive(true);
bb.addTransactionCreateEntry(p);
Encounter enc = new Encounter();
enc.setSubject(new Reference(p.getId()));
enc.addParticipant().setIndividual(new Reference("Practitioner?identifier=foo|bar"));
bb.addTransactionCreateEntry(enc);
enc = new Encounter();
enc.setSubject(new Reference(p.getId()));
enc.addParticipant().setIndividual(new Reference("Practitioner?identifier=foo|bar"));
bb.addTransactionCreateEntry(enc);
return (Bundle) bb.getBundle();
}
@Test
public void testTransactionWithMultipleCreates_PreExistingMatchUrl() {
myDaoConfig.setMassIngestionMode(true);
myDaoConfig.setMatchUrlCacheEnabled(true);
myDaoConfig.setDeleteEnabled(false);
myDaoConfig.setAutoCreatePlaceholderReferenceTargets(true);
myDaoConfig.setPopulateIdentifierInAutoCreatedPlaceholderReferenceTargets(true);
Practitioner pract = new Practitioner();
pract.addIdentifier().setSystem("foo").setValue("bar");
myPractitionerDao.create(pract);
// First pass
myCaptureQueriesListener.clear();
mySystemDao.transaction(mySrd, createTransactionWithCreatesAndOneMatchUrl());
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
// 1 lookup for the match URL only
assertEquals(1, myCaptureQueriesListener.countSelectQueries());
assertEquals(5, myCaptureQueriesListener.countInsertQueries());
assertEquals(1, myCaptureQueriesListener.countUpdateQueries());
assertEquals(0, myCaptureQueriesListener.countDeleteQueries());
runInTransaction(()->assertEquals(4, myResourceTableDao.count()));
// Run it again - This time even the match URL should be cached
myCaptureQueriesListener.clear();
mySystemDao.transaction(mySrd, createTransactionWithCreatesAndOneMatchUrl());
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertEquals(0, myCaptureQueriesListener.countSelectQueries());
assertEquals(4, myCaptureQueriesListener.countInsertQueries());
assertEquals(1, myCaptureQueriesListener.countUpdateQueries());
assertEquals(0, myCaptureQueriesListener.countDeleteQueries());
runInTransaction(()->assertEquals(7, myResourceTableDao.count()));
// Once more for good measure
myCaptureQueriesListener.clear();
mySystemDao.transaction(mySrd, createTransactionWithCreatesAndOneMatchUrl());
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertEquals(0, myCaptureQueriesListener.countSelectQueries());
assertEquals(4, myCaptureQueriesListener.countInsertQueries());
assertEquals(1, myCaptureQueriesListener.countUpdateQueries());
assertEquals(0, myCaptureQueriesListener.countDeleteQueries());
runInTransaction(()->assertEquals(10, myResourceTableDao.count()));
}
@Test @Test
public void testTransactionWithTwoCreates() { public void testTransactionWithTwoCreates() {
@ -1194,7 +1317,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test
myCaptureQueriesListener.clear(); myCaptureQueriesListener.clear();
mySystemDao.transaction(mySrd, bundleCreator.get()); mySystemDao.transaction(mySrd, bundleCreator.get());
myCaptureQueriesListener.logSelectQueries(); myCaptureQueriesListener.logSelectQueries();
assertEquals(1, myCaptureQueriesListener.countSelectQueries()); assertEquals(0, myCaptureQueriesListener.countSelectQueries());
assertEquals(3, myCaptureQueriesListener.countInsertQueries()); assertEquals(3, myCaptureQueriesListener.countInsertQueries());
assertEquals(0, myCaptureQueriesListener.countDeleteQueries()); assertEquals(0, myCaptureQueriesListener.countDeleteQueries());
@ -1744,7 +1867,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test
// Lookup the two existing IDs to make sure they are legit // Lookup the two existing IDs to make sure they are legit
myCaptureQueriesListener.logSelectQueriesForCurrentThread(); myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertEquals(2, myCaptureQueriesListener.countSelectQueriesForCurrentThread()); assertEquals(0, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
assertEquals(3, myCaptureQueriesListener.countInsertQueriesForCurrentThread()); assertEquals(3, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
assertEquals(0, myCaptureQueriesListener.countUpdateQueriesForCurrentThread()); assertEquals(0, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread()); assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread());

View File

@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.model.entity;
* #L% * #L%
*/ */
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource; import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource;
import ca.uhn.fhir.jpa.model.cross.IResourceLookup; import ca.uhn.fhir.jpa.model.cross.IResourceLookup;
import ca.uhn.fhir.jpa.model.search.ResourceTableRoutingBinder; import ca.uhn.fhir.jpa.model.search.ResourceTableRoutingBinder;
@ -39,6 +40,7 @@ import org.hibernate.search.mapper.pojo.mapping.definition.annotation.Indexed;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.IndexingDependency; import org.hibernate.search.mapper.pojo.mapping.definition.annotation.IndexingDependency;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.ObjectPath; import org.hibernate.search.mapper.pojo.mapping.definition.annotation.ObjectPath;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.PropertyValue; import org.hibernate.search.mapper.pojo.mapping.definition.annotation.PropertyValue;
import org.hl7.fhir.instance.model.api.IIdType;
import javax.persistence.*; import javax.persistence.*;
import java.io.Serializable; import java.io.Serializable;
@ -709,17 +711,32 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas
myForcedId = theForcedId; myForcedId = theForcedId;
} }
@Override @Override
public IdDt getIdDt() { public IdDt getIdDt() {
IdDt retVal = new IdDt();
populateId(retVal);
return retVal;
}
public IIdType getIdType(FhirContext theContext) {
IIdType retVal = theContext.getVersion().newIdType();
populateId(retVal);
return retVal;
}
private void populateId(IIdType retVal) {
if (getTransientForcedId() != null) { if (getTransientForcedId() != null) {
// Avoid a join query if possible // Avoid a join query if possible
return new IdDt(getResourceType() + '/' + getTransientForcedId() + '/' + Constants.PARAM_HISTORY + '/' + getVersion()); retVal.setValue(getResourceType() + '/' + getTransientForcedId() + '/' + Constants.PARAM_HISTORY + '/' + getVersion());
} else if (getForcedId() == null) { } else if (getForcedId() == null) {
Long id = this.getResourceId(); Long id = this.getResourceId();
return new IdDt(getResourceType() + '/' + id + '/' + Constants.PARAM_HISTORY + '/' + getVersion()); retVal.setValue(getResourceType() + '/' + id + '/' + Constants.PARAM_HISTORY + '/' + getVersion());
} else { } else {
String forcedId = getForcedId().getForcedId(); String forcedId = getForcedId().getForcedId();
return new IdDt(getResourceType() + '/' + forcedId + '/' + Constants.PARAM_HISTORY + '/' + getVersion()); retVal.setValue(getResourceType() + '/' + forcedId + '/' + Constants.PARAM_HISTORY + '/' + getVersion());
} }
} }

View File

@ -24,6 +24,7 @@ import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.interceptor.model.RequestPartitionId; import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.model.cross.IResourceLookup; import ca.uhn.fhir.jpa.model.cross.IResourceLookup;
import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
import org.hl7.fhir.instance.model.api.IBaseReference; import org.hl7.fhir.instance.model.api.IBaseReference;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
@ -37,16 +38,17 @@ public interface IResourceLinkResolver {
* <p> * <p>
* This method returns an {@link IResourceLookup} so as to avoid needing to resolve the entire resource. * This method returns an {@link IResourceLookup} so as to avoid needing to resolve the entire resource.
* *
* @param theRequestPartitionId The partition ID of the target resource * @param theRequestPartitionId The partition ID of the target resource
* @param theSearchParam The param that is being indexed * @param theSearchParam The param that is being indexed
* @param theSourcePath The path within the resource where this reference was found * @param theSourcePath The path within the resource where this reference was found
* @param theSourceResourceId The ID of the resource containing the reference to the target being resolved * @param theSourceResourceId The ID of the resource containing the reference to the target being resolved
* @param theTypeString The type of the resource being resolved * @param theTypeString The type of the resource being resolved
* @param theType The resource type of the target * @param theType The resource type of the target
* @param theReference The reference being resolved * @param theReference The reference being resolved
* @param theRequest The incoming request, if any * @param theRequest The incoming request, if any
* @param theTransactionDetails
*/ */
IResourceLookup findTargetResource(RequestPartitionId theRequestPartitionId, RuntimeSearchParam theSearchParam, String theSourcePath, IIdType theSourceResourceId, String theTypeString, Class<? extends IBaseResource> theType, IBaseReference theReference, RequestDetails theRequest); IResourceLookup findTargetResource(RequestPartitionId theRequestPartitionId, RuntimeSearchParam theSearchParam, String theSourcePath, IIdType theSourceResourceId, String theTypeString, Class<? extends IBaseResource> theType, IBaseReference theReference, RequestDetails theRequest, TransactionDetails theTransactionDetails);
void validateTypeOrThrowException(Class<? extends IBaseResource> theType); void validateTypeOrThrowException(Class<? extends IBaseResource> theType);

View File

@ -377,7 +377,7 @@ public class SearchParamExtractorService {
* if the reference is invalid * if the reference is invalid
*/ */
myResourceLinkResolver.validateTypeOrThrowException(type); myResourceLinkResolver.validateTypeOrThrowException(type);
resourceLink = resolveTargetAndCreateResourceLinkOrReturnNull(theRequestPartitionId, theEntity, transactionDate, theRuntimeSearchParam, path, thePathAndRef, nextId, typeString, type, nextReference, theRequest); resourceLink = resolveTargetAndCreateResourceLinkOrReturnNull(theRequestPartitionId, theEntity, transactionDate, theRuntimeSearchParam, path, thePathAndRef, nextId, typeString, type, nextReference, theRequest, theTransactionDetails);
if (resourceLink == null) { if (resourceLink == null) {
return; return;
} else { } else {
@ -402,7 +402,17 @@ public class SearchParamExtractorService {
theParams.myLinks.add(resourceLink); theParams.myLinks.add(resourceLink);
} }
private ResourceLink resolveTargetAndCreateResourceLinkOrReturnNull(@Nonnull RequestPartitionId theRequestPartitionId, ResourceTable theEntity, Date theUpdateTime, RuntimeSearchParam nextSpDef, String theNextPathsUnsplit, PathAndRef nextPathAndRef, IIdType theNextId, String theTypeString, Class<? extends IBaseResource> theType, IBaseReference theReference, RequestDetails theRequest) { private ResourceLink resolveTargetAndCreateResourceLinkOrReturnNull(@Nonnull RequestPartitionId theRequestPartitionId, ResourceTable theEntity, Date theUpdateTime, RuntimeSearchParam nextSpDef, String theNextPathsUnsplit, PathAndRef nextPathAndRef, IIdType theNextId, String theTypeString, Class<? extends IBaseResource> theType, IBaseReference theReference, RequestDetails theRequest, TransactionDetails theTransactionDetails) {
ResourcePersistentId resolvedResourceId = theTransactionDetails.getResolvedResourceId(theNextId);
if (resolvedResourceId != null) {
String targetResourceType = theNextId.getResourceType();
Long targetResourcePid = resolvedResourceId.getIdAsLong();
String targetResourceIdPart = theNextId.getIdPart();
Long targetVersion = theNextId.getVersionIdPartAsLong();
return ResourceLink.forLocalReference(nextPathAndRef.getPath(), theEntity, targetResourceType, targetResourcePid, targetResourceIdPart, theUpdateTime, targetVersion);
}
/* /*
* We keep a cache of resolved target resources. This is good since for some resource types, there * We keep a cache of resolved target resources. This is good since for some resource types, there
* are multiple search parameters that map to the same element path within a resource (e.g. * are multiple search parameters that map to the same element path within a resource (e.g.
@ -415,7 +425,7 @@ public class SearchParamExtractorService {
targetRequestPartitionId = RequestPartitionId.allPartitions(); targetRequestPartitionId = RequestPartitionId.allPartitions();
} }
IResourceLookup targetResource = myResourceLinkResolver.findTargetResource(targetRequestPartitionId, nextSpDef, theNextPathsUnsplit, theNextId, theTypeString, theType, theReference, theRequest); IResourceLookup targetResource = myResourceLinkResolver.findTargetResource(targetRequestPartitionId, nextSpDef, theNextPathsUnsplit, theNextId, theTypeString, theType, theReference, theRequest, theTransactionDetails);
if (targetResource == null) { if (targetResource == null) {
return null; return null;

View File

@ -117,7 +117,7 @@ public class TransactionDetails {
*/ */
@Nullable @Nullable
public ResourcePersistentId getResolvedResourceId(IIdType theId) { public ResourcePersistentId getResolvedResourceId(IIdType theId) {
String idValue = theId.toVersionless().getValue(); String idValue = theId.toUnqualifiedVersionless().getValue();
return myResolvedResourceIds.get(idValue); return myResolvedResourceIds.get(idValue);
} }