Improve error message on unique constraint violation (#2182)

* Improve error message on unique constraint violation

* Add changelog

* Test fixes

* Test fix
This commit is contained in:
James Agnew 2020-11-19 21:20:59 -05:00 committed by GitHub
parent 27cff8153b
commit 8b65db0c98
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 37 additions and 8 deletions

View File

@ -80,7 +80,7 @@ ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.invalidMatchUrlMultipleMatches=Invalid match
ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.transactionOperationWithMultipleMatchFailure=Failed to {0} resource with match URL "{1}" because this search matched {2} resources ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.transactionOperationWithMultipleMatchFailure=Failed to {0} resource with match URL "{1}" because this search matched {2} resources
ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.transactionOperationFailedNoId=Failed to {0} resource in transaction because no ID was provided ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.transactionOperationFailedNoId=Failed to {0} resource in transaction because no ID was provided
ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.transactionOperationFailedUnknownId=Failed to {0} resource in transaction because no resource could be found with ID {1} ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.transactionOperationFailedUnknownId=Failed to {0} resource in transaction because no resource could be found with ID {1}
ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.uniqueIndexConflictFailure=Can not create resource of type {0} as it would create a duplicate index matching query: {1} (existing index belongs to {2}) ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.uniqueIndexConflictFailure=Can not create resource of type {0} as it would create a duplicate unique index matching query: {1} (existing index belongs to {2}, new unique index created by {3})
ca.uhn.fhir.jpa.dao.BaseHapiFhirSystemDao.transactionContainsMultipleWithDuplicateId=Transaction bundle contains multiple resources with ID: {0} ca.uhn.fhir.jpa.dao.BaseHapiFhirSystemDao.transactionContainsMultipleWithDuplicateId=Transaction bundle contains multiple resources with ID: {0}
ca.uhn.fhir.jpa.dao.BaseHapiFhirSystemDao.transactionEntryHasInvalidVerb=Transaction bundle entry has missing or invalid HTTP Verb specified in Bundle.entry({1}).request.method. Found value: "{0}" ca.uhn.fhir.jpa.dao.BaseHapiFhirSystemDao.transactionEntryHasInvalidVerb=Transaction bundle entry has missing or invalid HTTP Verb specified in Bundle.entry({1}).request.method. Found value: "{0}"

View File

@ -0,0 +1,5 @@
---
type: add
issue: 2182
title: "When a unique index SearchParametr violation is blocked, the error message will now include the ID of the relevant
SearchParameter, in order to make troubleshooting easier."

View File

@ -206,7 +206,7 @@ public class SearchParamWithInlineReferencesExtractor {
for (String nextQueryString : queryStringsToPopulate) { for (String nextQueryString : queryStringsToPopulate) {
if (isNotBlank(nextQueryString)) { if (isNotBlank(nextQueryString)) {
ourLog.trace("Adding composite unique SP: {}", nextQueryString); ourLog.trace("Adding composite unique SP: {}", nextQueryString);
theParams.myCompositeStringUniques.add(new ResourceIndexedCompositeStringUnique(theEntity, nextQueryString)); theParams.myCompositeStringUniques.add(new ResourceIndexedCompositeStringUnique(theEntity, nextQueryString, next.getId()));
} }
} }
} }
@ -289,7 +289,13 @@ public class SearchParamWithInlineReferencesExtractor {
if (myDaoConfig.isUniqueIndexesCheckedBeforeSave()) { if (myDaoConfig.isUniqueIndexesCheckedBeforeSave()) {
ResourceIndexedCompositeStringUnique existing = myResourceIndexedCompositeStringUniqueDao.findByQueryString(next.getIndexString()); ResourceIndexedCompositeStringUnique existing = myResourceIndexedCompositeStringUniqueDao.findByQueryString(next.getIndexString());
if (existing != null) { if (existing != null) {
String msg = myContext.getLocalizer().getMessage(BaseHapiFhirDao.class, "uniqueIndexConflictFailure", theEntity.getResourceType(), next.getIndexString(), existing.getResource().getIdDt().toUnqualifiedVersionless().getValue());
String searchParameterId = "(unknown)";
if (next.getSearchParameterId() != null) {
searchParameterId = next.getSearchParameterId().toUnqualifiedVersionless().getValue();
}
String msg = myContext.getLocalizer().getMessage(BaseHapiFhirDao.class, "uniqueIndexConflictFailure", theEntity.getResourceType(), next.getIndexString(), existing.getResource().getIdDt().toUnqualifiedVersionless().getValue(), searchParameterId);
throw new PreconditionFailedException(msg); throw new PreconditionFailedException(msg);
} }
} }

View File

@ -139,7 +139,7 @@ public class FhirResourceDaoR4ConcurrentWriteTest extends BaseJpaR4Test {
myPatientDao.create(p); myPatientDao.create(p);
} catch (PreconditionFailedException e) { } catch (PreconditionFailedException e) {
// expected - This is as a result of the unique SP // expected - This is as a result of the unique SP
assertThat(e.getMessage(), containsString("duplicate index matching query: Patient?gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale")); assertThat(e.getMessage(), containsString("duplicate unique index matching query: Patient?gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale"));
} catch (ResourceVersionConflictException e) { } catch (ResourceVersionConflictException e) {
// expected - This is as a result of the unique SP // expected - This is as a result of the unique SP
assertThat(e.getMessage(), containsString("would have resulted in a duplicate value for a unique index")); assertThat(e.getMessage(), containsString("would have resulted in a duplicate value for a unique index"));

View File

@ -803,7 +803,7 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
myPatientDao.create(pt1).getId().toUnqualifiedVersionless(); myPatientDao.create(pt1).getId().toUnqualifiedVersionless();
fail(); fail();
} catch (PreconditionFailedException e) { } catch (PreconditionFailedException e) {
assertEquals("Can not create resource of type Patient as it would create a duplicate index matching query: Patient?birthdate=2011-01-01&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale (existing index belongs to Patient/" + id1.getIdPart() + ")", e.getMessage()); assertEquals("Can not create resource of type Patient as it would create a duplicate unique index matching query: Patient?birthdate=2011-01-01&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale (existing index belongs to Patient/" + id1.getIdPart() + ", new unique index created by SearchParameter/patient-gender-birthdate)", e.getMessage());
} }
} }
@ -1510,7 +1510,7 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
myPatientDao.create(pt1).getId().toUnqualifiedVersionless(); myPatientDao.create(pt1).getId().toUnqualifiedVersionless();
fail(); fail();
} catch (PreconditionFailedException e) { } catch (PreconditionFailedException e) {
// good assertThat(e.getMessage(), containsString("new unique index created by SearchParameter/patient-gender-birthdate"));
} }
Patient pt2 = new Patient(); Patient pt2 = new Patient();
@ -1525,7 +1525,7 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test {
myPatientDao.update(pt2); myPatientDao.update(pt2);
fail(); fail();
} catch (PreconditionFailedException e) { } catch (PreconditionFailedException e) {
// good assertThat(e.getMessage(), containsString("new unique index created by SearchParameter/patient-gender-birthdate"));
} }
} }

