Allow transaction with conditional create and patch in same bundle (#4735)
* Allow transaction with conditional create and patch in same bundle * Add changelog * Cleanup
This commit is contained in:
parent
cc9d1b992d
commit
869b6c306a
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
type: add
|
||||||
|
issue: 4735
|
||||||
|
title: "It is now possible to perform a conditional create and a conditional patch on the
|
||||||
|
same resource (i.e. the same conditional URL) within a FHIR Transaction bundle."
|
|
@ -50,6 +50,9 @@ import org.hl7.fhir.r4.model.Reference;
|
||||||
import org.hl7.fhir.r4.model.ServiceRequest;
|
import org.hl7.fhir.r4.model.ServiceRequest;
|
||||||
import org.hl7.fhir.r4.model.StringType;
|
import org.hl7.fhir.r4.model.StringType;
|
||||||
import org.hl7.fhir.r4.model.ValueSet;
|
import org.hl7.fhir.r4.model.ValueSet;
|
||||||
|
import org.hl7.fhir.r4.model.BooleanType;
|
||||||
|
import org.hl7.fhir.r4.model.CodeType;
|
||||||
|
import org.hl7.fhir.r4.model.Parameters;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.MethodOrderer;
|
import org.junit.jupiter.api.MethodOrderer;
|
||||||
|
@ -2765,8 +2768,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test
|
||||||
* as well as a large number of updates (PUT). This means that a lot of URLs and resources
|
* as well as a large number of updates (PUT). This means that a lot of URLs and resources
|
||||||
* need to be resolved (ie SQL SELECT) in order to proceed with the transaction. Prior
|
* need to be resolved (ie SQL SELECT) in order to proceed with the transaction. Prior
|
||||||
* to the optimization that introduced this test, we had 140 SELECTs, now it's 17.
|
* to the optimization that introduced this test, we had 140 SELECTs, now it's 17.
|
||||||
*/
|
*
|
||||||
/**
|
|
||||||
* See the class javadoc before changing the counts in this test!
|
* See the class javadoc before changing the counts in this test!
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
|
@ -2795,6 +2797,48 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See the class javadoc before changing the counts in this test!
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testTransactionWithConditionalCreateAndConditionalPatchOnSameUrl() {
|
||||||
|
// Setup
|
||||||
|
BundleBuilder bb = new BundleBuilder(myFhirContext);
|
||||||
|
Patient patient = new Patient();
|
||||||
|
patient.setActive(false);
|
||||||
|
patient.addIdentifier().setSystem("http://system").setValue("value");
|
||||||
|
bb.addTransactionCreateEntry(patient).conditional("Patient?identifier=http://system|value");
|
||||||
|
|
||||||
|
Parameters patch = new Parameters();
|
||||||
|
Parameters.ParametersParameterComponent op = patch.addParameter().setName("operation");
|
||||||
|
op.addPart().setName("type").setValue(new CodeType("replace"));
|
||||||
|
op.addPart().setName("path").setValue(new CodeType("Patient.active"));
|
||||||
|
op.addPart().setName("value").setValue(new BooleanType(true));
|
||||||
|
bb.addTransactionFhirPatchEntry(patch).conditional("Patient?identifier=http://system|value");
|
||||||
|
|
||||||
|
Bundle input = bb.getBundleTyped();
|
||||||
|
|
||||||
|
// Test
|
||||||
|
myCaptureQueriesListener.clear();
|
||||||
|
Bundle output = mySystemDao.transaction(mySrd, input);
|
||||||
|
|
||||||
|
// Verify
|
||||||
|
assertEquals(3, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
|
||||||
|
assertEquals(6, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
|
||||||
|
assertEquals(1, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
|
||||||
|
assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread());
|
||||||
|
assertEquals(1, myCaptureQueriesListener.countCommits());
|
||||||
|
assertEquals(0, myCaptureQueriesListener.countRollbacks());
|
||||||
|
|
||||||
|
assertEquals(input.getEntry().size(), output.getEntry().size());
|
||||||
|
|
||||||
|
runInTransaction(() -> {
|
||||||
|
assertEquals(1, myResourceTableDao.count());
|
||||||
|
assertEquals(1, myResourceHistoryTableDao.count());
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* See the class javadoc before changing the counts in this test!
|
* See the class javadoc before changing the counts in this test!
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -2,20 +2,25 @@ package ca.uhn.fhir.jpa.dao.r5;
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
|
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
|
||||||
import ca.uhn.fhir.util.BundleBuilder;
|
import ca.uhn.fhir.util.BundleBuilder;
|
||||||
|
import org.hl7.fhir.r5.model.BooleanType;
|
||||||
import org.hl7.fhir.r5.model.Bundle;
|
import org.hl7.fhir.r5.model.Bundle;
|
||||||
|
import org.hl7.fhir.r5.model.CodeType;
|
||||||
import org.hl7.fhir.r5.model.IdType;
|
import org.hl7.fhir.r5.model.IdType;
|
||||||
import org.hl7.fhir.r5.model.Observation;
|
import org.hl7.fhir.r5.model.Observation;
|
||||||
|
import org.hl7.fhir.r5.model.Parameters;
|
||||||
import org.hl7.fhir.r5.model.Patient;
|
import org.hl7.fhir.r5.model.Patient;
|
||||||
import org.hl7.fhir.r5.model.Quantity;
|
import org.hl7.fhir.r5.model.Quantity;
|
||||||
import org.hl7.fhir.r5.model.Reference;
|
import org.hl7.fhir.r5.model.Reference;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.params.ParameterizedTest;
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
import org.junit.jupiter.params.provider.CsvSource;
|
import org.junit.jupiter.params.provider.CsvSource;
|
||||||
|
import org.junit.jupiter.params.provider.ValueSource;
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import static org.apache.commons.lang3.StringUtils.countMatches;
|
import static org.apache.commons.lang3.StringUtils.countMatches;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
public class FhirSystemDaoTransactionR5Test extends BaseJpaR5Test {
|
public class FhirSystemDaoTransactionR5Test extends BaseJpaR5Test {
|
||||||
|
|
||||||
|
@ -381,6 +386,56 @@ public class FhirSystemDaoTransactionR5Test extends BaseJpaR5Test {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A FHIR transaction bundle containing a conditional create as well as a patch that point to the same resource
|
||||||
|
*/
|
||||||
|
@ParameterizedTest
|
||||||
|
@ValueSource(booleans = {true, false})
|
||||||
|
public void testConditionalCreateAndConditionalPatchOnSameResource(boolean thePreviouslyExisting) {
|
||||||
|
|
||||||
|
if (thePreviouslyExisting) {
|
||||||
|
Patient patient = new Patient();
|
||||||
|
patient.setActive(false);
|
||||||
|
patient.addIdentifier().setSystem("http://system").setValue("value");
|
||||||
|
myPatientDao.create(patient, mySrd);
|
||||||
|
}
|
||||||
|
|
||||||
|
BundleBuilder bb = new BundleBuilder(myFhirContext);
|
||||||
|
Patient patient = new Patient();
|
||||||
|
patient.setActive(false);
|
||||||
|
patient.addIdentifier().setSystem("http://system").setValue("value");
|
||||||
|
bb.addTransactionCreateEntry(patient).conditional("Patient?identifier=http://system|value");
|
||||||
|
|
||||||
|
Parameters patch = new Parameters();
|
||||||
|
Parameters.ParametersParameterComponent op = patch.addParameter().setName("operation");
|
||||||
|
op.addPart().setName("type").setValue(new CodeType("replace"));
|
||||||
|
op.addPart().setName("path").setValue(new CodeType("Patient.active"));
|
||||||
|
op.addPart().setName("value").setValue(new BooleanType(true));
|
||||||
|
bb.addTransactionFhirPatchEntry(patch).conditional("Patient?identifier=http://system|value");
|
||||||
|
|
||||||
|
Bundle input = bb.getBundleTyped();
|
||||||
|
ourLog.info("Bundle: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(input));
|
||||||
|
|
||||||
|
// Test
|
||||||
|
Bundle output = mySystemDao.transaction(mySrd, input);
|
||||||
|
|
||||||
|
// Verify
|
||||||
|
IdType createId = new IdType(output.getEntry().get(0).getResponse().getLocation());
|
||||||
|
IdType patchId = new IdType(output.getEntry().get(1).getResponse().getLocation());
|
||||||
|
assertEquals("1", createId.getVersionIdPart());
|
||||||
|
assertEquals("2", patchId.getVersionIdPart());
|
||||||
|
assertEquals(createId.getIdPart(), patchId.getIdPart());
|
||||||
|
|
||||||
|
Patient createdPatient = myPatientDao.read(patchId, mySrd);
|
||||||
|
assertEquals("http://system", createdPatient.getIdentifierFirstRep().getSystem());
|
||||||
|
assertTrue(createdPatient.getActive());
|
||||||
|
|
||||||
|
assertEquals(2, output.getEntry().size());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -115,6 +115,12 @@ public abstract class BaseStorageResourceDao<T extends IBaseResource> extends Ba
|
||||||
}
|
}
|
||||||
|
|
||||||
IBaseResource resourceToUpdate = getStorageResourceParser().toResource(entityToUpdate, false);
|
IBaseResource resourceToUpdate = getStorageResourceParser().toResource(entityToUpdate, false);
|
||||||
|
if (resourceToUpdate == null) {
|
||||||
|
// If this is null, we are presumably in a FHIR transaction bundle with both a create and a patch on the same
|
||||||
|
// resource. This is weird but not impossible.
|
||||||
|
resourceToUpdate = theTransactionDetails.getResolvedResource(resourceId);
|
||||||
|
}
|
||||||
|
|
||||||
IBaseResource destination;
|
IBaseResource destination;
|
||||||
switch (thePatchType) {
|
switch (thePatchType) {
|
||||||
case JSON_PATCH:
|
case JSON_PATCH:
|
||||||
|
|
Loading…
Reference in New Issue