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

* Simplify equals/not-equals TRUE/FALSE expressions, by returning them
as is (TRUE variant) or negating them (FALSE variant)

(cherry picked from commit 17858afbe6da5fa0b3ecfc537cabb337e4baaffe)
This commit is contained in:
Andrei Stefan 2020-05-07 03:02:04 +03:00 committed by GitHub
parent 8e570300eb
commit 980f175222
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 109 additions and 12 deletions

View File

@ -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(),

View File

@ -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"}

View File

@ -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;

View File

@ -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
//