Work on combo
This commit is contained in:
parent
9143f5995b
commit
c2ed5f8419
|
@ -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() {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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)));
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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");
|
||||||
|
|
Loading…
Reference in New Issue