Skip to content

Commit fdcdcb9

Browse files
committed
Insensivite RegexMatch optimization applies locally
1 parent ae057c6 commit fdcdcb9

File tree

4 files changed

+85
-69
lines changed

4 files changed

+85
-69
lines changed

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/LocalLogicalPlanOptimizer.java

Lines changed: 29 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
import org.elasticsearch.xpack.esql.optimizer.rules.logical.PropagateEmptyRelation;
1111
import org.elasticsearch.xpack.esql.optimizer.rules.logical.ReplaceStatsFilteredAggWithEval;
12+
import org.elasticsearch.xpack.esql.optimizer.rules.logical.ReplaceStringCasingWithInsensitiveRegexMatch;
1213
import org.elasticsearch.xpack.esql.optimizer.rules.logical.local.InferIsNotNull;
1314
import org.elasticsearch.xpack.esql.optimizer.rules.logical.local.InferNonNullAggConstraint;
1415
import org.elasticsearch.xpack.esql.optimizer.rules.logical.local.LocalPropagateEmptyRelation;
@@ -33,19 +34,17 @@
3334
*/
3435
public class LocalLogicalPlanOptimizer extends ParameterizedRuleExecutor<LogicalPlan, LocalLogicalOptimizerContext> {
3536

36-
private static final List<Batch<LogicalPlan>> RULES = replaceRules(
37-
arrayAsArrayList(
38-
new Batch<>(
39-
"Local rewrite",
40-
Limiter.ONCE,
41-
new ReplaceTopNWithLimitAndSort(),
42-
new ReplaceFieldWithConstantOrNull(),
43-
new InferIsNotNull(),
44-
new InferNonNullAggConstraint()
45-
),
46-
operators(),
47-
cleanup()
48-
)
37+
private static final List<Batch<LogicalPlan>> RULES = arrayAsArrayList(
38+
new Batch<>(
39+
"Local rewrite",
40+
Limiter.ONCE,
41+
new ReplaceTopNWithLimitAndSort(),
42+
new ReplaceFieldWithConstantOrNull(),
43+
new InferIsNotNull(),
44+
new InferNonNullAggConstraint()
45+
),
46+
localOperators(),
47+
cleanup()
4948
);
5049

5150
public LocalLogicalPlanOptimizer(LocalLogicalOptimizerContext localLogicalOptimizerContext) {
@@ -58,27 +57,25 @@ protected List<Batch<LogicalPlan>> batches() {
5857
}
5958

6059
@SuppressWarnings("unchecked")
61-
private static List<Batch<LogicalPlan>> replaceRules(List<Batch<LogicalPlan>> listOfRules) {
62-
List<Batch<LogicalPlan>> newBatches = new ArrayList<>(listOfRules.size());
63-
for (var batch : listOfRules) {
64-
var rules = batch.rules();
65-
List<Rule<?, LogicalPlan>> newRules = new ArrayList<>(rules.length);
66-
boolean updated = false;
67-
for (var r : rules) {
68-
if (r instanceof PropagateEmptyRelation) {
69-
newRules.add(new LocalPropagateEmptyRelation());
70-
updated = true;
71-
} else if (r instanceof ReplaceStatsFilteredAggWithEval) {
72-
// skip it: once a fragment contains an Agg, this can no longer be pruned, which the rule can do
73-
updated = true;
74-
} else {
75-
newRules.add(r);
76-
}
60+
private static Batch<LogicalPlan> localOperators() {
61+
var operators = operators();
62+
var rules = operators().rules();
63+
List<Rule<?, LogicalPlan>> newRules = new ArrayList<>(rules.length);
64+
65+
// apply updates to existing rules that have different applicability locally
66+
for (var r : rules) {
67+
switch (r) {
68+
case PropagateEmptyRelation ignoredPropagate -> newRules.add(new LocalPropagateEmptyRelation());
69+
// skip it: once a fragment contains an Agg, this can no longer be pruned, which the rule can do
70+
case ReplaceStatsFilteredAggWithEval ignoredReplace -> {}
71+
default -> newRules.add(r);
7772
}
78-
batch = updated ? batch.with(newRules.toArray(Rule[]::new)) : batch;
79-
newBatches.add(batch);
8073
}
81-
return newBatches;
74+
75+
// add rule that should only apply locally
76+
newRules.add(new ReplaceStringCasingWithInsensitiveRegexMatch());
77+
78+
return operators.with(newRules.toArray(Rule[]::new));
8279
}
8380

8481
public LogicalPlan localOptimize(LogicalPlan plan) {

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/LogicalPlanOptimizer.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
import org.elasticsearch.xpack.esql.optimizer.rules.logical.ReplaceRegexMatch;
5454
import org.elasticsearch.xpack.esql.optimizer.rules.logical.ReplaceRowAsLocalRelation;
5555
import org.elasticsearch.xpack.esql.optimizer.rules.logical.ReplaceStatsFilteredAggWithEval;
56-
import org.elasticsearch.xpack.esql.optimizer.rules.logical.ReplaceStringCasingWithInsensitiveEquivalent;
56+
import org.elasticsearch.xpack.esql.optimizer.rules.logical.ReplaceStringCasingWithInsensitiveEquals;
5757
import org.elasticsearch.xpack.esql.optimizer.rules.logical.ReplaceTrivialTypeConversions;
5858
import org.elasticsearch.xpack.esql.optimizer.rules.logical.SetAsOptimized;
5959
import org.elasticsearch.xpack.esql.optimizer.rules.logical.SimplifyComparisonsArithmetics;
@@ -185,7 +185,7 @@ protected static Batch<LogicalPlan> operators() {
185185
new CombineDisjunctions(),
186186
// TODO: bifunction can now (since we now have just one data types set) be pushed into the rule
187187
new SimplifyComparisonsArithmetics(DataType::areCompatible),
188-
new ReplaceStringCasingWithInsensitiveEquivalent(),
188+
new ReplaceStringCasingWithInsensitiveEquals(),
189189
new ReplaceStatsFilteredAggWithEval(),
190190
new ExtractAggregateCommonFilter(),
191191
// prune/elimination
Lines changed: 3 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -12,25 +12,17 @@
1212
import org.elasticsearch.xpack.esql.core.expression.Literal;
1313
import org.elasticsearch.xpack.esql.core.expression.function.scalar.ScalarFunction;
1414
import org.elasticsearch.xpack.esql.core.expression.predicate.operator.comparison.BinaryComparison;
15-
import org.elasticsearch.xpack.esql.core.expression.predicate.regex.RegexMatch;
16-
import org.elasticsearch.xpack.esql.core.expression.predicate.regex.StringPattern;
1715
import org.elasticsearch.xpack.esql.expression.function.scalar.string.ChangeCase;
18-
import org.elasticsearch.xpack.esql.expression.function.scalar.string.RLike;
19-
import org.elasticsearch.xpack.esql.expression.function.scalar.string.WildcardLike;
2016
import org.elasticsearch.xpack.esql.expression.predicate.logical.Not;
2117
import org.elasticsearch.xpack.esql.expression.predicate.nulls.IsNotNull;
2218
import org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.Equals;
2319
import org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.InsensitiveEquals;
2420
import org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.NotEquals;
2521
import org.elasticsearch.xpack.esql.optimizer.LogicalOptimizerContext;
2622

27-
/**
28-
* Replaces binary case-sensitive operations - like some comparisons and regex matches - involving a case-changing function (a subclass of
29-
* {@link ChangeCase}) and a string literal (be it an immediate value or a pattern) with an equivalent case-insensitive operation.
30-
*/
31-
public class ReplaceStringCasingWithInsensitiveEquivalent extends OptimizerRules.OptimizerExpressionRule<ScalarFunction> {
23+
public class ReplaceStringCasingWithInsensitiveEquals extends OptimizerRules.OptimizerExpressionRule<ScalarFunction> {
3224

33-
public ReplaceStringCasingWithInsensitiveEquivalent() {
25+
public ReplaceStringCasingWithInsensitiveEquals() {
3426
super(OptimizerRules.TransformDirection.DOWN);
3527
}
3628

@@ -41,8 +33,6 @@ protected Expression rule(ScalarFunction sf, LogicalOptimizerContext ctx) {
4133
e = rewriteBinaryComparison(ctx, sf, bc, false);
4234
} else if (sf instanceof Not not && not.field() instanceof BinaryComparison bc) {
4335
e = rewriteBinaryComparison(ctx, sf, bc, true);
44-
} else if (sf instanceof RegexMatch<? extends StringPattern> regexMatch) {
45-
e = rewriteRegexMatch(regexMatch);
4636
}
4737
return e;
4838
}
@@ -76,29 +66,7 @@ private static Expression replaceChangeCase(LogicalOptimizerContext ctx, BinaryC
7666
return e;
7767
}
7868

79-
private static Expression rewriteRegexMatch(RegexMatch<? extends StringPattern> regexMatch) {
80-
Expression e = regexMatch;
81-
if (regexMatch.field() instanceof ChangeCase changeCase) {
82-
var pattern = regexMatch.pattern().pattern();
83-
e = changeCase.caseType().matchesCase(pattern) ? insensitiveRegexMatch(regexMatch) : Literal.of(regexMatch, Boolean.FALSE);
84-
}
85-
return e;
86-
}
87-
88-
private static Expression insensitiveRegexMatch(RegexMatch<? extends StringPattern> regexMatch) {
89-
return switch (regexMatch) {
90-
case RLike rlike -> new RLike(rlike.source(), unwrapCase(rlike.field()), rlike.pattern(), true);
91-
case WildcardLike wildcardLike -> new WildcardLike(
92-
wildcardLike.source(),
93-
unwrapCase(wildcardLike.field()),
94-
wildcardLike.pattern(),
95-
true
96-
);
97-
default -> regexMatch;
98-
};
99-
}
100-
101-
private static Expression unwrapCase(Expression e) {
69+
static Expression unwrapCase(Expression e) {
10270
for (; e instanceof ChangeCase cc; e = cc.field()) {
10371
}
10472
return e;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
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+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
package org.elasticsearch.xpack.esql.optimizer.rules.logical;
9+
10+
import org.elasticsearch.xpack.esql.core.expression.Expression;
11+
import org.elasticsearch.xpack.esql.core.expression.Literal;
12+
import org.elasticsearch.xpack.esql.core.expression.predicate.regex.RegexMatch;
13+
import org.elasticsearch.xpack.esql.core.expression.predicate.regex.StringPattern;
14+
import org.elasticsearch.xpack.esql.expression.function.scalar.string.ChangeCase;
15+
import org.elasticsearch.xpack.esql.expression.function.scalar.string.RLike;
16+
import org.elasticsearch.xpack.esql.expression.function.scalar.string.WildcardLike;
17+
import org.elasticsearch.xpack.esql.optimizer.LogicalOptimizerContext;
18+
19+
import static org.elasticsearch.xpack.esql.optimizer.rules.logical.ReplaceStringCasingWithInsensitiveEquals.unwrapCase;
20+
21+
public class ReplaceStringCasingWithInsensitiveRegexMatch extends OptimizerRules.OptimizerExpressionRule<
22+
RegexMatch<? extends StringPattern>> {
23+
24+
public ReplaceStringCasingWithInsensitiveRegexMatch() {
25+
super(OptimizerRules.TransformDirection.DOWN);
26+
}
27+
28+
@Override
29+
protected Expression rule(RegexMatch<? extends StringPattern> regexMatch, LogicalOptimizerContext unused) {
30+
Expression e = regexMatch;
31+
if (regexMatch.field() instanceof ChangeCase changeCase) {
32+
var pattern = regexMatch.pattern().pattern();
33+
e = changeCase.caseType().matchesCase(pattern) ? insensitiveRegexMatch(regexMatch) : Literal.of(regexMatch, Boolean.FALSE);
34+
}
35+
return e;
36+
}
37+
38+
private static Expression insensitiveRegexMatch(RegexMatch<? extends StringPattern> regexMatch) {
39+
return switch (regexMatch) {
40+
case RLike rlike -> new RLike(rlike.source(), unwrapCase(rlike.field()), rlike.pattern(), true);
41+
case WildcardLike wildcardLike -> new WildcardLike(
42+
wildcardLike.source(),
43+
unwrapCase(wildcardLike.field()),
44+
wildcardLike.pattern(),
45+
true
46+
);
47+
default -> regexMatch;
48+
};
49+
}
50+
51+
}

0 commit comments

Comments
 (0)