-
Notifications
You must be signed in to change notification settings - Fork 207
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
FEAT: operators in data-prepper expression #1065
Changes from 7 commits
fce1e77
0106e92
cc492ea
d175a29
bb9669b
3c7d917
6fe9dfc
da97f79
9e66532
b57a1df
50f47df
73a2d71
5276cef
90817cf
da229ec
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package org.opensearch.dataprepper.expression; | ||
|
||
import org.opensearch.dataprepper.expression.antlr.DataPrepperExpressionParser; | ||
|
||
import static com.google.common.base.Preconditions.checkArgument; | ||
|
||
class AndOperator implements Operator<Boolean> { | ||
@Override | ||
public Integer getSymbol() { | ||
return DataPrepperExpressionParser.AND; | ||
} | ||
|
||
@Override | ||
public Boolean evaluate(Object... args) { | ||
checkArgument(args.length == 2, "Operands length needs to be 2."); | ||
checkArgument(args[0] instanceof Boolean, "Left operand needs to be Boolean."); | ||
checkArgument(args[1] instanceof Boolean, "Right Operand needs to be Boolean."); | ||
return ((Boolean) args[0]) && ((Boolean) args[1]); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package org.opensearch.dataprepper.expression; | ||
|
||
class BinaryOperandsKeyFactory { | ||
static String typesKey(final Object leftValue, final Object rightValue) { | ||
return leftValue.getClass().getName() + "_" + rightValue.getClass().getName(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package org.opensearch.dataprepper.expression; | ||
|
||
import org.opensearch.dataprepper.expression.antlr.DataPrepperExpressionParser; | ||
|
||
import static com.google.common.base.Preconditions.checkArgument; | ||
|
||
class EqualOperator implements Operator<Boolean> { | ||
|
||
@Override | ||
public Integer getSymbol() { | ||
return DataPrepperExpressionParser.EQUAL; | ||
} | ||
|
||
@Override | ||
public Boolean evaluate(Object... args) { | ||
checkArgument(args.length == 2, "Operands length needs to be 2."); | ||
if ((args[0] == null) || (args[1] == null)) { | ||
return args[0] == null && args[1] == null; | ||
} | ||
return args[0].equals(args[1]); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package org.opensearch.dataprepper.expression; | ||
|
||
import org.opensearch.dataprepper.expression.antlr.DataPrepperExpressionParser; | ||
|
||
import java.util.HashMap; | ||
import java.util.Map; | ||
import java.util.function.BiFunction; | ||
|
||
import static com.google.common.base.Preconditions.checkArgument; | ||
|
||
class GreaterThanOperator implements Operator<Boolean> { | ||
private static final Map<String, BiFunction<Object, Object, Boolean>> GREATER_THAN_ON = | ||
new HashMap<String, BiFunction<Object, Object, Boolean>>() {{ | ||
put(Integer.class.getName() + "_" + Integer.class.getName(), (a, b) -> (Integer) a > (Integer) b); | ||
put(Integer.class.getName() + "_" + Float.class.getName(), (a, b) -> (Integer) a > (Float) b); | ||
put(Float.class.getName() + "_" + Integer.class.getName(), (a, b) -> (Float) a > (Integer) b); | ||
put(Float.class.getName() + "_" + Float.class.getName(), (a, b) -> (Float) a > (Float) b);}}; | ||
|
||
@Override | ||
public Integer getSymbol() { | ||
return DataPrepperExpressionParser.GT; | ||
} | ||
|
||
@Override | ||
public Boolean evaluate(Object... args) { | ||
checkArgument(args.length == 2, "Operands length needs to be 2."); | ||
final Object leftValue = args[0]; | ||
final Object rightValue = args[1]; | ||
final BiFunction<Object, Object, Boolean> operation = GREATER_THAN_ON.get( | ||
BinaryOperandsKeyFactory.typesKey(leftValue, rightValue)); | ||
if (operation == null) { | ||
throw new IllegalArgumentException(leftValue + " or " + rightValue + " should be either Float or Integer"); | ||
} | ||
return operation.apply(leftValue, rightValue); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package org.opensearch.dataprepper.expression; | ||
|
||
import org.opensearch.dataprepper.expression.antlr.DataPrepperExpressionParser; | ||
|
||
import java.util.HashMap; | ||
import java.util.Map; | ||
import java.util.function.BiFunction; | ||
|
||
import static com.google.common.base.Preconditions.checkArgument; | ||
|
||
class GreaterThanOrEqualOperator implements Operator<Boolean> { | ||
private static final Map<String, BiFunction<Object, Object, Boolean>> GREATER_THAN_OR_EQUAL_ON = | ||
new HashMap<String, BiFunction<Object, Object, Boolean>>() {{ | ||
put(Integer.class.getName() + "_" + Integer.class.getName(), (a, b) -> (Integer) a >= (Integer) b); | ||
put(Integer.class.getName() + "_" + Float.class.getName(), (a, b) -> (Integer) a >= (Float) b); | ||
put(Float.class.getName() + "_" + Integer.class.getName(), (a, b) -> (Float) a >= (Integer) b); | ||
put(Float.class.getName() + "_" + Float.class.getName(), (a, b) -> (Float) a >= (Float) b);}}; | ||
|
||
@Override | ||
public Integer getSymbol() { | ||
return DataPrepperExpressionParser.GTE; | ||
} | ||
|
||
@Override | ||
public Boolean evaluate(Object... args) { | ||
checkArgument(args.length == 2, "Operands length needs to be 2."); | ||
final Object leftValue = args[0]; | ||
final Object rightValue = args[1]; | ||
final BiFunction<Object, Object, Boolean> operation = GREATER_THAN_OR_EQUAL_ON.get( | ||
BinaryOperandsKeyFactory.typesKey(leftValue, rightValue)); | ||
if (operation == null) { | ||
throw new IllegalArgumentException(leftValue + " or " + rightValue + " should be either Float or Integer"); | ||
} | ||
return operation.apply(leftValue, rightValue); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I really like @dinujoh 's suggestion and I think we can continue in this direction to reduce the duplicate code as well. Now that the operation logic has been reduced to a map, we can pull out all the boilerplate code and inject the unique map for each operator through a constructor. The
Then we can build these instances as part of our DI.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IIUC, we group all NumericComparisonOperator instantiation into a factory class? |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package org.opensearch.dataprepper.expression; | ||
|
||
import org.opensearch.dataprepper.expression.antlr.DataPrepperExpressionParser; | ||
|
||
import java.util.Set; | ||
|
||
import static com.google.common.base.Preconditions.checkArgument; | ||
|
||
class InOperator implements Operator<Boolean> { | ||
@Override | ||
public Integer getSymbol() { | ||
return DataPrepperExpressionParser.IN_SET; | ||
} | ||
|
||
@Override | ||
public Boolean evaluate(Object... args) { | ||
checkArgument(args.length == 2, "Operands length needs to be 2."); | ||
if (!(args[1] instanceof Set)) { | ||
throw new IllegalArgumentException(args[1] + " should be Set"); | ||
} | ||
return ((Set<?>) args[1]).contains(args[0]); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package org.opensearch.dataprepper.expression; | ||
|
||
import org.opensearch.dataprepper.expression.antlr.DataPrepperExpressionParser; | ||
|
||
import java.util.HashMap; | ||
import java.util.Map; | ||
import java.util.function.BiFunction; | ||
|
||
import static com.google.common.base.Preconditions.checkArgument; | ||
|
||
class LessThanOperator implements Operator<Boolean> { | ||
private static final Map<String, BiFunction<Object, Object, Boolean>> LESS_THAN_ON = | ||
new HashMap<String, BiFunction<Object, Object, Boolean>>() {{ | ||
put(Integer.class.getName() + "_" + Integer.class.getName(), (a, b) -> (Integer) a < (Integer) b); | ||
put(Integer.class.getName() + "_" + Float.class.getName(), (a, b) -> (Integer) a < (Float) b); | ||
put(Float.class.getName() + "_" + Integer.class.getName(), (a, b) -> (Float) a < (Integer) b); | ||
put(Float.class.getName() + "_" + Float.class.getName(), (a, b) -> (Float) a < (Float) b);}}; | ||
|
||
@Override | ||
public Integer getSymbol() { | ||
return DataPrepperExpressionParser.LT; | ||
} | ||
|
||
@Override | ||
public Boolean evaluate(Object... args) { | ||
checkArgument(args.length == 2, "Operands length needs to be 2."); | ||
final Object leftValue = args[0]; | ||
final Object rightValue = args[1]; | ||
final BiFunction<Object, Object, Boolean> operation = LESS_THAN_ON.get( | ||
BinaryOperandsKeyFactory.typesKey(leftValue, rightValue)); | ||
if (operation == null) { | ||
throw new IllegalArgumentException(leftValue + " or " + rightValue + " should be either Float or Integer"); | ||
} | ||
return operation.apply(leftValue, rightValue); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package org.opensearch.dataprepper.expression; | ||
|
||
import org.opensearch.dataprepper.expression.antlr.DataPrepperExpressionParser; | ||
|
||
import java.util.HashMap; | ||
import java.util.Map; | ||
import java.util.function.BiFunction; | ||
|
||
import static com.google.common.base.Preconditions.checkArgument; | ||
|
||
class LessThanOrEqualOperator implements Operator<Boolean> { | ||
private static final Map<String, BiFunction<Object, Object, Boolean>> LESS_THAN_OR_EQUAL_ON = | ||
new HashMap<String, BiFunction<Object, Object, Boolean>>() {{ | ||
put(Integer.class.getName() + "_" + Integer.class.getName(), (a, b) -> (Integer) a <= (Integer) b); | ||
put(Integer.class.getName() + "_" + Float.class.getName(), (a, b) -> (Integer) a <= (Float) b); | ||
put(Float.class.getName() + "_" + Integer.class.getName(), (a, b) -> (Float) a <= (Integer) b); | ||
put(Float.class.getName() + "_" + Float.class.getName(), (a, b) -> (Float) a <= (Float) b);}}; | ||
|
||
@Override | ||
public Integer getSymbol() { | ||
return DataPrepperExpressionParser.LTE; | ||
} | ||
|
||
@Override | ||
public Boolean evaluate(Object... args) { | ||
checkArgument(args.length == 2, "Operands length needs to be 2."); | ||
final Object leftValue = args[0]; | ||
final Object rightValue = args[1]; | ||
final BiFunction<Object, Object, Boolean> operation = LESS_THAN_OR_EQUAL_ON.get( | ||
BinaryOperandsKeyFactory.typesKey(leftValue, rightValue)); | ||
if (operation == null) { | ||
throw new IllegalArgumentException(leftValue + " or " + rightValue + " should be either Float or Integer"); | ||
} | ||
return operation.apply(leftValue, rightValue); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package org.opensearch.dataprepper.expression; | ||
|
||
import org.opensearch.dataprepper.expression.antlr.DataPrepperExpressionParser; | ||
|
||
import static com.google.common.base.Preconditions.checkArgument; | ||
|
||
class NotEqualOperator implements Operator<Boolean> { | ||
|
||
@Override | ||
public Integer getSymbol() { | ||
return DataPrepperExpressionParser.NOT_EQUAL; | ||
} | ||
|
||
@Override | ||
public Boolean evaluate(Object... args) { | ||
checkArgument(args.length == 2, "Operands length needs to be 2."); | ||
if ((args[0] == null) || (args[1] == null)) { | ||
return args[0] != null || args[1] != null; | ||
} | ||
return !args[0].equals(args[1]); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package org.opensearch.dataprepper.expression; | ||
|
||
import org.opensearch.dataprepper.expression.antlr.DataPrepperExpressionParser; | ||
|
||
import java.util.Set; | ||
|
||
import static com.google.common.base.Preconditions.checkArgument; | ||
|
||
class NotInOperator implements Operator<Boolean> { | ||
|
||
@Override | ||
public Integer getSymbol() { | ||
return DataPrepperExpressionParser.NOT_IN_SET; | ||
} | ||
|
||
@Override | ||
public Boolean evaluate(Object... args) { | ||
checkArgument(args.length == 2, "Operands length needs to be 2."); | ||
checkArgument(args[1] instanceof Set, "Right Operand needs to be Set."); | ||
return !((Set<?>) args[1]).contains(args[0]); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package org.opensearch.dataprepper.expression; | ||
|
||
import org.opensearch.dataprepper.expression.antlr.DataPrepperExpressionParser; | ||
|
||
import static com.google.common.base.Preconditions.checkArgument; | ||
|
||
class NotOperator implements Operator<Boolean> { | ||
@Override | ||
public Integer getSymbol() { | ||
return DataPrepperExpressionParser.NOT; | ||
} | ||
|
||
@Override | ||
public Boolean evaluate(Object... args) { | ||
checkArgument(args.length == 1, "Operands length needs to be 1."); | ||
checkArgument(args[0] instanceof Boolean, "Operand needs to be Boolean."); | ||
return !((Boolean) args[0]); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package org.opensearch.dataprepper.expression; | ||
|
||
interface Operator<T> { | ||
Integer getSymbol(); | ||
Comment on lines
+8
to
+9
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All interfaces should be documented. Please add java docs. |
||
|
||
/** | ||
* @since 1.3 | ||
* Placeholder interface for implementing Data-Prepper supported binary/unary operations on operands that | ||
* returns custom type T. | ||
* @param args operands | ||
* @return T | ||
*/ | ||
T evaluate(final Object... args); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package org.opensearch.dataprepper.expression; | ||
|
||
import org.opensearch.dataprepper.expression.antlr.DataPrepperExpressionParser; | ||
|
||
import static com.google.common.base.Preconditions.checkArgument; | ||
|
||
class OrOperator implements Operator<Boolean> { | ||
|
||
@Override | ||
public Integer getSymbol() { | ||
return DataPrepperExpressionParser.OR; | ||
} | ||
|
||
@Override | ||
public Boolean evaluate(Object... args) { | ||
checkArgument(args.length == 2, "Operands length needs to be 2."); | ||
checkArgument(args[0] instanceof Boolean, "Left operand needs to be Boolean."); | ||
checkArgument(args[1] instanceof Boolean, "Right Operand needs to be Boolean."); | ||
return ((Boolean) args[0]) || ((Boolean) args[1]); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This might be a higher level question for @sbayer55. What the user experience like with this error message actionable for our users? How will I, as a user, know which Operands length needs to be adjusted?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do not have the full context. But my guess is that L31 should always given the expression parser and correct parseTreeEvaluator evaluation logic that is supposed to be consistent with the ANTLR grammar
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are a couple of safety checks before the operator is called.
The exception message could be more specific, saying something like "Greater than operator requires two arguments". The evaluator class will then catch that exception and can provide more context on where in the statement the error occurred. I don't think it would make sense to expose that level of information to the operator instance.
Issue 1003 has a sequence flow diagram to give additional context.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sbayer55 Thanks. IMO, to provide more specific exception message with operator type encoded, we might need to rethink on the design of NumericCompareOperator or NegateOperator that have been proposed. @cmanning09 thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After a second look, we may inject operator type string e.g. ">=" into
NumericCompareOperator
but have not yet found a way for NegateOperator.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have you considered a negate operator that internally holds a reference to another operator whose results are negated?