Skip to content
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

Add support for basic arithmetic and string returning expressions to DataPrepper Expression #2697

Merged
merged 4 commits into from
May 19, 2023

Conversation

kkondaka
Copy link
Collaborator

@kkondaka kkondaka commented May 15, 2023

Description

Add support for basic arithmetic and string returning expressions to DataPrepper Expression.

Adds support for expressions returning arithmetic (integer/float) values from function/JsonPointers/literals

Also, adds support for expressions returning string values from function/JsonPointers/literals

Partially addresses the issues #2686 and #2685
Resolves #2703

Issues Resolved

#2703

Check List

  • [X ] New functionality includes testing.
  • New functionality has been documented.
    • New functionality has javadoc added
  • [ X] Commits are signed with a real name per the DCO

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
For more information on following Developer Certificate of Origin and signing off your commits, please check here.

@codecov
Copy link

codecov bot commented May 16, 2023

Codecov Report

Merging #2697 (08c40a6) into main (a540932) will increase coverage by 0.00%.
The diff coverage is 90.90%.

@@            Coverage Diff            @@
##               main    #2697   +/-   ##
=========================================
  Coverage     93.61%   93.61%           
- Complexity     2267     2269    +2     
=========================================
  Files           263      264    +1     
  Lines          6330     6331    +1     
  Branches        526      526           
=========================================
+ Hits           5926     5927    +1     
  Misses          266      266           
  Partials        138      138           
Impacted Files Coverage Δ
...h/dataprepper/pipeline/router/RouterAppConfig.java 0.00% <ø> (ø)
...lugins/processor/parsejson/ParseJsonProcessor.java 86.88% <0.00%> (ø)
...ch/dataprepper/expression/ExpressionEvaluator.java 100.00% <100.00%> (ø)
...taprepper/pipeline/router/RouteEventEvaluator.java 100.00% <100.00%> (ø)
...rch/dataprepper/pipeline/router/RouterFactory.java 100.00% <100.00%> (ø)
...prepper/expression/GenericExpressionEvaluator.java 100.00% <100.00%> (ø)
...rch/dataprepper/expression/ParseTreeEvaluator.java 100.00% <100.00%> (ø)

…l types of expressions

Signed-off-by: Krishna Kondaka <krishkdk@amazon.com>
Signed-off-by: Krishna Kondaka <krishkdk@amazon.com>
@Test
public void testDefaultEvaluateConditionalThrows() {
expressionEvaluator = new TestExpressionEvaluator();
assertThrows(ClassCastException.class, () -> expressionEvaluator.evaluateConditional("/status", event("{\"status\":200}")));
Copy link
Member

@graytaylor0 graytaylor0 May 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not related to this PR, but I don't love how a conditional expression of /status throws this Exception. I would prefer /status to check if the key exists, and /status == true to actually evaluate if the value is true.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This raises an interesting point which I didn't consider.

By combining the expression evaluator, there really is no way to evaluate one expression with some conditional goal in mind versus evaluating with an object in mind.

I don't think that should hinder this PR because I think it could be resolved with some internal refactoring.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already have a way to check for existence /status == null would check for key exists or not. As David said, that functionality is not new.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes that does work, I'm just saying I think /status as an alternative to /status != null is cleaner.

import static org.hamcrest.CoreMatchers.not;
import org.apache.commons.lang3.RandomStringUtils;

class ArithmeticExpressionEvaluatorIT {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since there is no ArithmeticExpressionEvaluator class, is this really an IT for GenericExpressionEvaluator?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another approach that is fairly common in the Java world is to split tests using an underscore. This is testing the GenericExpressionEvaluator for arithmetic and you have another testing GenericExpressionEvaluator for strings.

Thus:

  • GenericExpressionEvaluator_ArithmeticIT
  • GenericExpressionEvaluator_StringIT

This has the benefit of putting them next to each other in file tree UIs.

I'm fine keeping what you have or using this pattern.

Copy link
Collaborator Author

@kkondaka kkondaka May 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will rename to GenericExpressionEvaluator_ArithmeticIT and GenericExpressionEvaluator_StringIT


public class ExpressionEvaluatorTest {
private ExpressionEvaluator expressionEvaluator;
class TestExpressionEvaluator implements ExpressionEvaluator {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could use a mock and tell it to use real methods for the default method. But, it's ok this way too. I'm just pointing out that possibility.

@Test
public void testDefaultEvaluateConditionalThrows() {
expressionEvaluator = new TestExpressionEvaluator();
assertThrows(ClassCastException.class, () -> expressionEvaluator.evaluateConditional("/status", event("{\"status\":200}")));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This raises an interesting point which I didn't consider.

By combining the expression evaluator, there really is no way to evaluate one expression with some conditional goal in mind versus evaluating with an object in mind.

I don't think that should hinder this PR because I think it could be resolved with some internal refactoring.

try {
final ParseTreeEvaluatorListener listener = new ParseTreeEvaluatorListener(operatorProvider, coercionService, event);
walker.walk(listener, parseTree);
return coercionService.coerce(listener.getResult(), Boolean.class);
return listener.getResult();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there any possible limitations here? Could this break some existing scenarios?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't think of cases where it can break the existing scenarios. All the existing scenarios are changed to use Boolean and they all check for return Object type to be Boolean and if not throw an error.

import static org.hamcrest.CoreMatchers.not;
import org.apache.commons.lang3.RandomStringUtils;

class ArithmeticExpressionEvaluatorIT {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another approach that is fairly common in the Java world is to split tests using an underscore. This is testing the GenericExpressionEvaluator for arithmetic and you have another testing GenericExpressionEvaluator for strings.

Thus:

  • GenericExpressionEvaluator_ArithmeticIT
  • GenericExpressionEvaluator_StringIT

This has the benefit of putting them next to each other in file tree UIs.

I'm fine keeping what you have or using this pattern.

Signed-off-by: Krishna Kondaka <krishkdk@amazon.com>
if (result instanceof Boolean) {
return (Boolean) result;
} else {
throw new ClassCastException("Unexpected expression return type of " + result.getClass());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I took a look at the ParseTreeCoercionService class. It appears that it will throw the following when it fails to get the correct value.

throw new ExpressionCoercionException("Unable to cast " + obj.getClass().getName() + " into " + clazz.getName());

Perhaps, we should move this evaluateConditional implementation into the GenericExpressionEvaluator and use that ParseTreeCoercionService class instead.

So this would be an unimplemented method here in the interface.

evaluateConditional(String statement, Event context);

Then the GenericExpressionEvaluator looks somewhat like:

@Override
public Boolean evaluateConditional(final String statement, final Event context) {
    final Object result = evaluate(statement, context);
    return coersionService.coerce(result, Boolean.class);
}

Copy link
Collaborator Author

@kkondaka kkondaka May 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dlvenable Yes, I could change ClassCastException to ExpressionCoercionException if that helps. Ultimately there is not much difference between the two when the type is Boolean because Boolean cannot be Coerced to a different type anyways. I don't see the need to move the evaluateConditional to GenericExpressionEvaluator just for that purpose because Boolean is not really coercion friendly anyways. Let me know if you strongly prefer calling coersionService here.

@dlvenable dlvenable merged commit f2c9341 into opensearch-project:main May 19, 2023
@kkondaka kkondaka deleted the arith-expr branch July 13, 2023 04:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

DataPrepper ExpressionEvaluator should not be type specific
3 participants