issue-2901 added db access method for existence and setting the version to 1 if createplaceholders is true and object does not exist in db

This commit is contained in:
leif stawnyczy 2021-08-31 10:12:40 -04:00
parent e7c6ce920d
commit a38b791141
6 changed files with 98 additions and 33 deletions

View File

@ -161,6 +161,15 @@ public interface IFhirResourceDao<T extends IBaseResource> extends IDao {
*/ */
T read(IIdType theId); T read(IIdType theId);
/**
* Helper method to determine if some resources exist in the DB (without throwing).
* Returns a set that contains the IIdType for every resource found.
* If it's not found, it won't be included in the set.
* @param theIds - list of IIdType ids (for the same resource)
* @return
*/
Set<IIdType> hasResources(Collection<IIdType> theIds);
/** /**
* Read a resource by its internal PID * Read a resource by its internal PID
*/ */

View File

@ -37,6 +37,7 @@ import ca.uhn.fhir.jpa.api.model.LazyDaoMethodOutcome;
import ca.uhn.fhir.jpa.dao.index.IdHelperService; import ca.uhn.fhir.jpa.dao.index.IdHelperService;
import ca.uhn.fhir.jpa.dao.tx.HapiTransactionService; import ca.uhn.fhir.jpa.dao.tx.HapiTransactionService;
import ca.uhn.fhir.jpa.delete.DeleteConflictService; import ca.uhn.fhir.jpa.delete.DeleteConflictService;
import ca.uhn.fhir.jpa.model.cross.IResourceLookup;
import ca.uhn.fhir.jpa.model.entity.BaseHasResource; import ca.uhn.fhir.jpa.model.entity.BaseHasResource;
import ca.uhn.fhir.jpa.model.entity.BaseTag; import ca.uhn.fhir.jpa.model.entity.BaseTag;
import ca.uhn.fhir.jpa.model.entity.ForcedId; import ca.uhn.fhir.jpa.model.entity.ForcedId;
@ -1156,6 +1157,22 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
return myTransactionService.execute(theRequest, transactionDetails, tx -> doRead(theId, theRequest, theDeletedOk)); return myTransactionService.execute(theRequest, transactionDetails, tx -> doRead(theId, theRequest, theDeletedOk));
} }
@Override
public Set<IIdType> hasResources(Collection<IIdType> theIds) {
List<String> idPortions = theIds.stream().map(t -> t.getIdPart()).collect(Collectors.toList());
Collection<Object[]> matches = myForcedIdDao.findResourcesByForcedId(getResourceName(),
idPortions);
HashSet<IIdType> collected = new HashSet<>();
for (Object[] match : matches) {
String resourceType = (String) match[0];
String forcedId = (String) match[1];
collected.add(new IdDt(resourceType, forcedId));
}
return collected;
}
public T doRead(IIdType theId, RequestDetails theRequest, boolean theDeletedOk) { public T doRead(IIdType theId, RequestDetails theRequest, boolean theDeletedOk) {
assert TransactionSynchronizationManager.isActualTransactionActive(); assert TransactionSynchronizationManager.isActualTransactionActive();

View File

@ -7,6 +7,7 @@ import ca.uhn.fhir.jpa.util.ResourceCountCache;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
import ca.uhn.fhir.util.StopWatch; import ca.uhn.fhir.util.StopWatch;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;

View File

@ -650,6 +650,7 @@ public abstract class BaseTransactionProcessor {
catch (Exception ex) { catch (Exception ex) {
//FIXME - remove //FIXME - remove
ourLog.info("FindME"); ourLog.info("FindME");
ourLog.info(ex.getLocalizedMessage()); ourLog.info(ex.getLocalizedMessage());
ex.printStackTrace(); ex.printStackTrace();
entriesToProcess = new HashMap<>(); entriesToProcess = new HashMap<>();
@ -1306,28 +1307,61 @@ public abstract class BaseTransactionProcessor {
} else if (nextId.getValue().startsWith("urn:")) { } else if (nextId.getValue().startsWith("urn:")) {
throw new InvalidRequestException("Unable to satisfy placeholder ID " + nextId.getValue() + " found in element named '" + nextRef.getName() + "' within resource of type: " + nextResource.getIdElement().getResourceType()); throw new InvalidRequestException("Unable to satisfy placeholder ID " + nextId.getValue() + " found in element named '" + nextRef.getName() + "' within resource of type: " + nextResource.getIdElement().getResourceType());
} else { } else {
if (theReferencesToAutoVersion.contains(resourceReference)) { // resource type -> set of Ids
// we'll populate this with only those resource/ids of
// resource references that:
// a) do not exist in the idToPersistedOutcome
// (so not in top level of bundle)
// b) do not exist in DB
// (so newly created resources)
//
// we only do this if autocreateplaceholders is on
HashMap<String, Set<IIdType>> resourceTypeToListOfIds = new HashMap<>();
if (myDaoConfig.isAutoCreatePlaceholderReferenceTargets()) {
for (IBaseReference ref : theReferencesToAutoVersion) {
IIdType id = ref.getReferenceElement();
// if we don't have this in our idToPersistedOutcome
// and we have createplaceholderreferences on
// we will have to check if these objects exist in the DB
if (!theIdToPersistedOutcome.containsKey(id)) {
if (!resourceTypeToListOfIds.containsKey(id.getResourceType())) {
resourceTypeToListOfIds.put(id.getResourceType(), new HashSet<>());
}
//TODO - if we get here and there's still no resourceTypeToListOfIds.get(id.getResourceType()).add(id);
// value in theIdToPersistedOutcome map
// we should throw an invalid request exception
DaoMethodOutcome outcome = theIdToPersistedOutcome.get(nextId);
if (outcome == null) {
IFhirResourceDao dao = myDaoRegistry.getResourceDao(resourceReference.getReferenceElement().getResourceType());
IBaseResource baseResource;
try {
// DB hit
baseResource = dao.read(resourceReference.getReferenceElement());
} catch (ResourceNotFoundException ex) {
// does not exist - add the history/1
String val = resourceReference.getReferenceElement().getValue();
resourceReference.getReferenceElement().setValue(val + "/_history/1");
} }
} }
if (outcome != null && !outcome.isNop() && !Boolean.TRUE.equals(outcome.getCreated())) { for (String resourceType : resourceTypeToListOfIds.keySet()) {
IFhirResourceDao dao = myDaoRegistry.getResourceDao(resourceType);
Set<IIdType> idSet = resourceTypeToListOfIds.get(resourceType);
// DB hit :(
Set<IIdType> existing = dao.hasResources(idSet);
// we remove all the ids that are found, leaving
// only the ids of those not in the DB at all
idSet.removeAll(existing);
}
}
if (theReferencesToAutoVersion.contains(resourceReference)) {
DaoMethodOutcome outcome = theIdToPersistedOutcome.get(nextId);
if (outcome == null && myDaoConfig.isAutoCreatePlaceholderReferenceTargets()) {
// null outcome means it's a resource reference that was not at top of bundle
IIdType id = resourceReference.getReferenceElement();
String resourceType = id.getResourceType();
// if it exists in resourceTypeToListOfIds
// it's not in the DB (new resource)
Set<IIdType> ids = resourceTypeToListOfIds.get(resourceType);
if (!ids.contains(id)) {
// doesn't exist in the DB
// which means the history necessarily is 1 (first one)
resourceReference.getReferenceElement().setValue(id.getValue() + "/_history/1");
}
}
else if (outcome != null && !outcome.isNop() && !Boolean.TRUE.equals(outcome.getCreated())) {
addRollbackReferenceRestore(theTransactionDetails, resourceReference); addRollbackReferenceRestore(theTransactionDetails, resourceReference);
resourceReference.setReference(nextId.getValue()); resourceReference.setReference(nextId.getValue());
resourceReference.setResource(null); resourceReference.setResource(null);

View File

@ -111,6 +111,13 @@ public interface IForcedIdDao extends JpaRepository<ForcedId, Long> {
"WHERE f.myResourceType = :resource_type AND f.myForcedId IN ( :forced_id )") "WHERE f.myResourceType = :resource_type AND f.myForcedId IN ( :forced_id )")
Collection<Object[]> findAndResolveByForcedIdWithNoType(@Param("resource_type") String theResourceType, @Param("forced_id") Collection<String> theForcedIds); Collection<Object[]> findAndResolveByForcedIdWithNoType(@Param("resource_type") String theResourceType, @Param("forced_id") Collection<String> theForcedIds);
@Query("" +
"SELECT " +
" f.myResourceType, f.myForcedId " +
"FROM ForcedId f " +
"WHERE f.myResourceType = :resource_type AND f.myForcedId IN ( :forced_id )")
Collection<Object[]> findResourcesByForcedId(@Param("resource_type") String theResourceType, @Param("forced_id") Collection<String> theForcedIds);
/** /**
* This method returns a Collection where each row is an element in the collection. Each element in the collection * This method returns a Collection where each row is an element in the collection. Each element in the collection
* is an object array, where the order matters (the array represents columns returned by the query). Be careful if you change this query in any way. * is an object array, where the order matters (the array represents columns returned by the query). Be careful if you change this query in any way.

View File

@ -301,8 +301,8 @@ public class FhirResourceDaoR4VersionedReferenceTest extends BaseJpaR4Test {
@Test @Test
public void testInsertVersionedReferenceAtPathUsingTransaction() { public void testInsertVersionedReferenceAtPathUsingTransaction() {
// myFhirCtx.getParserOptions().setStripVersionsFromReferences(false); myFhirCtx.getParserOptions().setStripVersionsFromReferences(false);
// myDaoConfig.setAutoCreatePlaceholderReferenceTargets(true); myDaoConfig.setAutoCreatePlaceholderReferenceTargets(true);
myModelConfig.setAutoVersionReferenceAtPaths("Observation.subject"); myModelConfig.setAutoVersionReferenceAtPaths("Observation.subject");
Patient p = new Patient(); Patient p = new Patient();
@ -893,24 +893,21 @@ public class FhirResourceDaoR4VersionedReferenceTest extends BaseJpaR4Test {
BundleBuilder builder = new BundleBuilder(myFhirCtx); BundleBuilder builder = new BundleBuilder(myFhirCtx);
builder.addTransactionUpdateEntry(obs); builder.addTransactionUpdateEntry(obs);
Bundle submitted = (Bundle)builder.getBundle();
//1 make sure this test throws the InvalidRequestException (make separate test for this) //1 make sure this test throws the InvalidRequestException (make separate test for this)
//2 add a test for patient created before bundle and then process observation with reference to patient (null check for outcome) //2 add a test for patient created before bundle and then process observation with reference to patient (null check for outcome)
//3 //3
// Assertions.assertThrows() Bundle returnedTr = mySystemDao.transaction(new SystemRequestDetails(), submitted);
try {
Bundle returnedTr = mySystemDao.transaction(new SystemRequestDetails(), (Bundle) builder.getBundle());
System.out.println("returned " + returnedTr.getEntry().size()); Assertions.assertTrue(returnedTr != null);
Observation obRet = myObservationDao.read(obs.getIdElement());
System.out.println("HELLO " + obRet.getId()); // some verification
Patient returned = myPatientDao.read(patientRef.getReferenceElement()); Observation obRet = myObservationDao.read(obs.getIdElement());
Assertions.assertTrue(returned != null); Assertions.assertTrue(obRet != null);
} Patient returned = myPatientDao.read(patientRef.getReferenceElement());
catch (Exception ex) { Assertions.assertTrue(returned != null);
System.out.println("TEST " + ex.getLocalizedMessage());
Assertions.assertTrue(ex == null);
}
} }