Add partition selector to id parameter (#2208)

* Add partition selector to id parameter

* Add changelog
This commit is contained in:
James Agnew 2020-12-02 14:10:37 -05:00 committed by GitHub
parent 30b6244988
commit a3ae6c3813
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 238 additions and 4 deletions

View File

@ -0,0 +1,6 @@
---
type: bug
issue: 2208
title: "When performing a JPA server search on a partitioned server, searches with only the `_id` parameter and no other
parameters did not include the partition selector in the generated SQL, resulting in leakage across partitions. Thanks to
GitHub user @jtheory for reporting!"

View File

@ -739,7 +739,7 @@ public class QueryStack {
codePredicates.add(singleCode); codePredicates.add(singleCode);
} }
return join.combineWithRequestPartitionIdPredicate(theRequestPartitionId, ComboCondition.or(codePredicates.toArray(new Condition[0]))); return join.combineWithRequestPartitionIdPredicate(theRequestPartitionId, toOrPredicate(codePredicates));
} }
public Condition createPredicateTag(@Nullable DbColumn theSourceJoinColumn, List<List<IQueryParameterType>> theList, String theParamName, RequestPartitionId theRequestPartitionId) { public Condition createPredicateTag(@Nullable DbColumn theSourceJoinColumn, List<List<IQueryParameterType>> theList, String theParamName, RequestPartitionId theRequestPartitionId) {

View File

@ -115,12 +115,15 @@ public class ResourceIdPredicateBuilder extends BasePredicateBuilder {
List<Long> resourceIds = ResourcePersistentId.toLongList(allOrPids); List<Long> resourceIds = ResourcePersistentId.toLongList(allOrPids);
if (theSourceJoinColumn == null) { if (theSourceJoinColumn == null) {
BaseJoiningPredicateBuilder queryRootTable = super.getOrCreateQueryRootTable(); BaseJoiningPredicateBuilder queryRootTable = super.getOrCreateQueryRootTable();
Condition predicate;
switch (operation) { switch (operation) {
default: default:
case eq: case eq:
return queryRootTable.createPredicateResourceIds(false, resourceIds); predicate = queryRootTable.createPredicateResourceIds(false, resourceIds);
return queryRootTable.combineWithRequestPartitionIdPredicate(theRequestPartitionId, predicate);
case ne: case ne:
return queryRootTable.createPredicateResourceIds(true, resourceIds); predicate = queryRootTable.createPredicateResourceIds(true, resourceIds);
return queryRootTable.combineWithRequestPartitionIdPredicate(theRequestPartitionId, predicate);
} }
} else { } else {
return QueryStack.toEqualToOrInPredicate(theSourceJoinColumn, generatePlaceholders(resourceIds), operation == SearchFilterParser.CompareOperation.ne); return QueryStack.toEqualToOrInPredicate(theSourceJoinColumn, generatePlaceholders(resourceIds), operation == SearchFilterParser.CompareOperation.ne);

View File

@ -1136,6 +1136,231 @@ public class PartitioningSqlR4Test extends BaseJpaR4SystemTest {
} }
} }
@Test
public void testSearch_IdParamOnly_PidId_SpecificPartition() {
IIdType patientIdNull = createPatient(withPartition(null), withActiveTrue());
IIdType patientId1 = createPatient(withPartition(1), withActiveTrue());
IIdType patientId2 = createPatient(withPartition(2), withActiveTrue());
/* *******************************
* _id param is only parameter
* *******************************/
// Read in correct Partition
{
myCaptureQueriesListener.clear();
addReadPartition(1);
SearchParameterMap map = SearchParameterMap.newSynchronous(Patient.SP_RES_ID, new TokenParam(patientId1.toUnqualifiedVersionless().getValue()));
IBundleProvider searchOutcome = myPatientDao.search(map);
assertEquals(1, searchOutcome.size());
IIdType gotId1 = searchOutcome.getResources(0,1).get(0).getIdElement().toUnqualifiedVersionless();
assertEquals(patientId1, gotId1);
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false);
ourLog.info("Search SQL:\n{}", searchSql);
// Only the read columns should be used, no criteria use partition
assertThat(searchSql, searchSql, containsString("PARTITION_ID IN ('1')"));
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"), searchSql);
}
// Read in null Partition
{
addReadPartition(1);
SearchParameterMap map = SearchParameterMap.newSynchronous(Patient.SP_RES_ID, new TokenParam(patientIdNull.toUnqualifiedVersionless().getValue()));
IBundleProvider searchOutcome = myPatientDao.search(map);
assertEquals(0, searchOutcome.size());
}
// Read in wrong Partition
{
addReadPartition(1);
SearchParameterMap map = SearchParameterMap.newSynchronous(Patient.SP_RES_ID, new TokenParam(patientId2.toUnqualifiedVersionless().getValue()));
IBundleProvider searchOutcome = myPatientDao.search(map);
assertEquals(0, searchOutcome.size());
}
}
@Test
public void testSearch_IdParamSecond_PidId_SpecificPartition() {
IIdType patientIdNull = createPatient(withPartition(null), withActiveTrue());
IIdType patientId1 = createPatient(withPartition(1), withActiveTrue());
IIdType patientId2 = createPatient(withPartition(2), withActiveTrue());
/* *******************************
* _id param is second parameter
* *******************************/
// Read in correct Partition
{
myCaptureQueriesListener.clear();
addReadPartition(1);
SearchParameterMap map = SearchParameterMap.newSynchronous()
.add(Patient.SP_ACTIVE, new TokenParam("true"))
.add(Patient.SP_RES_ID, new TokenParam(patientId1.toUnqualifiedVersionless().getValue()));
IBundleProvider searchOutcome = myPatientDao.search(map);
assertEquals(1, searchOutcome.size());
IIdType gotId1 = searchOutcome.getResources(0,1).get(0).getIdElement().toUnqualifiedVersionless();
assertEquals(patientId1, gotId1);
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false);
ourLog.info("Search SQL:\n{}", searchSql);
// Only the read columns should be used, no criteria use partition
assertThat(searchSql, searchSql, containsString("PARTITION_ID IN ('1')"));
assertEquals(2, StringUtils.countMatches(searchSql, "PARTITION_ID"), searchSql); // If this switches to 1 that would be fine
}
// Read in null Partition
{
addReadPartition(1);
SearchParameterMap map = SearchParameterMap.newSynchronous()
.add(Patient.SP_ACTIVE, new TokenParam("true"))
.add(Patient.SP_RES_ID, new TokenParam(patientIdNull.toUnqualifiedVersionless().getValue()));
IBundleProvider searchOutcome = myPatientDao.search(map);
assertEquals(0, searchOutcome.size());
}
// Read in wrong Partition
{
addReadPartition(1);
SearchParameterMap map = SearchParameterMap.newSynchronous()
.add(Patient.SP_ACTIVE, new TokenParam("true"))
.add(Patient.SP_RES_ID, new TokenParam(patientId2.toUnqualifiedVersionless().getValue()));
IBundleProvider searchOutcome = myPatientDao.search(map);
assertEquals(0, searchOutcome.size());
}
}
@Test
public void testSearch_IdParamOnly_ForcedId_SpecificPartition() {
addReadPartition(new Integer[]{null});
IIdType patientIdNull = createPatient(withPartition(null), withId("PT-NULL"), withActiveTrue());
addReadPartition(1);
IIdType patientId1 = createPatient(withPartition(1), withId("PT-1"), withActiveTrue());
addReadPartition(2);
IIdType patientId2 = createPatient(withPartition(2), withId("PT-2"), withActiveTrue());
/* *******************************
* _id param is only parameter
* *******************************/
// Read in correct Partition
{
myCaptureQueriesListener.clear();
addReadPartition(1);
SearchParameterMap map = SearchParameterMap.newSynchronous(Patient.SP_RES_ID, new TokenParam(patientId1.toUnqualifiedVersionless().getValue()));
IBundleProvider searchOutcome = myPatientDao.search(map);
assertEquals(1, searchOutcome.size());
IIdType gotId1 = searchOutcome.getResources(0,1).get(0).getIdElement().toUnqualifiedVersionless();
assertEquals(patientId1, gotId1);
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false).toUpperCase();
ourLog.info("Search SQL:\n{}", searchSql);
// Only the read columns should be used, no criteria use partition
assertThat(searchSql, searchSql, containsString("PARTITION_ID IN ('1')"));
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"), searchSql);
}
// Read in null Partition
{
addReadPartition(1);
SearchParameterMap map = SearchParameterMap.newSynchronous(Patient.SP_RES_ID, new TokenParam(patientIdNull.toUnqualifiedVersionless().getValue()));
IBundleProvider searchOutcome = myPatientDao.search(map);
assertEquals(0, searchOutcome.size());
}
// Read in wrong Partition
{
addReadPartition(1);
SearchParameterMap map = SearchParameterMap.newSynchronous(Patient.SP_RES_ID, new TokenParam(patientId2.toUnqualifiedVersionless().getValue()));
IBundleProvider searchOutcome = myPatientDao.search(map);
assertEquals(0, searchOutcome.size());
}
}
@Test
public void testSearch_IdParamSecond_ForcedId_SpecificPartition() {
addReadPartition(new Integer[]{null});
IIdType patientIdNull = createPatient(withPartition(null), withId("PT-NULL"), withActiveTrue());
addReadPartition(1);
IIdType patientId1 = createPatient(withPartition(1), withId("PT-1"), withActiveTrue());
addReadPartition(2);
IIdType patientId2 = createPatient(withPartition(2), withId("PT-2"), withActiveTrue());
/* *******************************
* _id param is second parameter
* *******************************/
// Read in correct Partition
{
myCaptureQueriesListener.clear();
addReadPartition(1);
SearchParameterMap map = SearchParameterMap.newSynchronous()
.add(Patient.SP_ACTIVE, new TokenParam("true"))
.add(Patient.SP_RES_ID, new TokenParam(patientId1.toUnqualifiedVersionless().getValue()));
IBundleProvider searchOutcome = myPatientDao.search(map);
assertEquals(1, searchOutcome.size());
IIdType gotId1 = searchOutcome.getResources(0,1).get(0).getIdElement().toUnqualifiedVersionless();
assertEquals(patientId1, gotId1);
// First SQL resolves the forced ID
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false).toUpperCase();
ourLog.info("Search SQL:\n{}", searchSql);
assertThat(searchSql, searchSql, containsString("PARTITION_ID IN ('1')"));
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"), searchSql);
// Second SQL performs the search
searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(1).getSql(true, false).toUpperCase();
ourLog.info("Search SQL:\n{}", searchSql);
assertThat(searchSql, searchSql, containsString("PARTITION_ID IN ('1')"));
assertEquals(2, StringUtils.countMatches(searchSql, "PARTITION_ID"), searchSql); // If this switches to 1 that would be fine
}
// Read in null Partition
{
addReadPartition(1);
SearchParameterMap map = SearchParameterMap.newSynchronous()
.add(Patient.SP_ACTIVE, new TokenParam("true"))
.add(Patient.SP_RES_ID, new TokenParam(patientIdNull.toUnqualifiedVersionless().getValue()));
IBundleProvider searchOutcome = myPatientDao.search(map);
assertEquals(0, searchOutcome.size());
}
// Read in wrong Partition
{
addReadPartition(1);
SearchParameterMap map = SearchParameterMap.newSynchronous()
.add(Patient.SP_ACTIVE, new TokenParam("true"))
.add(Patient.SP_RES_ID, new TokenParam(patientId2.toUnqualifiedVersionless().getValue()));
IBundleProvider searchOutcome = myPatientDao.search(map);
assertEquals(0, searchOutcome.size());
}
}
@Test @Test
public void testSearch_MissingParamString_SearchAllPartitions() { public void testSearch_MissingParamString_SearchAllPartitions() {
myPartitionSettings.setIncludePartitionInSearchHashes(false); myPartitionSettings.setIncludePartitionInSearchHashes(false);

View File

@ -115,7 +115,7 @@ public interface ITestDataBuilder {
default Consumer<IBaseResource> withId(String theId) { default Consumer<IBaseResource> withId(String theId) {
return t -> { return t -> {
assertThat(theId, matchesPattern("[a-zA-Z0-9]+")); assertThat(theId, matchesPattern("[a-zA-Z0-9-]+"));
t.setId(theId); t.setId(theId);
}; };
} }