Work on combo

This commit is contained in:
James Agnew 2024-07-03 13:40:18 -04:00
parent 9143f5995b
commit c2ed5f8419
6 changed files with 97 additions and 38 deletions

View File

@ -314,9 +314,7 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
* parameters all have no modifiers. * parameters all have no modifiers.
*/ */
private boolean isCompositeUniqueSpCandidate() { private boolean isCompositeUniqueSpCandidate() {
return myStorageSettings.isUniqueIndexesEnabled() return myStorageSettings.isUniqueIndexesEnabled() && myParams.getEverythingMode() == null;
&& myParams.getEverythingMode() == null
&& myParams.isAllParametersHaveNoModifier();
} }
@SuppressWarnings("ConstantConditions") @SuppressWarnings("ConstantConditions")
@ -1954,7 +1952,8 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
IQueryParameterType nextOr = nextPermutation.get(paramIndex); IQueryParameterType nextOr = nextPermutation.get(paramIndex);
String nextOrValue = nextOr.getValueAsQueryToken(myContext); String nextOrValue = nextOr.getValueAsQueryToken(myContext);
RuntimeSearchParam nextParamDef = mySearchParamRegistry.getActiveSearchParam(myResourceName, nextParamName); RuntimeSearchParam nextParamDef =
mySearchParamRegistry.getActiveSearchParam(myResourceName, nextParamName);
if (theComboParam.getComboSearchParamType() == ComboSearchParamType.NON_UNIQUE) { if (theComboParam.getComboSearchParamType() == ComboSearchParamType.NON_UNIQUE) {
if (nextParamDef.getParamType() == RestSearchParameterTypeEnum.STRING) { if (nextParamDef.getParamType() == RestSearchParameterTypeEnum.STRING) {
nextOrValue = StringUtil.normalizeStringForSearchIndexing(nextOrValue); nextOrValue = StringUtil.normalizeStringForSearchIndexing(nextOrValue);
@ -1975,7 +1974,7 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
String indexString = searchStringBuilder.toString(); String indexString = searchStringBuilder.toString();
ourLog.debug( ourLog.debug(
"Checking for {} combo index for query: {}", theComboParam.getComboSearchParamType(), indexString); "Checking for {} combo index for query: {}", theComboParam.getComboSearchParamType(), indexString);
indexStrings.add(indexString); indexStrings.add(indexString);
} }
@ -2008,10 +2007,16 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
theParams.clean(); theParams.clean();
} }
/**
* Returns {@literal true} if the actual parameter instances in a given query are actually usable for
* searching against a combo param with the given parameter names. This might be {@literal false} if
* parameters have modifiers (e.g. <code>?name:exact=SIMPSON</code>), prefixes
* (e.g. <code>?date=gt2024-02-01</code>), etc.
*/
private boolean validateParamValuesAreValidForComboParam( private boolean validateParamValuesAreValidForComboParam(
RequestDetails theRequest, @Nonnull SearchParameterMap theParams, List<String> comboParamNames) { RequestDetails theRequest, @Nonnull SearchParameterMap theParams, List<String> theComboParamNames) {
boolean paramValuesAreValidForCombo = true; boolean paramValuesAreValidForCombo = true;
for (String nextParamName : comboParamNames) { for (String nextParamName : theComboParamNames) {
List<List<IQueryParameterType>> nextValues = theParams.get(nextParamName); List<List<IQueryParameterType>> nextValues = theParams.get(nextParamName);
if (nextValues.isEmpty()) { if (nextValues.isEmpty()) {
@ -2024,7 +2029,9 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
if (nextOrValue instanceof DateParam) { if (nextOrValue instanceof DateParam) {
DateParam dateParam = (DateParam) nextOrValue; DateParam dateParam = (DateParam) nextOrValue;
if (dateParam.getPrecision() != TemporalPrecisionEnum.DAY) { if (dateParam.getPrecision() != TemporalPrecisionEnum.DAY) {
String message = "Search with params " + comboParamNames + " is not a candidate for combo searching - Date search with non-DAY precision for parameter '" + nextParamName + "'"; String message = "Search with params " + theComboParamNames
+ " is not a candidate for combo searching - Date search with non-DAY precision for parameter '"
+ nextParamName + "'";
firePerformanceInfo(theRequest, message); firePerformanceInfo(theRequest, message);
paramValuesAreValidForCombo = false; paramValuesAreValidForCombo = false;
break; break;
@ -2033,12 +2040,23 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
if (nextOrValue instanceof BaseParamWithPrefix) { if (nextOrValue instanceof BaseParamWithPrefix) {
BaseParamWithPrefix<?> paramWithPrefix = (BaseParamWithPrefix<?>) nextOrValue; BaseParamWithPrefix<?> paramWithPrefix = (BaseParamWithPrefix<?>) nextOrValue;
if (paramWithPrefix.getPrefix() != null) { if (paramWithPrefix.getPrefix() != null) {
String message = "Search with params " + comboParamNames + " is not a candidate for combo searching - Parameter '" + nextParamName + "' has prefix: '" + paramWithPrefix.getPrefix().getValue() + "'"; String message = "Search with params " + theComboParamNames
+ " is not a candidate for combo searching - Parameter '" + nextParamName
+ "' has prefix: '"
+ paramWithPrefix.getPrefix().getValue() + "'";
firePerformanceInfo(theRequest, message); firePerformanceInfo(theRequest, message);
paramValuesAreValidForCombo = false; paramValuesAreValidForCombo = false;
break; break;
} }
} }
if (isNotBlank(nextOrValue.getQueryParameterQualifier())) {
String message = "Search with params " + theComboParamNames
+ " is not a candidate for combo searching - Parameter '" + nextParamName
+ "' has modifier: '" + nextOrValue.getQueryParameterQualifier() + "'";
firePerformanceInfo(theRequest, message);
paramValuesAreValidForCombo = false;
break;
}
} }
} }
@ -2452,8 +2470,7 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
.add(RequestDetails.class, theRequest) .add(RequestDetails.class, theRequest)
.addIfMatchesType(ServletRequestDetails.class, theRequest) .addIfMatchesType(ServletRequestDetails.class, theRequest)
.add(StorageProcessingMessage.class, message); .add(StorageProcessingMessage.class, message);
CompositeInterceptorBroadcaster.doCallHooks( CompositeInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, theRequest, pointcut, params);
myInterceptorBroadcaster, theRequest, pointcut, params);
} }
public static int getMaximumPageSize() { public static int getMaximumPageSize() {

View File

@ -44,19 +44,20 @@ public class ComboNonUniqueSearchParameterPredicateBuilder extends BaseSearchPar
myColumnHashComplete = getTable().addColumn("HASH_COMPLETE"); myColumnHashComplete = getTable().addColumn("HASH_COMPLETE");
} }
public Condition createPredicateHashComplete(RequestPartitionId theRequestPartitionId, List<String> theIndexStrings) { public Condition createPredicateHashComplete(
RequestPartitionId theRequestPartitionId, List<String> theIndexStrings) {
PartitionablePartitionId partitionId = PartitionablePartitionId partitionId =
PartitionablePartitionId.toStoragePartition(theRequestPartitionId, getPartitionSettings()); PartitionablePartitionId.toStoragePartition(theRequestPartitionId, getPartitionSettings());
Condition predicate; Condition predicate;
if (theIndexStrings.size() == 1) { if (theIndexStrings.size() == 1) {
long hash = ResourceIndexedComboTokenNonUnique.calculateHashComplete( long hash = ResourceIndexedComboTokenNonUnique.calculateHashComplete(
getPartitionSettings(), partitionId, theIndexStrings.get(0)); getPartitionSettings(), partitionId, theIndexStrings.get(0));
predicate = BinaryCondition.equalTo(myColumnHashComplete, generatePlaceholder(hash)); predicate = BinaryCondition.equalTo(myColumnHashComplete, generatePlaceholder(hash));
} else { } else {
List<Long> hashes = theIndexStrings List<Long> hashes = theIndexStrings.stream()
.stream() .map(t -> ResourceIndexedComboTokenNonUnique.calculateHashComplete(
.map(t -> ResourceIndexedComboTokenNonUnique.calculateHashComplete(getPartitionSettings(), partitionId, t)) getPartitionSettings(), partitionId, t))
.collect(Collectors.toList()); .collect(Collectors.toList());
predicate = new InCondition(myColumnHashComplete, generatePlaceholders(hashes)); predicate = new InCondition(myColumnHashComplete, generatePlaceholders(hashes));
} }
return combineWithRequestPartitionIdPredicate(theRequestPartitionId, predicate); return combineWithRequestPartitionIdPredicate(theRequestPartitionId, predicate);

View File

@ -41,7 +41,8 @@ public class ComboUniqueSearchParameterPredicateBuilder extends BaseSearchParamP
myColumnString = getTable().addColumn("IDX_STRING"); myColumnString = getTable().addColumn("IDX_STRING");
} }
public Condition createPredicateIndexString(RequestPartitionId theRequestPartitionId, List<String> theIndexStrings) { public Condition createPredicateIndexString(
RequestPartitionId theRequestPartitionId, List<String> theIndexStrings) {
Condition predicate; Condition predicate;
if (theIndexStrings.size() == 1) { if (theIndexStrings.size() == 1) {
predicate = BinaryCondition.equalTo(myColumnString, generatePlaceholder(theIndexStrings.get(0))); predicate = BinaryCondition.equalTo(myColumnString, generatePlaceholder(theIndexStrings.get(0)));

View File

@ -65,13 +65,15 @@ public class PermutationBuilder {
* @param <T> The type associated with {@literal theInput}. The actual type doesn't matter, this method does not look at the * @param <T> The type associated with {@literal theInput}. The actual type doesn't matter, this method does not look at the
* values at all other than to copy them to the output lists. * values at all other than to copy them to the output lists.
*/ */
private static <T> void doCalculatePermutationsIntoIndicesArrayAndPopulateList(int thePositionX, int[] theIndices, List<List<T>> theInput, List<List<T>> theOutput) { private static <T> void doCalculatePermutationsIntoIndicesArrayAndPopulateList(
int thePositionX, int[] theIndices, List<List<T>> theInput, List<List<T>> theOutput) {
if (thePositionX != theInput.size()) { if (thePositionX != theInput.size()) {
// If we're not at the end of the list of input vectors, recursively self-invoke once for each // If we're not at the end of the list of input vectors, recursively self-invoke once for each
// possible option at the current position in the list of input vectors. // possible option at the current position in the list of input vectors.
for (int positionY = 0; positionY < theInput.get(thePositionX).size(); positionY++) { for (int positionY = 0; positionY < theInput.get(thePositionX).size(); positionY++) {
theIndices[thePositionX] = positionY; theIndices[thePositionX] = positionY;
doCalculatePermutationsIntoIndicesArrayAndPopulateList(thePositionX + 1, theIndices, theInput, theOutput); doCalculatePermutationsIntoIndicesArrayAndPopulateList(
thePositionX + 1, theIndices, theInput, theOutput);
} }
} else { } else {
// If we're at the end of the list of input vectors, then we've been passed the // If we're at the end of the list of input vectors, then we've been passed the
@ -97,5 +99,4 @@ public class PermutationBuilder {
} }
return retVal; return retVal;
} }
} }

View File

@ -353,22 +353,6 @@ public class SearchParameterMap implements Serializable {
return this; return this;
} }
/**
* This will only return true if all parameters have no modifier of any kind
*/
public boolean isAllParametersHaveNoModifier() {
for (List<List<IQueryParameterType>> nextParamName : values()) {
for (List<IQueryParameterType> nextAnd : nextParamName) {
for (IQueryParameterType nextOr : nextAnd) {
if (isNotBlank(nextOr.getQueryParameterQualifier())) {
return false;
}
}
}
}
return true;
}
/** /**
* If set, tells the server to load these results synchronously, and not to load * If set, tells the server to load these results synchronously, and not to load
* more than X results * more than X results

View File

@ -2,6 +2,7 @@ package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.interceptor.api.IInterceptorService; import ca.uhn.fhir.interceptor.api.IInterceptorService;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedComboTokenNonUnique; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedComboTokenNonUnique;
import ca.uhn.fhir.jpa.model.entity.StorageSettings;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.searchparam.submit.interceptor.SearchParamValidatingInterceptor; import ca.uhn.fhir.jpa.searchparam.submit.interceptor.SearchParamValidatingInterceptor;
import ca.uhn.fhir.jpa.util.SqlQuery; import ca.uhn.fhir.jpa.util.SqlQuery;
@ -58,6 +59,8 @@ public class FhirResourceDaoR4ComboNonUniqueParamTest extends BaseComboParamsR4T
@AfterEach @AfterEach
public void restoreInterceptor() { public void restoreInterceptor() {
myStorageSettings.setIndexMissingFields(new StorageSettings().getIndexMissingFields());
if (myInterceptorFound) { if (myInterceptorFound) {
myInterceptorService.unregisterInterceptor(mySearchParamValidatingInterceptor); myInterceptorService.unregisterInterceptor(mySearchParamValidatingInterceptor);
} }
@ -765,6 +768,58 @@ public class FhirResourceDaoR4ComboNonUniqueParamTest extends BaseComboParamsR4T
} }
@ParameterizedTest
@ValueSource(booleans = {true, false})
public void testStringModifier(boolean theUseExact) {
IIdType id1 = createObservation("2021-01-02");
createObservation("2023-01-02");
SearchParameterMap params = SearchParameterMap
.newSynchronous()
.add("note-text", new StringParam("Hello").setExact(theUseExact))
.add("date", new DateParam("2021-01-02"));
myCaptureQueriesListener.clear();
IBundleProvider results = myObservationDao.search(params, mySrd);
List<String> actual = toUnqualifiedVersionlessIdValues(results);
myCaptureQueriesListener.logSelectQueries();
assertThat(actual).contains(id1.toUnqualifiedVersionless().getValue());
if (theUseExact) {
assertComboIndexNotUsed();
assertThat(myMessages.toString()).contains("INFO Search with params [date, note-text] is not a candidate for combo searching - Parameter 'note-text' has modifier: ':exact'");
} else {
assertComboIndexUsed();
}
}
@ParameterizedTest
@ValueSource(booleans = {true, false})
public void testMissing(boolean theUseMissing) {
myStorageSettings.setIndexMissingFields(StorageSettings.IndexEnabledEnum.ENABLED);
IIdType id1 = createObservation("2021-01-02");
createObservation("2023-01-02");
SearchParameterMap params = SearchParameterMap
.newSynchronous()
.add("note-text", new StringParam("Hello").setMissing(theUseMissing ? false : null))
.add("date", new DateParam("2021-01-02"));
myCaptureQueriesListener.clear();
IBundleProvider results = myObservationDao.search(params, mySrd);
List<String> actual = toUnqualifiedVersionlessIdValues(results);
myCaptureQueriesListener.logSelectQueries();
assertThat(actual).contains(id1.toUnqualifiedVersionless().getValue());
if (theUseMissing) {
assertComboIndexNotUsed();
assertThat(myMessages.toString()).contains("INFO Search with params [date, note-text] is not a candidate for combo searching - Parameter 'note-text' has modifier: ':missing'");
} else {
assertComboIndexUsed();
}
}
private void assertComboIndexUsed() { private void assertComboIndexUsed() {
String sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false); String sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false);
assertThat(sql).contains("HFJ_IDX_CMB_TOK_NU"); assertThat(sql).contains("HFJ_IDX_CMB_TOK_NU");