Browse Source

EQL: simplify equals/not-equals TRUE/FALSE expressions (#56191)

* Simplify equals/not-equals TRUE/FALSE expressions, by returning them
as is (TRUE variant) or negating them (FALSE variant)
Andrei Stefan 5 years ago
parent
commit
17858afbe6

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

@@ -15,6 +15,7 @@ import org.elasticsearch.xpack.ql.expression.predicate.operator.comparison.Binar
 import org.elasticsearch.xpack.ql.expression.predicate.operator.comparison.Equals;
 import org.elasticsearch.xpack.ql.expression.predicate.operator.comparison.NotEquals;
 import org.elasticsearch.xpack.ql.expression.predicate.regex.Like;
+import org.elasticsearch.xpack.ql.optimizer.OptimizerRules.BooleanEqualsSimplification;
 import org.elasticsearch.xpack.ql.optimizer.OptimizerRules.BooleanLiteralsOnTheRight;
 import org.elasticsearch.xpack.ql.optimizer.OptimizerRules.BooleanSimplification;
 import org.elasticsearch.xpack.ql.optimizer.OptimizerRules.CombineBinaryComparisons;
@@ -47,6 +48,7 @@ public class Optimizer extends RuleExecutor<LogicalPlan> {
                 // boolean
                 new BooleanSimplification(),
                 new BooleanLiteralsOnTheRight(),
+                new BooleanEqualsSimplification(),
                 // needs to occur before BinaryComparison combinations
                 new ReplaceWildcards(),
                 new ReplaceNullChecks(),

+ 60 - 10
x-pack/plugin/eql/src/test/resources/queryfolder_tests.txt

@@ -13,48 +13,57 @@
 
 
 basic
-process where true;
+process where true
+;
 null
 ;
 
 singleNumericFilterEquals
-process where serial_event_id = 1;
+process where serial_event_id = 1
+;
 "term":{"serial_event_id":{"value":1
 ;
 
 singleNumericFilterLess
-process where serial_event_id < 4;
+process where serial_event_id < 4
+;
 "range":{"serial_event_id":{"from":null,"to":4,"include_lower":false,"include_upper":false
 ;
 
 singleNumericFilterLessEquals
-process where serial_event_id <= 4;
+process where serial_event_id <= 4
+;
 "range":{"serial_event_id":{"from":null,"to":4,"include_lower":false,"include_upper":true
 ;
 
 singleNumericFilterGreater
-process where serial_event_id > 4;
+process where serial_event_id > 4
+;
 "range":{"serial_event_id":{"from":4,"to":null,"include_lower":false,"include_upper":false
 ;
 
 singleNumericFilterGreaterEquals
-process where serial_event_id >= 4;
+process where serial_event_id >= 4
+;
 "range":{"serial_event_id":{"from":4,"to":null,"include_lower":true,"include_upper":false
 ;
 
 mixedTypeFilter
-process where process_name == "notepad.exe" or (serial_event_id < 4.5 and serial_event_id >= 3.1);
+process where process_name == "notepad.exe" or (serial_event_id < 4.5 and serial_event_id >= 3.1)
+;
 "term":{"process_name":{"value":"notepad.exe"
 "range":{"serial_event_id":{"from":3.1,"to":4.5,"include_lower":true,"include_upper":false
 ;
 
 notFilter
-process where not (exit_code > -1);
+process where not (exit_code > -1)
+;
 "range":{"exit_code":{"from":null,"to":-1,"include_lower":false,"include_upper":true
 ;
 
 inFilter
-process where process_name in ("python.exe", "SMSS.exe", "explorer.exe");
+process where process_name in ("python.exe", "SMSS.exe", "explorer.exe")
+;
 "terms":{"process_name":["python.exe","SMSS.exe","explorer.exe"],
 ;
 
@@ -65,6 +74,46 @@ process where process_path == "*\\red_ttp\\wininit.*" and opcode in (0,1,2,3)
 {"terms":{"opcode":[0,1,2,3]
 ;
 
+functionEqualsTrue
+process where cidrMatch(source_address, "10.0.0.0/8") == true
+;
+{"bool":{"must":[{"term":{"event.category":{"value":"process"
+{"term":{"source_address":{"value":"10.0.0.0/8"
+;
+
+functionEqualsFalse
+process where cidrMatch(source_address, "10.0.0.0/8") == false
+;
+{"bool":{"must":[{"term":{"event.category":{"value":"process"
+{"bool":{"must_not":[{"term":{"source_address":{"value":"10.0.0.0/8"
+;
+
+functionNotEqualsTrue
+process where cidrMatch(source_address, "10.0.0.0/8") != true
+;
+{"bool":{"must":[{"term":{"event.category":{"value":"process"
+{"bool":{"must_not":[{"term":{"source_address":{"value":"10.0.0.0/8"
+;
+
+functionNotEqualsFalse
+process where cidrMatch(source_address, "10.0.0.0/8") != false
+;
+{"bool":{"must":[{"term":{"event.category":{"value":"process"
+{"term":{"source_address":{"value":"10.0.0.0/8"
+;
+
+twoFunctionsEqualsBooleanLiterals
+process where endsWith(process_path, 'x') == true and endsWith(process_path, 'yx') == false
+;
+{"bool":{"must":[{"term":{"event.category":{"value":"process",
+{"bool":{"must":[{"script":{"script":{"source":"InternalQlScriptUtils.nullSafeFilter(
+InternalEqlScriptUtils.endsWith(InternalQlScriptUtils.docValue(doc,params.v0),params.v1))","lang":"painless",
+"params":{"v0":"process_path","v1":"x"}}
+{"script":{"script":{"source":"InternalQlScriptUtils.nullSafeFilter(InternalQlScriptUtils.not(
+InternalEqlScriptUtils.endsWith(InternalQlScriptUtils.docValue(doc,params.v0),params.v1)))","lang":"painless",
+"params":{"v0":"process_path","v1":"yx"}}
+;
+
 endsWithFunction
 process where endsWith(user_name, 'c')
 ;
@@ -114,7 +163,8 @@ InternalQlScriptUtils.docValue(doc,params.v0),params.v1))"
 ;
 
 stringFunction
-process where string(pid) == "123";
+process where string(pid) == "123"
+;
 "script":{"source":"InternalQlScriptUtils.nullSafeFilter(InternalQlScriptUtils.eq(
 InternalEqlScriptUtils.string(InternalQlScriptUtils.docValue(doc,params.v0)),params.v1))",
 "params":{"v0":"pid","v1":"123"}

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

@@ -62,6 +62,34 @@ public final class OptimizerRules {
             return e.foldable() ? Literal.of(e) : e;
         }
     }
+
+    /**
+     * This rule must always be placed after {@link BooleanLiteralsOnTheRight}, since it looks at TRUE/FALSE literals' existence
+     * on the right hand-side of the {@link Equals}/{@link NotEquals} expressions.
+     */
+    public static final class BooleanEqualsSimplification extends OptimizerExpressionRule {
+
+        public BooleanEqualsSimplification() {
+            super(TransformDirection.UP);
+        }
+
+        @Override
+        protected Expression rule(Expression e) {
+            if (e instanceof Equals || e instanceof NotEquals) {
+                // for expression "==" or "!=" TRUE/FALSE, return the expression itself or its negated variant
+                BinaryComparison bc = (BinaryComparison) e;
+
+                if (TRUE.equals(bc.right())) {
+                    return e instanceof Equals ? bc.left() : new Not(bc.left().source(), bc.left());
+                }
+                if (FALSE.equals(bc.right())) {
+                    return e instanceof Equals ? new Not(bc.left().source(), bc.left()) : bc.left();
+                }
+            }
+
+            return e;
+        }
+    }
     
     public static final class BooleanSimplification extends OptimizerExpressionRule {
 
@@ -255,7 +283,7 @@ public final class OptimizerRules {
                                     if (comp != null) {
                                         // var cannot be equal to two different values at the same time
                                         if (comp != 0) {
-                                        return new Literal(and.source(), Boolean.FALSE, DataTypes.BOOLEAN);
+                                            return new Literal(and.source(), Boolean.FALSE, DataTypes.BOOLEAN);
                                         }
                                     }
                                 }
@@ -263,7 +291,7 @@ public final class OptimizerRules {
                         equals.add(otherEq);
                     } else {
                         exps.add(otherEq);
-                        }
+                    }
                 } else if (ex instanceof GreaterThan || ex instanceof GreaterThanOrEqual ||
                     ex instanceof LessThan || ex instanceof LessThanOrEqual) {
                     BinaryComparison bc = (BinaryComparison) ex;

+ 17 - 0
x-pack/plugin/ql/src/test/java/org/elasticsearch/xpack/ql/optimizer/OptimizerRulesTests.java

@@ -34,6 +34,7 @@ import org.elasticsearch.xpack.ql.expression.predicate.regex.Like;
 import org.elasticsearch.xpack.ql.expression.predicate.regex.LikePattern;
 import org.elasticsearch.xpack.ql.expression.predicate.regex.RLike;
 import org.elasticsearch.xpack.ql.expression.predicate.regex.RLikePattern;
+import org.elasticsearch.xpack.ql.optimizer.OptimizerRules.BooleanEqualsSimplification;
 import org.elasticsearch.xpack.ql.optimizer.OptimizerRules.BooleanLiteralsOnTheRight;
 import org.elasticsearch.xpack.ql.optimizer.OptimizerRules.BooleanSimplification;
 import org.elasticsearch.xpack.ql.optimizer.OptimizerRules.CombineBinaryComparisons;
@@ -272,6 +273,22 @@ public class OptimizerRulesTests extends ESTestCase {
         assertEquals(expected, simplification.rule(actual));
     }
 
+    public void testBoolEqualsSimplification() {
+        BooleanEqualsSimplification s = new BooleanEqualsSimplification();
+
+        assertEquals(DUMMY_EXPRESSION, s.rule(new Equals(EMPTY, DUMMY_EXPRESSION, TRUE)));
+        assertEquals(new Not(EMPTY, DUMMY_EXPRESSION), s.rule(new Equals(EMPTY, DUMMY_EXPRESSION, FALSE)));
+
+        assertEquals(new Not(EMPTY, DUMMY_EXPRESSION), s.rule(notEqualsOf(DUMMY_EXPRESSION, TRUE)));
+        assertEquals(DUMMY_EXPRESSION, s.rule(notEqualsOf(DUMMY_EXPRESSION, FALSE)));
+
+        assertEquals(NULL, s.rule(new Equals(EMPTY, NULL, TRUE)));
+        assertEquals(new Not(EMPTY, NULL), s.rule(new Equals(EMPTY, NULL, FALSE)));
+
+        assertEquals(new Not(EMPTY, NULL), s.rule(notEqualsOf(NULL, TRUE)));
+        assertEquals(NULL, s.rule(notEqualsOf(NULL, FALSE)));
+    }
+
     //
     // Range optimization
     //