From f5f922c6f681b78091877909c829b2a1749e01f1 Mon Sep 17 00:00:00 2001 From: Ross Wolf <31489089+rw-access@users.noreply.github.com> Date: Mon, 9 Mar 2020 10:40:20 -0600 Subject: [PATCH] EQL: Add IsNull/IsNotNull checks (#52791) * EQL: Add IsNull/IsNotNull checks * EQL: Simplify IsNull/IsNotNull optimization * EQL: Split string tests over multiple lines --- .../xpack/eql/optimizer/Optimizer.java | 27 +++++++++ .../xpack/eql/optimizer/OptimizerTests.java | 59 ++++++++++++++++++- 2 files changed, 84 insertions(+), 2 deletions(-) diff --git a/x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/optimizer/Optimizer.java b/x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/optimizer/Optimizer.java index 979c6223800..aea8042c07e 100644 --- a/x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/optimizer/Optimizer.java +++ b/x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/optimizer/Optimizer.java @@ -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 { 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 { }); } } + + private static class ReplaceNullChecks extends OptimizerRule { + + @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; + }); + } + } } diff --git a/x-pack/plugin/eql/src/test/java/org/elasticsearch/xpack/eql/optimizer/OptimizerTests.java b/x-pack/plugin/eql/src/test/java/org/elasticsearch/xpack/eql/optimizer/OptimizerTests.java index 6b53fec8caa..bbd589331b8 100644 --- a/x-pack/plugin/eql/src/test/java/org/elasticsearch/xpack/eql/optimizer/OptimizerTests.java +++ b/x-pack/plugin/eql/src/test/java/org/elasticsearch/xpack/eql/optimizer/OptimizerTests.java @@ -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 loadEqlMapping(String name) { return TypesTests.loadMapping(name); } @@ -51,9 +56,54 @@ public class OptimizerTests extends ESTestCase { return accept(index, eql); } + public void testIsNull() { + List 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 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 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 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);