Skip to content

Commit 4c23fad

Browse files
authored
QL: Optimize Like/Rlike all (#62682)
Replace common Like and RLike queries that match all characters with IsNotNull (exists) queries Fix #62585
1 parent a183f89 commit 4c23fad

File tree

14 files changed

+219
-35
lines changed

14 files changed

+219
-35
lines changed

x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/optimizer/Optimizer.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import org.elasticsearch.xpack.ql.optimizer.OptimizerRules.PropagateEquals;
4040
import org.elasticsearch.xpack.ql.optimizer.OptimizerRules.PruneLiteralsInOrderBy;
4141
import org.elasticsearch.xpack.ql.optimizer.OptimizerRules.ReplaceSurrogateFunction;
42+
import org.elasticsearch.xpack.ql.optimizer.OptimizerRules.ReplaceMatchAll;
4243
import org.elasticsearch.xpack.ql.optimizer.OptimizerRules.SetAsOptimized;
4344
import org.elasticsearch.xpack.ql.optimizer.OptimizerRules.TransformDirection;
4445
import org.elasticsearch.xpack.ql.plan.logical.Filter;
@@ -66,7 +67,11 @@ public LogicalPlan optimize(LogicalPlan verified) {
6667
@Override
6768
protected Iterable<RuleExecutor<LogicalPlan>.Batch> batches() {
6869
Batch substitutions = new Batch("Substitution", Limiter.ONCE,
69-
new ReplaceSurrogateFunction());
70+
// needed for replace wildcards
71+
new BooleanLiteralsOnTheRight(),
72+
new ReplaceWildcards(),
73+
new ReplaceSurrogateFunction(),
74+
new ReplaceMatchAll());
7075

7176
Batch operators = new Batch("Operator Optimization",
7277
new ConstantFolding(),
@@ -75,7 +80,6 @@ protected Iterable<RuleExecutor<LogicalPlan>.Batch> batches() {
7580
new BooleanLiteralsOnTheRight(),
7681
new BooleanEqualsSimplification(),
7782
// needs to occur before BinaryComparison combinations
78-
new ReplaceWildcards(),
7983
new ReplaceNullChecks(),
8084
new PropagateEquals(),
8185
new CombineBinaryComparisons(),
@@ -106,7 +110,7 @@ protected Iterable<RuleExecutor<LogicalPlan>.Batch> batches() {
106110
private static class ReplaceWildcards extends OptimizerRule<Filter> {
107111

108112
private static boolean isWildcard(Expression expr) {
109-
if (expr.foldable()) {
113+
if (expr instanceof Literal) {
110114
Object value = expr.fold();
111115
return value instanceof String && value.toString().contains("*");
112116
}

x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/planner/QueryTranslator.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ final class QueryTranslator {
3939
new ExpressionTranslators.BinaryComparisons(),
4040
new ExpressionTranslators.Ranges(),
4141
new BinaryLogic(),
42+
new ExpressionTranslators.IsNotNulls(),
43+
new ExpressionTranslators.IsNulls(),
4244
new ExpressionTranslators.Nots(),
4345
new ExpressionTranslators.Likes(),
4446
new ExpressionTranslators.InComparisons(),
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
package org.elasticsearch.xpack.eql.planner;
8+
9+
import org.elasticsearch.xpack.eql.plan.physical.PhysicalPlan;
10+
11+
import static org.hamcrest.Matchers.containsString;
12+
13+
public class QueryTranslationTests extends AbstractQueryFolderTestCase {
14+
15+
public void testLikeOptimization() throws Exception {
16+
PhysicalPlan plan = plan("process where process_name == \"*\" ");
17+
assertThat(asQuery(plan), containsString("\"exists\":{\"field\":\"process_name\""));
18+
}
19+
20+
public void testMatchOptimization() throws Exception {
21+
PhysicalPlan plan = plan("process where match(process_name, \".*\") ");
22+
assertThat(asQuery(plan), containsString("\"exists\":{\"field\":\"process_name\""));
23+
}
24+
25+
private static String asQuery(PhysicalPlan plan) {
26+
return plan.toString().replaceAll("\\s+", "");
27+
}
28+
}

x-pack/plugin/ql/src/main/java/org/elasticsearch/xpack/ql/expression/predicate/regex/LikePattern.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@
55
*/
66
package org.elasticsearch.xpack.ql.expression.predicate.regex;
77

8+
import org.apache.lucene.index.Term;
9+
import org.apache.lucene.search.WildcardQuery;
10+
import org.apache.lucene.util.automaton.Automaton;
11+
import org.apache.lucene.util.automaton.MinimizationOperations;
12+
import org.apache.lucene.util.automaton.Operations;
813
import org.elasticsearch.xpack.ql.util.StringUtils;
914

1015
import java.util.Objects;
@@ -48,6 +53,12 @@ public String asJavaRegex() {
4853
return regex;
4954
}
5055

56+
@Override
57+
public boolean matchesAll() {
58+
Automaton automaton = WildcardQuery.toAutomaton(new Term(null, wildcard));
59+
return Operations.isTotal(MinimizationOperations.minimize(automaton, Operations.DEFAULT_MAX_DETERMINIZED_STATES));
60+
}
61+
5162
/**
5263
* Returns the pattern in (Lucene) wildcard format.
5364
*/

x-pack/plugin/ql/src/main/java/org/elasticsearch/xpack/ql/expression/predicate/regex/RLikePattern.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
*/
66
package org.elasticsearch.xpack.ql.expression.predicate.regex;
77

8+
import org.apache.lucene.util.automaton.Operations;
9+
import org.apache.lucene.util.automaton.RegExp;
10+
811
public class RLikePattern implements StringPattern {
912

1013
private final String regexpPattern;
@@ -17,4 +20,9 @@ public RLikePattern(String regexpPattern) {
1720
public String asJavaRegex() {
1821
return regexpPattern;
1922
}
23+
24+
@Override
25+
public boolean matchesAll() {
26+
return Operations.isTotal(new RegExp(regexpPattern).toAutomaton());
27+
}
2028
}

x-pack/plugin/ql/src/main/java/org/elasticsearch/xpack/ql/expression/predicate/regex/RegexMatch.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,12 @@
2525
public abstract class RegexMatch<T extends StringPattern> extends UnaryScalarFunction {
2626

2727
private final T pattern;
28-
28+
2929
protected RegexMatch(Source source, Expression value, T pattern) {
3030
super(source, value);
3131
this.pattern = pattern;
3232
}
33-
33+
3434
public T pattern() {
3535
return pattern;
3636
}
@@ -65,6 +65,10 @@ public Boolean fold() {
6565
return RegexProcessor.RegexOperation.match(val, pattern().asJavaRegex());
6666
}
6767

68+
public boolean matchesAll() {
69+
return pattern.matchesAll();
70+
}
71+
6872
@Override
6973
protected Processor makeProcessor() {
7074
return new RegexProcessor(pattern().asJavaRegex());

x-pack/plugin/ql/src/main/java/org/elasticsearch/xpack/ql/expression/predicate/regex/StringPattern.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,11 @@ interface StringPattern {
1010
* Returns the pattern in (Java) regex format.
1111
*/
1212
String asJavaRegex();
13+
14+
/**
15+
* Hint method on whether this pattern matches everything or not.
16+
*/
17+
default boolean matchesAll() {
18+
return false;
19+
}
1320
}

x-pack/plugin/ql/src/main/java/org/elasticsearch/xpack/ql/optimizer/OptimizerRules.java

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import org.elasticsearch.xpack.ql.expression.predicate.logical.And;
1919
import org.elasticsearch.xpack.ql.expression.predicate.logical.Not;
2020
import org.elasticsearch.xpack.ql.expression.predicate.logical.Or;
21+
import org.elasticsearch.xpack.ql.expression.predicate.nulls.IsNotNull;
2122
import org.elasticsearch.xpack.ql.expression.predicate.operator.comparison.BinaryComparison;
2223
import org.elasticsearch.xpack.ql.expression.predicate.operator.comparison.Equals;
2324
import org.elasticsearch.xpack.ql.expression.predicate.operator.comparison.GreaterThan;
@@ -26,6 +27,7 @@
2627
import org.elasticsearch.xpack.ql.expression.predicate.operator.comparison.LessThanOrEqual;
2728
import org.elasticsearch.xpack.ql.expression.predicate.operator.comparison.NotEquals;
2829
import org.elasticsearch.xpack.ql.expression.predicate.operator.comparison.NullEquals;
30+
import org.elasticsearch.xpack.ql.expression.predicate.regex.RegexMatch;
2931
import org.elasticsearch.xpack.ql.plan.logical.Filter;
3032
import org.elasticsearch.xpack.ql.plan.logical.Limit;
3133
import org.elasticsearch.xpack.ql.plan.logical.LogicalPlan;
@@ -50,7 +52,7 @@
5052

5153

5254
public final class OptimizerRules {
53-
55+
5456
public static final class ConstantFolding extends OptimizerExpressionRule {
5557

5658
public ConstantFolding() {
@@ -90,7 +92,7 @@ protected Expression rule(Expression e) {
9092
return e;
9193
}
9294
}
93-
95+
9496
public static final class BooleanSimplification extends OptimizerExpressionRule {
9597

9698
public BooleanSimplification() {
@@ -230,7 +232,7 @@ private Expression literalToTheRight(BinaryOperator<?, ?, ?, ?> be) {
230232
return be.left() instanceof Literal && !(be.right() instanceof Literal) ? be.swapLeftAndRight() : be;
231233
}
232234
}
233-
235+
234236
/**
235237
* Propagate Equals to eliminate conjuncted Ranges or BinaryComparisons.
236238
* When encountering a different Equals, non-containing {@link Range} or {@link BinaryComparison}, the conjunction becomes false.
@@ -537,7 +539,7 @@ private Expression propagate(Or or) {
537539
return updated ? Predicates.combineOr(CollectionUtils.combine(exps, equals, notEquals, inequalities, ranges)) : or;
538540
}
539541
}
540-
542+
541543
public static final class CombineBinaryComparisons extends OptimizerExpressionRule {
542544

543545
public CombineBinaryComparisons() {
@@ -1046,7 +1048,7 @@ protected Expression rule(Expression e) {
10461048
return e;
10471049
}
10481050
}
1049-
1051+
10501052
public abstract static class PruneFilters extends OptimizerRule<Filter> {
10511053

10521054
@Override
@@ -1094,7 +1096,7 @@ private static Expression foldBinaryLogic(Expression expression) {
10941096
return expression;
10951097
}
10961098
}
1097-
1099+
10981100
public static final class PruneLiteralsInOrderBy extends OptimizerRule<OrderBy> {
10991101

11001102
@Override
@@ -1120,7 +1122,7 @@ protected LogicalPlan rule(OrderBy ob) {
11201122
return ob;
11211123
}
11221124
}
1123-
1125+
11241126

11251127
public abstract static class SkipQueryOnLimitZero extends OptimizerRule<Limit> {
11261128
@Override
@@ -1136,6 +1138,23 @@ protected LogicalPlan rule(Limit limit) {
11361138
protected abstract LogicalPlan skipPlan(Limit limit);
11371139
}
11381140

1141+
public static class ReplaceMatchAll extends OptimizerExpressionRule {
1142+
1143+
public ReplaceMatchAll() {
1144+
super(TransformDirection.DOWN);
1145+
}
1146+
1147+
protected Expression rule(Expression e) {
1148+
if (e instanceof RegexMatch) {
1149+
RegexMatch<?> regexMatch = (RegexMatch<?>) e;
1150+
if (regexMatch.matchesAll()) {
1151+
return new IsNotNull(e.source(), regexMatch.field());
1152+
}
1153+
}
1154+
return e;
1155+
}
1156+
}
1157+
11391158
public static final class SetAsOptimized extends Rule<LogicalPlan, LogicalPlan> {
11401159

11411160
@Override

x-pack/plugin/ql/src/main/java/org/elasticsearch/xpack/ql/planner/ExpressionTranslators.java

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
import org.elasticsearch.xpack.ql.expression.predicate.logical.And;
2121
import org.elasticsearch.xpack.ql.expression.predicate.logical.Not;
2222
import org.elasticsearch.xpack.ql.expression.predicate.logical.Or;
23+
import org.elasticsearch.xpack.ql.expression.predicate.nulls.IsNotNull;
24+
import org.elasticsearch.xpack.ql.expression.predicate.nulls.IsNull;
2325
import org.elasticsearch.xpack.ql.expression.predicate.operator.comparison.BinaryComparison;
2426
import org.elasticsearch.xpack.ql.expression.predicate.operator.comparison.Equals;
2527
import org.elasticsearch.xpack.ql.expression.predicate.operator.comparison.GreaterThan;
@@ -34,6 +36,7 @@
3436
import org.elasticsearch.xpack.ql.expression.predicate.regex.RLike;
3537
import org.elasticsearch.xpack.ql.expression.predicate.regex.RegexMatch;
3638
import org.elasticsearch.xpack.ql.querydsl.query.BoolQuery;
39+
import org.elasticsearch.xpack.ql.querydsl.query.ExistsQuery;
3740
import org.elasticsearch.xpack.ql.querydsl.query.MatchQuery;
3841
import org.elasticsearch.xpack.ql.querydsl.query.MultiMatchQuery;
3942
import org.elasticsearch.xpack.ql.querydsl.query.NotQuery;
@@ -73,6 +76,8 @@ public final class ExpressionTranslators {
7376
new BinaryComparisons(),
7477
new Ranges(),
7578
new BinaryLogic(),
79+
new IsNulls(),
80+
new IsNotNulls(),
7681
new Nots(),
7782
new Likes(),
7883
new InComparisons(),
@@ -206,6 +211,46 @@ public static Query doTranslate(Not not, TranslatorHandler handler) {
206211
}
207212
}
208213

214+
public static class IsNotNulls extends ExpressionTranslator<IsNotNull> {
215+
216+
@Override
217+
protected Query asQuery(IsNotNull isNotNull, TranslatorHandler handler) {
218+
return doTranslate(isNotNull, handler);
219+
}
220+
221+
public static Query doTranslate(IsNotNull isNotNull, TranslatorHandler handler) {
222+
Query query = null;
223+
224+
if (isNotNull.field() instanceof FieldAttribute) {
225+
query = new ExistsQuery(isNotNull.source(), handler.nameOf(isNotNull.field()));
226+
} else {
227+
query = new ScriptQuery(isNotNull.source(), isNotNull.asScript());
228+
}
229+
230+
return handler.wrapFunctionQuery(isNotNull, isNotNull.field(), query);
231+
}
232+
}
233+
234+
public static class IsNulls extends ExpressionTranslator<IsNull> {
235+
236+
@Override
237+
protected Query asQuery(IsNull isNull, TranslatorHandler handler) {
238+
return doTranslate(isNull, handler);
239+
}
240+
241+
public static Query doTranslate(IsNull isNull, TranslatorHandler handler) {
242+
Query query = null;
243+
244+
if (isNull.field() instanceof FieldAttribute) {
245+
query = new NotQuery(isNull.source(), new ExistsQuery(isNull.source(), handler.nameOf(isNull.field())));
246+
} else {
247+
query = new ScriptQuery(isNull.source(), isNull.asScript());
248+
}
249+
250+
return handler.wrapFunctionQuery(isNull, isNull.field(), query);
251+
}
252+
}
253+
209254
// assume the Optimizer properly orders the predicates to ease the translation
210255
public static class BinaryComparisons extends ExpressionTranslator<BinaryComparison> {
211256

x-pack/plugin/ql/src/main/java/org/elasticsearch/xpack/ql/util/StringUtils.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public static String camelCaseToUnderscore(String string) {
6262
}
6363
return sb.toString().toUpperCase(Locale.ROOT);
6464
}
65-
65+
6666
//CAMEL_CASE to camelCase
6767
public static String underscoreToLowerCamelCase(String string) {
6868
if (!Strings.hasText(string)) {
@@ -337,4 +337,4 @@ public static String ordinal(int i) {
337337

338338
}
339339
}
340-
}
340+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
package org.elasticsearch.xpack.ql.expression.predicate.regex;
8+
9+
import org.elasticsearch.test.ESTestCase;
10+
11+
public class StringPatternTests extends ESTestCase {
12+
13+
private boolean isTotalWildcard(String pattern, char escape) {
14+
return new LikePattern(pattern, escape).matchesAll();
15+
}
16+
17+
private boolean isTotalRegex(String pattern) {
18+
return new RLikePattern(pattern).matchesAll();
19+
}
20+
21+
public void testWildcardMatchAll() throws Exception {
22+
assertTrue(isTotalWildcard("%", '0'));
23+
assertTrue(isTotalWildcard("%%", '0'));
24+
25+
assertFalse(isTotalWildcard("a%", '0'));
26+
assertFalse(isTotalWildcard("%_", '0'));
27+
assertFalse(isTotalWildcard("%_%_%", '0'));
28+
assertFalse(isTotalWildcard("_%", '0'));
29+
assertFalse(isTotalWildcard("0%", '0'));
30+
}
31+
32+
public void testRegexMatchAll() throws Exception {
33+
assertTrue(isTotalRegex(".*"));
34+
assertTrue(isTotalRegex(".*.*"));
35+
assertTrue(isTotalRegex(".*.?"));
36+
assertTrue(isTotalRegex(".?.*"));
37+
assertTrue(isTotalRegex(".*.?.*"));
38+
39+
assertFalse(isTotalRegex("..*"));
40+
assertFalse(isTotalRegex("ab."));
41+
assertFalse(isTotalRegex("..?"));
42+
}
43+
}

0 commit comments

Comments
 (0)