Skip to content

Commit 1a688b2

Browse files
authored
Optimize StringMatcher for match-all patterns (#77738)
Detect a StringMatcher of "*" and optimize it as a predicate of s -> true This is about 80% faster than using an automaton. This speed improvement is very minor on a single execution but can add up to milliseconds is used in a tight loop - e.g. testing against every indices in a large cluster
1 parent 900c4c0 commit 1a688b2

File tree

2 files changed

+39
-0
lines changed

2 files changed

+39
-0
lines changed

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/support/StringMatcher.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ public class StringMatcher implements Predicate<String> {
3636

3737
private static final StringMatcher MATCH_NOTHING = new StringMatcher("(empty)", s -> false);
3838

39+
protected static final Predicate<String> ALWAYS_TRUE_PREDICATE = s -> true;
40+
3941
private final String description;
4042
private final Predicate<String> predicate;
4143
private static final Logger LOGGER = LogManager.getLogger(StringMatcher.class);
@@ -67,6 +69,11 @@ public boolean test(String s) {
6769
return predicate.test(s);
6870
}
6971

72+
// For testing
73+
Predicate<String> getPredicate() {
74+
return predicate;
75+
}
76+
7077
@Override
7178
public StringMatcher or(Predicate<? super String> other) {
7279
Objects.requireNonNull(other);
@@ -118,6 +125,9 @@ public StringMatcher build() {
118125
}
119126

120127
final String description = describe(allText);
128+
if (nonExactMatch.contains("*")) {
129+
return new StringMatcher(description, ALWAYS_TRUE_PREDICATE);
130+
}
121131
if (exactMatch.isEmpty()) {
122132
return new StringMatcher(description, buildAutomataPredicate(nonExactMatch));
123133
}

x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/support/StringMatcherTests.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,12 @@
1111

1212
import java.util.List;
1313
import java.util.Locale;
14+
import java.util.function.Supplier;
15+
import java.util.stream.Collectors;
16+
import java.util.stream.Stream;
1417

1518
import static org.hamcrest.Matchers.equalTo;
19+
import static org.hamcrest.Matchers.sameInstance;
1620

1721
public class StringMatcherTests extends ESTestCase {
1822

@@ -23,6 +27,31 @@ public void testEmptySet() throws Exception {
2327
}
2428
}
2529

30+
public void testMatchAllWildcard() throws Exception {
31+
Supplier<String> randomPattern = () -> {
32+
final String s = randomAlphaOfLengthBetween(3, 5);
33+
switch (randomIntBetween(1, 4)) {
34+
case 1:
35+
return s;
36+
case 2:
37+
return s + "*";
38+
case 3:
39+
return "*" + s;
40+
default:
41+
return "*" + s + "*";
42+
}
43+
};
44+
final List<String> patterns = Stream.of(randomList(0, 3, randomPattern), List.of("*"), randomList(0, 3, randomPattern))
45+
.flatMap(List::stream)
46+
.collect(Collectors.toList());
47+
final StringMatcher matcher = StringMatcher.of(patterns);
48+
for (int i = 0; i < 10; i++) {
49+
assertMatch(matcher, randomAlphaOfLengthBetween(i, 20));
50+
}
51+
52+
assertThat(matcher.getPredicate(), sameInstance(StringMatcher.ALWAYS_TRUE_PREDICATE));
53+
}
54+
2655
public void testSingleWildcard() throws Exception {
2756
final String prefix = randomAlphaOfLengthBetween(3, 5);
2857
final StringMatcher matcher = StringMatcher.of(prefix + "*");

0 commit comments

Comments
 (0)