View File

@ -26,6 +26,7 @@ import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle; import org.apache.commons.lang3.builder.ToStringStyle;
import org.hl7.fhir.instance.model.api.IIdType;
import javax.persistence.*; import javax.persistence.*;
@ -59,6 +60,8 @@ public class ResourceIndexedCompositeStringUnique extends BasePartitionable impl
@SuppressWarnings("unused") @SuppressWarnings("unused")
@Column(name = PartitionablePartitionId.PARTITION_ID, insertable = false, updatable = false, nullable = true) @Column(name = PartitionablePartitionId.PARTITION_ID, insertable = false, updatable = false, nullable = true)
private Integer myPartitionIdValue; private Integer myPartitionIdValue;
@Transient
private IIdType mySearchParameterId;
/** /**
* Constructor * Constructor
@ -70,10 +73,11 @@ public class ResourceIndexedCompositeStringUnique extends BasePartitionable impl
/** /**
* Constructor * Constructor
*/ */
public ResourceIndexedCompositeStringUnique(ResourceTable theResource, String theIndexString) { public ResourceIndexedCompositeStringUnique(ResourceTable theResource, String theIndexString, IIdType theSearchParameterId) {
setResource(theResource); setResource(theResource);
setIndexString(theIndexString); setIndexString(theIndexString);
setPartitionId(theResource.getPartitionId()); setPartitionId(theResource.getPartitionId());
setSearchParameterId(theSearchParameterId);
} }
@Override @Override
@ -131,4 +135,18 @@ public class ResourceIndexedCompositeStringUnique extends BasePartitionable impl
.append("partition", getPartitionId()) .append("partition", getPartitionId())
.toString(); .toString();
} }
/**
* Note: This field is not persisted, so it will only be populated for new indexes
*/
public void setSearchParameterId(IIdType theSearchParameterId) {
mySearchParameterId = theSearchParameterId;
}
/**
* Note: This field is not persisted, so it will only be populated for new indexes
*/
public IIdType getSearchParameterId() {
return mySearchParameterId;
}
} }