#9747 Correctly compare cases

Fixes #9760
This commit is contained in:
Armin 2018-06-18 21:14:06 +02:00 committed by Armin Braun
parent c431aba536
commit 8dc46bece1
2 changed files with 266 additions and 267 deletions

View file

@ -4,15 +4,16 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.jruby.RubyInteger;
import org.jruby.RubyNumeric;
import java.util.function.Predicate;
import org.jruby.RubyString;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.util.ByteList;
import org.logstash.ConvertedList;
import org.logstash.ConvertedMap;
import org.logstash.Event;
import org.logstash.FieldReference;
import org.logstash.RubyUtil;
import org.logstash.Rubyfier;
import org.logstash.Valuefier;
import org.logstash.config.ir.expression.BinaryBooleanExpression;
import org.logstash.config.ir.expression.BooleanExpression;
@ -55,15 +56,13 @@ public interface EventCondition {
*/
final class Compiler {
/**
* {@link EventCondition} that is always {@code true}.
*/
private static final EventCondition TRUE = event -> true;
private static final Predicate<Integer> LESS_THAN = i -> i < 0;
/**
* {@link EventCondition} that is always {@code false}.
*/
private static final EventCondition FALSE = event -> false;
private static final Predicate<Integer> LESS_OR_EQUAL_THAN = i -> i <= 0;
private static final Predicate<Integer> GREATER_THAN = i -> i > 0;
private static final Predicate<Integer> GREATER_OR_EQUAL_THAN = i -> i >= 0;
/**
* Cache of all compiled {@link EventCondition}.
@ -96,24 +95,17 @@ public interface EventCondition {
condition = regex((RegexEq) expression);
} else if (expression instanceof In) {
condition = in((In) expression);
} else if (expression instanceof Or) {
condition = or(booleanPair((BinaryBooleanExpression) expression));
} else if (expression instanceof Or || expression instanceof And) {
condition = booleanCondition((BinaryBooleanExpression) expression);
} else if (expression instanceof Truthy) {
condition = truthy((Truthy) expression);
} else if (expression instanceof Not) {
condition = not((Not) expression);
} else if (expression instanceof Gt) {
condition = gt((Gt) expression);
} else if (expression instanceof Gte) {
condition = gte((Gte) expression);
} else if (expression instanceof Lt) {
condition = lt((Lt) expression);
} else if (expression instanceof Lte) {
condition = lte((Lte) expression);
} else if (expression instanceof And) {
condition = and(booleanPair((BinaryBooleanExpression) expression));
} else if (expression instanceof Gt || expression instanceof Gte
|| expression instanceof Lt || expression instanceof Lte) {
condition = comparison((BinaryBooleanExpression) expression);
} else if (expression instanceof Neq) {
condition = neq((Neq) expression);
condition = not(eq((BinaryBooleanExpression) expression));
} else {
throw new EventCondition.Compiler.UnexpectedTypeException(expression);
}
@ -122,7 +114,7 @@ public interface EventCondition {
}
}
private EventCondition[] booleanPair(final BinaryBooleanExpression expression) {
private EventCondition booleanCondition(final BinaryBooleanExpression expression) {
final Expression left = expression.getLeft();
final Expression right = expression.getRight();
final EventCondition first;
@ -143,7 +135,13 @@ public interface EventCondition {
} else {
throw new EventCondition.Compiler.UnexpectedTypeException(left, right);
}
return new EventCondition[]{first, second};
if (expression instanceof And) {
return event -> first.fulfilled(event) && second.fulfilled(event);
} else if (expression instanceof Or) {
return event -> first.fulfilled(event) || second.fulfilled(event);
} else {
throw new EventCondition.Compiler.UnexpectedTypeException(expression);
}
}
private EventCondition not(final Not not) {
@ -186,18 +184,6 @@ public interface EventCondition {
expression.getRight() instanceof EventValueExpression;
}
private static EventCondition neq(final Neq neq) {
final EventCondition condition;
final Expression uleft = neq.getLeft();
final Expression uright = neq.getRight();
if (eAndV(neq)) {
condition = not(eq((EventValueExpression) uleft, (ValueExpression) uright));
} else {
throw new EventCondition.Compiler.UnexpectedTypeException(uleft, uright);
}
return condition;
}
private static EventCondition truthy(final Truthy truthy) {
final EventCondition condition;
final Expression inner = truthy.getExpression();
@ -224,42 +210,43 @@ public interface EventCondition {
return condition;
}
private static EventCondition gte(final Gte gte) {
final EventCondition condition;
final Expression uleft = gte.getLeft();
final Expression uright = gte.getRight();
if (eAndV(gte)) {
final EventValueExpression left = (EventValueExpression) uleft;
final ValueExpression right = (ValueExpression) uright;
condition = or(gt(left, right), eq(left, right));
private static EventCondition comparison(final BinaryBooleanExpression expression) {
final Predicate<Integer> conditional;
final Predicate<Integer> converse;
if (expression instanceof Gte) {
conditional = GREATER_OR_EQUAL_THAN;
converse = LESS_OR_EQUAL_THAN;
} else if (expression instanceof Lte) {
conditional = LESS_OR_EQUAL_THAN;
converse = GREATER_OR_EQUAL_THAN;
} else if (expression instanceof Lt) {
conditional = LESS_THAN;
converse = GREATER_THAN;
} else if (expression instanceof Gt) {
conditional = GREATER_THAN;
converse = LESS_THAN;
} else {
throw new EventCondition.Compiler.UnexpectedTypeException(uleft, uright);
throw new EventCondition.Compiler.UnexpectedTypeException(expression);
}
return condition;
}
private static EventCondition lte(final Lte lte) {
final EventCondition condition;
final Expression uleft = lte.getLeft();
final Expression uright = lte.getRight();
if (eAndV(lte)) {
condition = not(gt((EventValueExpression) uleft, (ValueExpression) uright));
final Expression uleft = expression.getLeft();
final Expression uright = expression.getRight();
if (eAndV(expression)) {
condition = compareFieldToConstant(
(EventValueExpression) uleft, (ValueExpression) uright, conditional
);
} else if (vAndE(expression)) {
condition = compareFieldToConstant(
(EventValueExpression) uright, (ValueExpression) uleft, converse
);
} else if (vAndV(expression)) {
return compareConstants(
(ValueExpression) uleft, (ValueExpression) uright, conditional
);
} else {
throw new EventCondition.Compiler.UnexpectedTypeException(uleft, uright);
}
return condition;
}
private static EventCondition lt(final Lt lt) {
final EventCondition condition;
final Expression uleft = lt.getLeft();
final Expression uright = lt.getRight();
if (eAndV(lt)) {
final EventValueExpression left = (EventValueExpression) uleft;
final ValueExpression right = (ValueExpression) uright;
condition = not(or(gt(left, right), eq(left, right)));
} else {
throw new EventCondition.Compiler.UnexpectedTypeException(uleft, uright);
return compareFields(
(EventValueExpression) uleft, (EventValueExpression) uright, conditional
);
}
return condition;
}
@ -306,27 +293,27 @@ public interface EventCondition {
/**
* Compiles a constant (due to both of its sides being constant {@link ValueExpression})
* conditional into either {@link EventCondition.Compiler#TRUE} or
* {@link EventCondition.Compiler#FALSE}.
* conditional.
* @param left Constant left side {@link ValueExpression}
* @param right Constant right side {@link ValueExpression}
* @return Either {@link EventCondition.Compiler#TRUE} or
* {@link EventCondition.Compiler#FALSE}
* @return Constant {@link EventCondition}
*/
private static EventCondition in(final ValueExpression left, final ValueExpression right) {
final Object found = right.get();
final Object other = left.get();
final boolean res;
if (found instanceof ConvertedList && other instanceof RubyString) {
return ((ConvertedList) found).stream().anyMatch(item -> item.toString()
.equals(other.toString())) ? TRUE : FALSE;
res = ((ConvertedList) found).stream().anyMatch(item -> item.toString()
.equals(other.toString()));
} else if (found instanceof RubyString && other instanceof RubyString) {
return found.toString().contains(other.toString()) ? TRUE : FALSE;
res = found.toString().contains(other.toString());
} else if (found instanceof RubyString && other instanceof ConvertedList) {
return ((ConvertedList) other).stream()
.anyMatch(item -> item.toString().equals(found.toString())) ? TRUE : FALSE;
res = ((ConvertedList) other).stream()
.anyMatch(item -> item.toString().equals(found.toString()));
} else {
return found != null && other != null && found.equals(other) ? TRUE : FALSE;
res = found != null && found.equals(other);
}
return constant(res);
}
private static boolean listValueRight(final In in) {
@ -345,22 +332,16 @@ public interface EventCondition {
);
}
@SuppressWarnings("unchecked")
private static EventCondition eq(final EventValueExpression evalE,
final ValueExpression valE) {
final Object value = valE.get();
final String field = evalE.getFieldName();
if (value instanceof String) {
return new EventCondition.Compiler.FieldEqualsString(field, (String) value);
} else if (value instanceof Long || value instanceof Integer ||
value instanceof Short) {
return new EventCondition.Compiler.FieldEqualsLong(
field, ((Number) value).longValue()
);
}
throw new EventCondition.Compiler.UnexpectedTypeException(value);
return rubyFieldEquals(
(Comparable<IRubyObject>) Rubyfier.deep(RubyUtil.RUBY, valE.get()),
evalE.getFieldName()
);
}
private static EventCondition eq(final Eq equals) {
private static EventCondition eq(final BinaryBooleanExpression equals) {
final Expression left = equals.getLeft();
final Expression right = equals.getRight();
final EventCondition condition;
@ -370,47 +351,20 @@ public interface EventCondition {
condition = eq((EventValueExpression) right, (ValueExpression) left);
} else if (eAndE(equals)) {
condition = eq((EventValueExpression) left, (EventValueExpression) right);
} else if (vAndV(equals)) {
condition = ((ValueExpression) left).get()
.equals(((ValueExpression) right).get()) ? TRUE : FALSE;
} else {
throw new EventCondition.Compiler.UnexpectedTypeException(left, right);
condition = constant(
((ValueExpression) left).get().equals(((ValueExpression) right).get())
);
}
return condition;
}
private static EventCondition eq(final EventValueExpression first,
final EventValueExpression second) {
return new EventCondition.Compiler.FieldEqualsField(
FieldReference.from(first.getFieldName()), FieldReference.from(second.getFieldName())
);
}
private static EventCondition gt(final Gt greater) {
final EventCondition condition;
final Expression left = greater.getLeft();
final Expression right = greater.getRight();
if (eAndV(greater)) {
condition = gt((EventValueExpression) left, (ValueExpression) right);
} else {
throw new EventCondition.Compiler.UnexpectedTypeException(left, right);
}
return condition;
}
private static EventCondition gt(final EventValueExpression left,
final ValueExpression right) {
final Object value = right.get();
final String field = left.getFieldName();
if (value instanceof String) {
return new EventCondition.Compiler.FieldGreaterThanString(field, (String) value);
} else if (value instanceof Long || value instanceof Integer ||
value instanceof Short) {
return new EventCondition.Compiler.FieldGreaterThanNumber(
field, RubyUtil.RUBY.newFixnum(((Number) value).longValue())
);
}
throw new EventCondition.Compiler.UnexpectedTypeException(value);
final FieldReference field1 = FieldReference.from(first.getFieldName());
final FieldReference field2 = FieldReference.from(second.getFieldName());
return event -> event.getEvent().getUnconvertedField(field1)
.equals(event.getEvent().getUnconvertedField(field2));
}
private static EventCondition truthy(final EventValueExpression evalE) {
@ -418,15 +372,46 @@ public interface EventCondition {
}
private static EventCondition not(final EventCondition condition) {
return new EventCondition.Compiler.Negated(condition);
return event -> !condition.fulfilled(event);
}
private static EventCondition or(final EventCondition... conditions) {
return new EventCondition.Compiler.OrCondition(conditions[0], conditions[1]);
private static EventCondition compareConstants(final ValueExpression left,
final ValueExpression right, final Predicate<Integer> operator) {
return constant(operator.test(compare(left.get(), right.get())));
}
private static EventCondition and(final EventCondition... conditions) {
return new EventCondition.Compiler.AndCondition(conditions[0], conditions[1]);
private static EventCondition compareFields(final EventValueExpression left,
final EventValueExpression right, final Predicate<Integer> operator) {
final FieldReference one = FieldReference.from(left.getFieldName());
final FieldReference other = FieldReference.from(right.getFieldName());
return event -> {
final Event javaEvent = event.getEvent();
return operator.test(
compare(
javaEvent.getUnconvertedField(one), javaEvent.getUnconvertedField(other)
)
);
};
}
@SuppressWarnings("unchecked")
private static EventCondition compareFieldToConstant(final EventValueExpression left,
final ValueExpression right, final Predicate<Integer> operator) {
final FieldReference one = FieldReference.from(left.getFieldName());
final Comparable<IRubyObject> other =
(Comparable<IRubyObject>) Rubyfier.deep(RubyUtil.RUBY, right.get());
return event -> {
final Event javaEvent = event.getEvent();
return operator.test(compare(javaEvent.getUnconvertedField(one), other));
};
}
@SuppressWarnings("unchecked")
private static int compare(final Object left, final Object right) {
if (left instanceof Comparable<?>) {
return ((Comparable) left).compareTo(right);
}
throw new EventCondition.Compiler.UnexpectedTypeException(left, right);
}
/**
@ -444,144 +429,15 @@ public interface EventCondition {
return false;
}
private static final class Negated implements EventCondition {
private final EventCondition condition;
Negated(final EventCondition condition) {
this.condition = condition;
}
@Override
public boolean fulfilled(final JrubyEventExtLibrary.RubyEvent event) {
return !condition.fulfilled(event);
}
private static EventCondition rubyFieldEquals(final Comparable<IRubyObject> left,
final String field) {
final FieldReference reference = FieldReference.from(field);
return event ->
left.equals((IRubyObject) event.getEvent().getUnconvertedField(reference));
}
private static final class AndCondition implements EventCondition {
private final EventCondition first;
private final EventCondition second;
AndCondition(final EventCondition first, final EventCondition second) {
this.first = first;
this.second = second;
}
@Override
public boolean fulfilled(final JrubyEventExtLibrary.RubyEvent event) {
return first.fulfilled(event) && second.fulfilled(event);
}
}
private static final class OrCondition implements EventCondition {
private final EventCondition first;
private final EventCondition second;
OrCondition(final EventCondition first, final EventCondition second) {
this.first = first;
this.second = second;
}
@Override
public boolean fulfilled(final JrubyEventExtLibrary.RubyEvent event) {
return first.fulfilled(event) || second.fulfilled(event);
}
}
private static final class FieldGreaterThanString implements EventCondition {
private final FieldReference field;
private final RubyString value;
private FieldGreaterThanString(final String field, final String value) {
this.field = FieldReference.from(field);
this.value = RubyUtil.RUBY.newString(value);
}
@Override
public boolean fulfilled(final JrubyEventExtLibrary.RubyEvent event) {
return value.compareTo(
(IRubyObject) event.getEvent().getUnconvertedField(field)
) < 0;
}
}
private static final class FieldGreaterThanNumber implements EventCondition {
private final FieldReference field;
private final RubyNumeric value;
private FieldGreaterThanNumber(final String field, final RubyNumeric value) {
this.field = FieldReference.from(field);
this.value = value;
}
@Override
public boolean fulfilled(final JrubyEventExtLibrary.RubyEvent event) {
return value.compareTo(
(IRubyObject) event.getEvent().getUnconvertedField(field)
) < 0;
}
}
private static final class FieldEqualsString implements EventCondition {
private final FieldReference field;
private final RubyString value;
private FieldEqualsString(final String field, final String value) {
this.field = FieldReference.from(field);
this.value = RubyUtil.RUBY.newString(value);
}
@Override
public boolean fulfilled(final JrubyEventExtLibrary.RubyEvent event) {
final Object val = event.getEvent().getUnconvertedField(field);
return value.equals(val);
}
}
private static final class FieldEqualsLong implements EventCondition {
private final FieldReference field;
private final long value;
private FieldEqualsLong(final String field, final long value) {
this.field = FieldReference.from(field);
this.value = value;
}
@Override
public boolean fulfilled(final JrubyEventExtLibrary.RubyEvent event) {
final Object val = event.getEvent().getUnconvertedField(field);
return val instanceof RubyInteger && ((RubyInteger) val).getLongValue() == value;
}
}
private static final class FieldEqualsField implements EventCondition {
private final FieldReference one;
private final FieldReference other;
private FieldEqualsField(final FieldReference one, final FieldReference other) {
this.one = one;
this.other = other;
}
@Override
public boolean fulfilled(final JrubyEventExtLibrary.RubyEvent event) {
return event.getEvent().getUnconvertedField(one)
.equals(event.getEvent().getUnconvertedField(other));
}
private static EventCondition constant(final boolean value) {
return value ? event -> true : event -> false;
}
private static final class FieldMatches implements EventCondition {
@ -687,7 +543,7 @@ public interface EventCondition {
} else if (rfound instanceof ConvertedList) {
return contains((ConvertedList) rfound, lfound);
} else {
return lfound != null && rfound != null && lfound.equals(rfound);
return lfound != null && lfound.equals(rfound);
}
}
}
@ -749,6 +605,14 @@ public interface EventCondition {
UnexpectedTypeException(final Object inner) {
super(String.format("Unexpected input type %s", inner.getClass()));
}
UnexpectedTypeException(final Object left, final Object right) {
super(
String.format(
"Unexpected input type combination %s %s", left.getClass(), right.getClass()
)
);
}
}
}
}

View file

@ -20,6 +20,7 @@ import org.junit.Before;
import org.junit.Test;
import org.logstash.Event;
import org.logstash.RubyUtil;
import org.logstash.common.IncompleteSourceWithMetadataException;
import org.logstash.config.ir.compiler.AbstractOutputDelegatorExt;
import org.logstash.config.ir.compiler.FilterDelegatorExt;
import org.logstash.config.ir.compiler.RubyIntegration;
@ -155,6 +156,90 @@ public final class CompiledPipelineTest extends RubyEnvTestCase {
MatcherAssert.assertThat(outputEvents.contains(testEvent), CoreMatchers.is(true));
}
@Test
public void correctlyCompilesEquals() throws Exception {
final String eq = "==";
assertCorrectFieldComparison(eq, 6, false);
assertCorrectFieldComparison(eq, 7, true);
assertCorrectFieldComparison(eq, 8, false);
assertCorrectValueComparison(eq, 6, false);
assertCorrectValueComparison(eq, 7, true);
assertCorrectValueComparison(eq, 8, false);
assertCorrectFieldToFieldComparison(eq, 7, 6, false);
assertCorrectFieldToFieldComparison(eq, 7, 7, true);
assertCorrectFieldToFieldComparison(eq, 7, 8, false);
}
@Test
public void correctlyCompilesNotEquals() throws Exception {
final String eq = "!=";
assertCorrectFieldComparison(eq, 6, true);
assertCorrectFieldComparison(eq, 7, false);
assertCorrectFieldComparison(eq, 8, true);
assertCorrectValueComparison(eq, 6, true);
assertCorrectValueComparison(eq, 7, false);
assertCorrectValueComparison(eq, 8, true);
assertCorrectFieldToFieldComparison(eq, 7, 6, true);
assertCorrectFieldToFieldComparison(eq, 7, 7, false);
assertCorrectFieldToFieldComparison(eq, 7, 8, true);
}
@Test
public void correctlyCompilesGreaterThan() throws Exception {
final String gt = ">";
assertCorrectFieldComparison(gt, 6, true);
assertCorrectFieldComparison(gt, 7, false);
assertCorrectFieldComparison(gt, 8, false);
assertCorrectValueComparison(gt, 6, true);
assertCorrectValueComparison(gt, 7, false);
assertCorrectValueComparison(gt, 8, false);
assertCorrectFieldToFieldComparison(gt, 7, 6, true);
assertCorrectFieldToFieldComparison(gt, 7, 7, false);
assertCorrectFieldToFieldComparison(gt, 7, 8, false);
}
@Test
public void correctlyCompilesLessThan() throws Exception {
final String lt = "<";
assertCorrectFieldComparison(lt, 6, false);
assertCorrectFieldComparison(lt, 7, false);
assertCorrectFieldComparison(lt, 8, true);
assertCorrectValueComparison(lt, 6, false);
assertCorrectValueComparison(lt, 7, false);
assertCorrectValueComparison(lt, 8, true);
assertCorrectFieldToFieldComparison(lt, 7, 6, false);
assertCorrectFieldToFieldComparison(lt, 7, 7, false);
assertCorrectFieldToFieldComparison(lt, 7, 8, true);
}
@Test
public void correctlyCompilesLessOrEqualThan() throws Exception {
final String lte = "<=";
assertCorrectFieldComparison(lte, 6, false);
assertCorrectFieldComparison(lte, 7, true);
assertCorrectFieldComparison(lte, 8, true);
assertCorrectValueComparison(lte, 6, false);
assertCorrectValueComparison(lte, 7, true);
assertCorrectValueComparison(lte, 8, true);
assertCorrectFieldToFieldComparison(lte, 7, 6, false);
assertCorrectFieldToFieldComparison(lte, 7, 7, true);
assertCorrectFieldToFieldComparison(lte, 7, 8, true);
}
@Test
public void correctlyCompilesGreaterOrEqualThan() throws Exception {
final String gte = ">=";
assertCorrectFieldComparison(gte, 6, true);
assertCorrectFieldComparison(gte, 7, true);
assertCorrectFieldComparison(gte, 8, false);
assertCorrectValueComparison(gte, 6, true);
assertCorrectValueComparison(gte, 7, true);
assertCorrectValueComparison(gte, 8, false);
assertCorrectFieldToFieldComparison(gte, 7, 6, true);
assertCorrectFieldToFieldComparison(gte, 7, 7, true);
assertCorrectFieldToFieldComparison(gte, 7, 8, false);
}
@Test
public void conditionalNestedMetaFieldPipeline() throws Exception {
final PipelineIR pipelineIR = ConfigCompiler.configToPipelineIR(
@ -212,6 +297,56 @@ public final class CompiledPipelineTest extends RubyEnvTestCase {
MatcherAssert.assertThat(outputEvents.contains(testEvent), CoreMatchers.is(true));
}
private void assertCorrectValueComparison(final String op, final int value,
final boolean expected) throws Exception {
final Event event = new Event();
verifyComparison(expected, String.format("7 %s %d ", op, value), event);
}
private void assertCorrectFieldComparison(final String op, final int value,
final boolean expected) throws Exception {
final Event event = new Event();
event.setField("baz", value);
verifyComparison(expected, String.format("7 %s [baz]", op), event);
}
private void assertCorrectFieldToFieldComparison(final String op, final int value1,
final int value2, final boolean expected) throws Exception {
final Event event = new Event();
event.setField("brr", value1);
event.setField("baz", value2);
verifyComparison(expected, String.format("[brr] %s [baz]", op), event);
}
private void verifyComparison(final boolean expected, final String conditional,
final Event event) throws IncompleteSourceWithMetadataException {
final JrubyEventExtLibrary.RubyEvent testEvent =
JrubyEventExtLibrary.RubyEvent.newRubyEvent(RubyUtil.RUBY, event);
new CompiledPipeline(
ConfigCompiler.configToPipelineIR(
"input {mockinput{}} filter { " +
String.format("if %s { ", conditional) +
" mockaddfilter {} " +
"} " +
"} output {mockoutput{} }",
false
),
new CompiledPipelineTest.MockPluginFactory(
Collections.singletonMap("mockinput", () -> null),
Collections.singletonMap("mockaddfilter", () -> ADD_FIELD_FILTER),
Collections.singletonMap("mockoutput", mockOutputSupplier())
)
).buildExecution()
.compute(RubyUtil.RUBY.newArray(testEvent), false, false);
final Collection<JrubyEventExtLibrary.RubyEvent> outputEvents = EVENT_SINKS.get(runId);
MatcherAssert.assertThat(outputEvents.size(), CoreMatchers.is(1));
MatcherAssert.assertThat(outputEvents.contains(testEvent), CoreMatchers.is(true));
MatcherAssert.assertThat(
event.getField("foo"), CoreMatchers.is(expected ? "bar" : null)
);
outputEvents.clear();
}
private Supplier<Consumer<Collection<JrubyEventExtLibrary.RubyEvent>>> mockOutputSupplier() {
return () -> events -> events.forEach(
event -> EVENT_SINKS.get(runId).add((JrubyEventExtLibrary.RubyEvent) event)