This commit is contained in:
James Agnew 2024-07-12 17:26:36 -04:00
parent c948ef4707
commit 1468bc1b49
7 changed files with 32 additions and 219 deletions

View File

@ -70,7 +70,6 @@ import ca.uhn.fhir.jpa.searchparam.util.LastNParameterHelper;
import ca.uhn.fhir.jpa.util.BaseIterator;
import ca.uhn.fhir.jpa.util.CartesianProductUtil;
import ca.uhn.fhir.jpa.util.CurrentThreadCaptureQueriesListener;
import ca.uhn.fhir.jpa.util.PermutationBuilder;
import ca.uhn.fhir.jpa.util.QueryChunker;
import ca.uhn.fhir.jpa.util.SqlQueryList;
import ca.uhn.fhir.model.api.IQueryParameterType;
@ -943,7 +942,6 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
theQueryStack.addSortOnLastUpdated(ascending);
} else {
// FIXME: test for activation
RuntimeSearchParam param = mySearchParamRegistry.getActiveSearchParam(
myResourceName, theSort.getParamName(), ISearchParamRegistry.ContextEnum.SORT);
@ -2043,7 +2041,7 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
String nextOrValue = nextOr.getValueAsQueryToken(myContext);
RuntimeSearchParam nextParamDef =
mySearchParamRegistry.getActiveSearchParam(myResourceName, nextParamName);
mySearchParamRegistry.getActiveSearchParam(myResourceName, nextParamName, ISearchParamRegistry.ContextEnum.SEARCH);
if (theComboParam.getComboSearchParamType() == ComboSearchParamType.NON_UNIQUE) {
if (nextParamDef.getParamType() == RestSearchParameterTypeEnum.STRING) {
nextOrValue = StringUtil.normalizeStringForSearchIndexing(nextOrValue);

View File

@ -1,99 +0,0 @@
package ca.uhn.fhir.jpa.util;
import java.util.ArrayList;
import java.util.List;
/**
* Utility class for
*/
public class PermutationBuilder {
/**
* Non instantiable
*/
private PermutationBuilder() {
// nothing
}
/**
* Given a list of lists of options, returns a list of every possible combination of options.
* For example, given the input lists:
* <pre>
* [
* [ A0, A1 ],
* [ B0, B1, B2 ]
* ]
* </pre>
* This method will return the following output lists:
* <pre>
* [
* [ A0, B0 ],
* [ A1, B0 ],
* [ A0, B1 ],
* [ A1, B1 ],
* [ A0, B2 ],
* [ A1, B2 ]
* ]
* </pre>
* <p>
* This method will return a newly created list and will not modify the input lists.
* </p>
*
* @param theInput A list of lists to calculate permutations of
* @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.
* @since 7.4.0
*/
public static <T> List<List<T>> calculatePermutations(List<List<T>> theInput) {
List<List<T>> listToPopulate = new ArrayList<>(calculatePermutationCount(theInput));
int[] indices = new int[theInput.size()];
doCalculatePermutationsIntoIndicesArrayAndPopulateList(0, indices, theInput, listToPopulate);
return listToPopulate;
}
/**
* Recursively called/calling method which navigates across a list of input ArrayLists and populates the array of indices
*
* @param thePositionX The offset within {@literal theInput}. We navigate recursively from 0 through {@literal theInput.size()}
* @param theIndices The array to populate. This array has a length matching the size of {@literal theInput} and each entry
* represents an offset within the list at {@literal theInput.get(arrayIndex)}
* @param theInput The input List of ArrayLists
* @param theOutput A list to populate with all permutations
* @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.
*/
private static <T> void doCalculatePermutationsIntoIndicesArrayAndPopulateList(
int thePositionX, int[] theIndices, List<List<T>> theInput, List<List<T>> theOutput) {
if (thePositionX != theInput.size()) {
// 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.
for (int positionY = 0; positionY < theInput.get(thePositionX).size(); positionY++) {
theIndices[thePositionX] = positionY;
doCalculatePermutationsIntoIndicesArrayAndPopulateList(
thePositionX + 1, theIndices, theInput, theOutput);
}
} else {
// If we're at the end of the list of input vectors, then we've been passed the
List<T> nextList = new ArrayList<>(theInput.size());
for (int positionX = 0; positionX < theInput.size(); positionX++) {
nextList.add(theInput.get(positionX).get(theIndices[positionX]));
}
theOutput.add(nextList);
}
}
/**
* Returns the number of permutations that {@link #calculatePermutations(List)} will
* calculate for the given list.
*
* @throws ArithmeticException If the number of permutations exceeds {@link Integer#MAX_VALUE}
* @since 7.4.0
*/
public static <T> int calculatePermutationCount(List<List<T>> theLists) throws ArithmeticException {
int retVal = !theLists.isEmpty() ? 1 : 0;
for (List<T> theList : theLists) {
retVal = Math.multiplyExact(retVal, theList.size());
}
return retVal;
}
}

View File

@ -1,106 +0,0 @@
package ca.uhn.fhir.jpa.util;
import org.junit.jupiter.api.Test;
import java.util.List;
import java.util.stream.IntStream;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
class PermutationBuilderTest {
@Test
public void testCalculatePermutations() {
List<List<String>> input = List.of(
List.of("A0", "A1"),
List.of("B0", "B1", "B2"),
List.of("C0", "C1")
);
List<List<String>> output = PermutationBuilder.calculatePermutations(input);
assertThat(output).containsExactlyInAnyOrder(
List.of("A0", "B0", "C0"),
List.of("A1", "B0", "C0"),
List.of("A0", "B1", "C0"),
List.of("A1", "B1", "C0"),
List.of("A0", "B2", "C0"),
List.of("A1", "B2", "C0"),
List.of("A0", "B0", "C1"),
List.of("A1", "B0", "C1"),
List.of("A0", "B1", "C1"),
List.of("A1", "B1", "C1"),
List.of("A0", "B2", "C1"),
List.of("A1", "B2", "C1")
);
}
@Test
public void testCalculatePermutations_OneDimensonalInputOnX() {
List<List<String>> input = List.of(
List.of("A0", "A1")
);
List<List<String>> output = PermutationBuilder.calculatePermutations(input);
assertThat(output).containsExactlyInAnyOrder(
List.of("A0"),
List.of("A1")
);
assertNotSame(input, output);
}
@Test
public void testCalculatePermutations_OneDimensonalInputOnY() {
List<List<String>> input = List.of(
List.of("A0"),
List.of("B0"),
List.of("C0")
);
List<List<String>> output = PermutationBuilder.calculatePermutations(input);
assertThat(output).containsExactlyInAnyOrder(
List.of("A0", "B0", "C0")
);
assertNotSame(input, output);
}
@Test
public void testCalculatePermutations_OneDimensonalInputOnXandY() {
List<List<String>> input = List.of(
List.of("A0")
);
List<List<String>> output = PermutationBuilder.calculatePermutations(input);
assertThat(output).containsExactlyInAnyOrder(
List.of("A0")
);
assertNotSame(input, output);
}
@Test
public void testCalculatePermutationCount() {
List<List<String>> input = List.of(
List.of("A0", "A1"),
List.of("B0", "B1", "B2"),
List.of("C0", "C1")
);
assertEquals(12, PermutationBuilder.calculatePermutationCount(input));
}
@Test
public void testCalculatePermutationCountEmpty() {
List<List<String>> input = List.of();
assertEquals(0, PermutationBuilder.calculatePermutationCount(input));
}
@Test
public void testCalculatePermutationCountOverflow() {
List<Integer> ranges = IntStream.range(0, 10001).boxed().toList();
List<List<Integer>> input = IntStream.range(0, 20).boxed().map(t -> ranges).toList();
assertThrows(ArithmeticException.class, () -> PermutationBuilder.calculatePermutationCount(input));
}
}

View File

@ -122,8 +122,10 @@ public class SearchParamRegistryImpl
// Can still be null in unit test scenarios
if (myActiveSearchParams != null) {
RuntimeSearchParam param = myActiveSearchParams.get(theResourceName, theParamName);
if (isAllowedForContext(param, theContext)) {
return param;
if (param != null) {
if (isAllowedForContext(param, theContext)) {
return param;
}
}
}

View File

@ -165,7 +165,6 @@ public interface ISearchParamRegistry {
* @param theResourceType the resource type.
* @return the {@link ResourceSearchParams} that has all the search params.
*/
// FIXME: can this be removed?
default ResourceSearchParams getRuntimeSearchParams(String theResourceType, ContextEnum theContext) {
ResourceSearchParams availableSearchParams =
getActiveSearchParams(theResourceType, theContext).makeCopy();

View File

@ -28,6 +28,7 @@ import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Stream;
import static ca.uhn.fhir.rest.server.util.ISearchParamRegistry.isAllowedForContext;
@ -35,6 +36,7 @@ import static ca.uhn.fhir.rest.server.util.ISearchParamRegistry.isAllowedForCont
public class ResourceSearchParams {
private final String myResourceName;
private final Map<String, RuntimeSearchParam> myMap;
private final Map<ISearchParamRegistry.ContextEnum, ResourceSearchParams> myContextToParams = new HashMap<>();
public ResourceSearchParams(String theResourceName) {
myResourceName = theResourceName;
@ -50,16 +52,30 @@ public class ResourceSearchParams {
return myMap.values();
}
/**
* Returns a filtered view of this {@link ResourceSearchParams} instance if
* any parameters are not valid for the given {@literal theContext}.
*/
public ResourceSearchParams toFilteredForContext(ISearchParamRegistry.ContextEnum theContext) {
Map<String, RuntimeSearchParam> filteredMap = new HashMap<>(myMap.size());
for (var nextEntry : myMap.entrySet()) {
String key = nextEntry.getKey();
RuntimeSearchParam nextParam = nextEntry.getValue();
if (isAllowedForContext(nextParam, theContext)) {
filteredMap.put(key, nextParam);
}
if (theContext == null) {
return this;
}
synchronized (this) {
ResourceSearchParams retVal = myContextToParams.get(theContext);
if (retVal == null) {
Map<String, RuntimeSearchParam> filteredMap = new HashMap<>(myMap.size());
for (var nextEntry : myMap.entrySet()) {
String key = nextEntry.getKey();
RuntimeSearchParam nextParam = nextEntry.getValue();
if (isAllowedForContext(nextParam, theContext)) {
filteredMap.put(key, nextParam);
}
}
retVal = new ResourceSearchParams(myResourceName, filteredMap);
myContextToParams.put(theContext, retVal);
}
return retVal;
}
return new ResourceSearchParams(myResourceName, filteredMap);
}
public static ResourceSearchParams empty(String theResourceName) {
@ -71,6 +87,7 @@ public class ResourceSearchParams {
}
public void remove(String theName) {
myContextToParams.clear();
myMap.remove(theName);
}
@ -83,10 +100,12 @@ public class ResourceSearchParams {
}
public RuntimeSearchParam put(String theName, RuntimeSearchParam theSearchParam) {
myContextToParams.clear();
return myMap.put(theName, theSearchParam);
}
public void addSearchParamIfAbsent(String theParamName, RuntimeSearchParam theRuntimeSearchParam) {
myContextToParams.clear();
myMap.putIfAbsent(theParamName, theRuntimeSearchParam);
}