diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/IckleQueryOperators.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/IckleQueryOperators.java
index cc745979c53..abae9212d38 100644
--- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/IckleQueryOperators.java
+++ b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/IckleQueryOperators.java
@@ -26,6 +26,7 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
+import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
@@ -35,10 +36,10 @@ import java.util.stream.Stream;
*
* For example,
*
- * for operator {@link ModelCriteriaBuilder.Operator.EQ} we concatenate left operand and right operand with equal sign:
+ * for operator {@link ModelCriteriaBuilder.Operator#EQ} we concatenate left operand and right operand with equal sign:
* {@code fieldName = :parameterName}
*
- * however, for operator {@link ModelCriteriaBuilder.Operator.EXISTS} we add following:
+ * however, for operator {@link ModelCriteriaBuilder.Operator#EXISTS} we add following:
*
* {@code fieldName IS NOT NULL AND fieldName IS NOT EMPTY"}.
*
@@ -46,7 +47,7 @@ import java.util.stream.Stream;
* corresponding value is then saved into {@code Map} that is passed to each {@link ExpressionCombinator}.
*/
public class IckleQueryOperators {
- private static final String UNWANTED_CHARACTERS_REGEX = "[^a-zA-Z\\d]";
+ private static final Pattern UNWANTED_CHARACTERS_REGEX = Pattern.compile("[^a-zA-Z\\d]");
public static final String C = "c";
private static final Map OPERATOR_TO_STRING = new HashMap<>();
private static final Map OPERATOR_TO_EXPRESSION_COMBINATORS = new HashMap<>();
@@ -123,14 +124,18 @@ public class IckleQueryOperators {
}
private static String removeForbiddenCharactersFromNamedParameter(String name) {
- return name.replaceAll(UNWANTED_CHARACTERS_REGEX, "");
+ return UNWANTED_CHARACTERS_REGEX.matcher(name).replaceAll( "");
}
/**
* Maps {@code namePrefix} to next available parameter name. For example, if {@code namePrefix == "id"}
* and {@code existingNames} set already contains {@code id0} and {@code id1} it returns {@code id2}.
+ * Any character that is not an alphanumeric will be stripped, so that it works for prefixes like
+ * {@code "attributes.name"} as well.
*
- * This method is used for computing available names for name query parameters
+ * This method is used for computing available names for name query parameters.
+ * Instead of creating generic named parameters that would be hard to debug and read by humans, it creates readable
+ * named parameters from the prefix.
*
* @param existingNames set of parameter names that are already used in this Ickle query
* @param namePrefix name of the parameter
diff --git a/model/map-hot-rod/src/test/java/org/keycloak/models/map/storage/hotRod/IckleQueryOperatorsTest.java b/model/map-hot-rod/src/test/java/org/keycloak/models/map/storage/hotRod/IckleQueryOperatorsTest.java
new file mode 100644
index 00000000000..f690209a9f7
--- /dev/null
+++ b/model/map-hot-rod/src/test/java/org/keycloak/models/map/storage/hotRod/IckleQueryOperatorsTest.java
@@ -0,0 +1,42 @@
+package org.keycloak.models.map.storage.hotRod;
+
+import org.hamcrest.Matchers;
+import org.junit.Test;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class IckleQueryOperatorsTest {
+
+ @Test
+ public void testFindAvailableNamedParamSimple() {
+ Set existingNames = new HashSet<>();
+
+ String param = IckleQueryOperators.findAvailableNamedParam(existingNames, "clientId");
+
+ assertThat("should create the first ID", param, Matchers.equalTo("clientId0"));
+ }
+
+ @Test
+ public void testFindAvailableNamedParamAlreadyExists() {
+ Set existingNames = new HashSet<>();
+ existingNames.add("clientId0");
+
+ String param = IckleQueryOperators.findAvailableNamedParam(existingNames, "clientId");
+
+ assertThat("should create the next ID as clientId0 is already taken", param, Matchers.equalTo("clientId1"));
+ }
+
+ @Test
+ public void testFindAvailableNamedParamIllegalCharacterInPrefix() {
+ Set existingNames = new HashSet<>();
+ existingNames.add("clientid0");
+
+ String param = IckleQueryOperators.findAvailableNamedParam(existingNames, "client.id");
+
+ assertThat("should remove non-characters and non-numbers from the ID", param, Matchers.equalTo("clientid1"));
+ }
+
+}
\ No newline at end of file