EQL: Add IsNull/IsNotNull checks (#52791)
* EQL: Add IsNull/IsNotNull checks * EQL: Simplify IsNull/IsNotNull optimization * EQL: Split string tests over multiple lines
This commit is contained in:
parent
f78bd00f57
commit
f5f922c6f6
|
@ -8,6 +8,8 @@ package org.elasticsearch.xpack.eql.optimizer;
|
|||
|
||||
import org.elasticsearch.xpack.ql.expression.Expression;
|
||||
import org.elasticsearch.xpack.ql.expression.predicate.logical.Not;
|
||||
import org.elasticsearch.xpack.ql.expression.predicate.nulls.IsNotNull;
|
||||
import org.elasticsearch.xpack.ql.expression.predicate.nulls.IsNull;
|
||||
import org.elasticsearch.xpack.ql.expression.predicate.operator.comparison.BinaryComparison;
|
||||
import org.elasticsearch.xpack.ql.expression.predicate.operator.comparison.Equals;
|
||||
import org.elasticsearch.xpack.ql.expression.predicate.operator.comparison.NotEquals;
|
||||
|
@ -43,6 +45,7 @@ public class Optimizer extends RuleExecutor<LogicalPlan> {
|
|||
new BooleanLiteralsOnTheRight(),
|
||||
// needs to occur before BinaryComparison combinations
|
||||
new ReplaceWildcards(),
|
||||
new ReplaceNullChecks(),
|
||||
new PropagateEquals(),
|
||||
new CombineBinaryComparisons(),
|
||||
// prune/elimination
|
||||
|
@ -102,4 +105,28 @@ public class Optimizer extends RuleExecutor<LogicalPlan> {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static class ReplaceNullChecks extends OptimizerRule<Filter> {
|
||||
|
||||
@Override
|
||||
protected LogicalPlan rule(Filter filter) {
|
||||
|
||||
return filter.transformExpressionsUp(e -> {
|
||||
// expr == null || expr != null
|
||||
if (e instanceof Equals || e instanceof NotEquals) {
|
||||
BinaryComparison cmp = (BinaryComparison) e;
|
||||
|
||||
if (cmp.right().foldable() && cmp.right().fold() == null) {
|
||||
if (e instanceof Equals) {
|
||||
e = new IsNull(e.source(), cmp.left());
|
||||
} else {
|
||||
e = new IsNotNull(e.source(), cmp.left());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return e;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,8 @@ import org.elasticsearch.xpack.eql.parser.EqlParser;
|
|||
import org.elasticsearch.xpack.ql.expression.FieldAttribute;
|
||||
import org.elasticsearch.xpack.ql.expression.predicate.logical.And;
|
||||
import org.elasticsearch.xpack.ql.expression.predicate.logical.Not;
|
||||
import org.elasticsearch.xpack.ql.expression.predicate.nulls.IsNotNull;
|
||||
import org.elasticsearch.xpack.ql.expression.predicate.nulls.IsNull;
|
||||
import org.elasticsearch.xpack.ql.expression.predicate.regex.Like;
|
||||
import org.elasticsearch.xpack.ql.index.EsIndex;
|
||||
import org.elasticsearch.xpack.ql.index.IndexResolution;
|
||||
|
@ -24,6 +26,8 @@ import org.elasticsearch.xpack.ql.plan.logical.OrderBy;
|
|||
import org.elasticsearch.xpack.ql.type.EsField;
|
||||
import org.elasticsearch.xpack.ql.type.TypesTests;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class OptimizerTests extends ESTestCase {
|
||||
|
@ -32,6 +36,7 @@ public class OptimizerTests extends ESTestCase {
|
|||
private static final String INDEX_NAME = "test";
|
||||
private EqlParser parser = new EqlParser();
|
||||
private IndexResolution index = loadIndexResolution("mapping-default.json");
|
||||
|
||||
private static Map<String, EsField> loadEqlMapping(String name) {
|
||||
return TypesTests.loadMapping(name);
|
||||
}
|
||||
|
@ -51,9 +56,54 @@ public class OptimizerTests extends ESTestCase {
|
|||
return accept(index, eql);
|
||||
}
|
||||
|
||||
public void testIsNull() {
|
||||
List<String> tests = Arrays.asList(
|
||||
"foo where command_line == null",
|
||||
"foo where null == command_line"
|
||||
);
|
||||
|
||||
for (String q : tests) {
|
||||
LogicalPlan plan = accept(q);
|
||||
assertTrue(plan instanceof OrderBy);
|
||||
plan = ((OrderBy) plan).child();
|
||||
assertTrue(plan instanceof Filter);
|
||||
|
||||
Filter filter = (Filter) plan;
|
||||
And condition = (And) filter.condition();
|
||||
assertTrue(condition.right() instanceof IsNull);
|
||||
|
||||
IsNull check = (IsNull) condition.right();
|
||||
assertEquals(((FieldAttribute) check.field()).name(), "command_line");
|
||||
}
|
||||
}
|
||||
public void testIsNotNull() {
|
||||
List<String> tests = Arrays.asList(
|
||||
"foo where command_line != null",
|
||||
"foo where null != command_line"
|
||||
);
|
||||
|
||||
for (String q : tests) {
|
||||
LogicalPlan plan = accept(q);
|
||||
assertTrue(plan instanceof OrderBy);
|
||||
plan = ((OrderBy) plan).child();
|
||||
assertTrue(plan instanceof Filter);
|
||||
|
||||
Filter filter = (Filter) plan;
|
||||
And condition = (And) filter.condition();
|
||||
assertTrue(condition.right() instanceof IsNotNull);
|
||||
|
||||
IsNotNull check = (IsNotNull) condition.right();
|
||||
assertEquals(((FieldAttribute) check.field()).name(), "command_line");
|
||||
}
|
||||
}
|
||||
|
||||
public void testEqualsWildcard() {
|
||||
for (String q : new String[]{"foo where command_line == '* bar *'", "foo where '* bar *' == command_line"}) {
|
||||
List<String> tests = Arrays.asList(
|
||||
"foo where command_line == '* bar *'",
|
||||
"foo where '* bar *' == command_line"
|
||||
);
|
||||
|
||||
for (String q : tests) {
|
||||
LogicalPlan plan = accept(q);
|
||||
assertTrue(plan instanceof OrderBy);
|
||||
plan = ((OrderBy) plan).child();
|
||||
|
@ -72,7 +122,12 @@ public class OptimizerTests extends ESTestCase {
|
|||
}
|
||||
|
||||
public void testNotEqualsWildcard() {
|
||||
for (String q : new String[]{"foo where command_line != '* baz *'", "foo where '* baz *' != command_line"}) {
|
||||
List<String> tests = Arrays.asList(
|
||||
"foo where command_line != '* baz *'",
|
||||
"foo where '* baz *' != command_line"
|
||||
);
|
||||
|
||||
for (String q : tests) {
|
||||
|
||||
LogicalPlan plan = accept(q);
|
||||
assertTrue(plan instanceof OrderBy);
|
||||
|
|
Loading…
Reference in New Issue