From af333887e3be38add25129bc119923336c2f29cf Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Wed, 4 Sep 2024 15:11:45 -0700 Subject: [PATCH] IF function should support complex predicates in PPL (#2756) (#2969) (#2970) (cherry picked from commit 593ffabbbdc6d6a9529ef3d6c233785e29fedb07) (cherry picked from commit 1e95b68604fc767b904083ae9b98594e0c8784a6) Signed-off-by: Lantao Jin Signed-off-by: github-actions[bot] Co-authored-by: github-actions[bot] --- docs/user/ppl/functions/condition.rst | 11 ++ ppl/src/main/antlr/OpenSearchPPLParser.g4 | 16 ++- .../opensearch/sql/ppl/parser/AstBuilder.java | 3 +- .../sql/ppl/parser/AstExpressionBuilder.java | 2 +- .../ppl/parser/AstExpressionBuilderTest.java | 114 ++++++++++++++++++ .../sql/sql/antlr/SQLSyntaxParserTest.java | 16 +++ 6 files changed, 154 insertions(+), 8 deletions(-) diff --git a/docs/user/ppl/functions/condition.rst b/docs/user/ppl/functions/condition.rst index fea76bedda..e48d4cb75c 100644 --- a/docs/user/ppl/functions/condition.rst +++ b/docs/user/ppl/functions/condition.rst @@ -181,3 +181,14 @@ Example:: | Bates | Nanette | Bates | | Adams | Dale | Adams | +----------+-------------+------------+ + + os> source=accounts | eval is_vip = if(age > 30 AND isnotnull(employer), true, false) | fields is_vip, firstname, lastname + fetched rows / total rows = 4/4 + +----------+-------------+------------+ + | is_vip | firstname | lastname | + |----------+-------------+------------| + | True | Amber | Duke | + | True | Hattie | Bond | + | False | Nanette | Bates | + | False | Dale | Adams | + +----------+-------------+------------+ diff --git a/ppl/src/main/antlr/OpenSearchPPLParser.g4 b/ppl/src/main/antlr/OpenSearchPPLParser.g4 index 39fb7f53a6..4dc223b028 100644 --- a/ppl/src/main/antlr/OpenSearchPPLParser.g4 +++ b/ppl/src/main/antlr/OpenSearchPPLParser.g4 @@ -255,6 +255,7 @@ expression | valueExpression ; +// predicates logicalExpression : comparisonExpression # comparsion | NOT logicalExpression # logicalNot @@ -362,7 +363,7 @@ dataTypeFunctionCall // boolean functions booleanFunctionCall - : conditionFunctionBase LT_PRTHS functionArgs RT_PRTHS + : conditionFunctionName LT_PRTHS functionArgs RT_PRTHS ; convertedDataType @@ -382,7 +383,8 @@ evalFunctionName : mathematicalFunctionName | dateTimeFunctionName | textFunctionName - | conditionFunctionBase + | conditionFunctionName + | flowControlFunctionName | systemFunctionName | positionFunctionName ; @@ -392,7 +394,7 @@ functionArgs ; functionArg - : (ident EQUAL)? valueExpression + : (ident EQUAL)? expression ; relevanceArg @@ -623,11 +625,15 @@ timestampFunctionName ; // condition function return boolean value -conditionFunctionBase +conditionFunctionName : LIKE - | IF | ISNULL | ISNOTNULL + ; + +// flow control function return non-boolean value +flowControlFunctionName + : IF | IFNULL | NULLIF ; diff --git a/ppl/src/main/java/org/opensearch/sql/ppl/parser/AstBuilder.java b/ppl/src/main/java/org/opensearch/sql/ppl/parser/AstBuilder.java index 9419d8110b..1bef820399 100644 --- a/ppl/src/main/java/org/opensearch/sql/ppl/parser/AstBuilder.java +++ b/ppl/src/main/java/org/opensearch/sql/ppl/parser/AstBuilder.java @@ -331,8 +331,7 @@ public UnresolvedPlan visitTableFunction(TableFunctionContext ctx) { arg -> { String argName = (arg.ident() != null) ? arg.ident().getText() : null; builder.add( - new UnresolvedArgument( - argName, this.internalVisitExpression(arg.valueExpression()))); + new UnresolvedArgument(argName, this.internalVisitExpression(arg.expression()))); }); return new TableFunction(this.internalVisitExpression(ctx.qualifiedName()), builder.build()); } diff --git a/ppl/src/main/java/org/opensearch/sql/ppl/parser/AstExpressionBuilder.java b/ppl/src/main/java/org/opensearch/sql/ppl/parser/AstExpressionBuilder.java index f36765d3d7..aec22ac231 100644 --- a/ppl/src/main/java/org/opensearch/sql/ppl/parser/AstExpressionBuilder.java +++ b/ppl/src/main/java/org/opensearch/sql/ppl/parser/AstExpressionBuilder.java @@ -187,7 +187,7 @@ public UnresolvedExpression visitTakeAggFunctionCall( /** Eval function. */ @Override public UnresolvedExpression visitBooleanFunctionCall(BooleanFunctionCallContext ctx) { - final String functionName = ctx.conditionFunctionBase().getText().toLowerCase(); + final String functionName = ctx.conditionFunctionName().getText().toLowerCase(); return buildFunction( FUNCTION_NAME_MAPPING.getOrDefault(functionName, functionName), ctx.functionArgs().functionArg()); diff --git a/ppl/src/test/java/org/opensearch/sql/ppl/parser/AstExpressionBuilderTest.java b/ppl/src/test/java/org/opensearch/sql/ppl/parser/AstExpressionBuilderTest.java index 30b2d5dc6f..596d8f34be 100644 --- a/ppl/src/test/java/org/opensearch/sql/ppl/parser/AstExpressionBuilderTest.java +++ b/ppl/src/test/java/org/opensearch/sql/ppl/parser/AstExpressionBuilderTest.java @@ -138,6 +138,120 @@ public void testEvalFunctionExprNoArgs() { assertEqual("source=t | eval f=PI()", eval(relation("t"), let(field("f"), function("PI")))); } + @Test + public void testEvalIfFunctionExpr() { + assertEqual( + "source=t | eval f=if(true, 1, 0)", + eval( + relation("t"), + let(field("f"), function("if", booleanLiteral(true), intLiteral(1), intLiteral(0))))); + assertEqual( + "source=t | eval f=if(1>2, 1, 0)", + eval( + relation("t"), + let( + field("f"), + function( + "if", + compare(">", intLiteral(1), intLiteral(2)), + intLiteral(1), + intLiteral(0))))); + assertEqual( + "source=t | eval f=if(1<=2, 1, 0)", + eval( + relation("t"), + let( + field("f"), + function( + "if", + compare("<=", intLiteral(1), intLiteral(2)), + intLiteral(1), + intLiteral(0))))); + assertEqual( + "source=t | eval f=if(1=2, 1, 0)", + eval( + relation("t"), + let( + field("f"), + function( + "if", + compare("=", intLiteral(1), intLiteral(2)), + intLiteral(1), + intLiteral(0))))); + assertEqual( + "source=t | eval f=if(1!=2, 1, 0)", + eval( + relation("t"), + let( + field("f"), + function( + "if", + compare("!=", intLiteral(1), intLiteral(2)), + intLiteral(1), + intLiteral(0))))); + assertEqual( + "source=t | eval f=if(isnull(a), 1, 0)", + eval( + relation("t"), + let( + field("f"), + function("if", function("is null", field("a")), intLiteral(1), intLiteral(0))))); + assertEqual( + "source=t | eval f=if(isnotnull(a), 1, 0)", + eval( + relation("t"), + let( + field("f"), + function( + "if", function("is not null", field("a")), intLiteral(1), intLiteral(0))))); + assertEqual( + "source=t | eval f=if(not 1>2, 1, 0)", + eval( + relation("t"), + let( + field("f"), + function( + "if", + not(compare(">", intLiteral(1), intLiteral(2))), + intLiteral(1), + intLiteral(0))))); + assertEqual( + "source=t | eval f=if(not a in (0, 1), 1, 0)", + eval( + relation("t"), + let( + field("f"), + function( + "if", + not(in(field("a"), intLiteral(0), intLiteral(1))), + intLiteral(1), + intLiteral(0))))); + assertEqual( + "source=t | eval f=if(not a in (0, 1) OR isnull(a), 1, 0)", + eval( + relation("t"), + let( + field("f"), + function( + "if", + or( + not(in(field("a"), intLiteral(0), intLiteral(1))), + function("is null", field("a"))), + intLiteral(1), + intLiteral(0))))); + assertEqual( + "source=t | eval f=if(like(a, '_a%b%c_d_'), 1, 0)", + eval( + relation("t"), + let( + field("f"), + function( + "if", + function("like", field("a"), stringLiteral("_a%b%c_d_")), + intLiteral(1), + intLiteral(0))))); + } + @Test public void testPositionFunctionExpr() { assertEqual( diff --git a/sql/src/test/java/org/opensearch/sql/sql/antlr/SQLSyntaxParserTest.java b/sql/src/test/java/org/opensearch/sql/sql/antlr/SQLSyntaxParserTest.java index a1a6923bf1..2fff7ff21c 100644 --- a/sql/src/test/java/org/opensearch/sql/sql/antlr/SQLSyntaxParserTest.java +++ b/sql/src/test/java/org/opensearch/sql/sql/antlr/SQLSyntaxParserTest.java @@ -685,6 +685,22 @@ public void canParseMultiMatchAlternateSyntax() { assertNotNull(parser.parse("SELECT * FROM test WHERE Field = multimatch(\"query\")")); } + @Test + public void canParseIfFunction() { + assertNotNull(parser.parse("SELECT IF(1 > 2, 1, 0)")); + assertNotNull(parser.parse("SELECT IF(1 < 2, 1, 0)")); + assertNotNull(parser.parse("SELECT IF(1 >= 2, 1, 0)")); + assertNotNull(parser.parse("SELECT IF(1 <= 2, 1, 0)")); + assertNotNull(parser.parse("SELECT IF(1 <> 2, 1, 0)")); + assertNotNull(parser.parse("SELECT IF(1 != 2, 1, 0)")); + assertNotNull(parser.parse("SELECT IF(1 = 2, 1, 0)")); + assertNotNull(parser.parse("SELECT IF(true, 1, 0)")); + assertNotNull(parser.parse("SELECT IF(1 IS NOT NULL, 1, 0)")); + assertNotNull(parser.parse("SELECT IF(NOT 1 > 2, 1, 0)")); + assertNotNull(parser.parse("SELECT IF(NOT 1 IN (0, 1), 1, 0)")); + assertNotNull(parser.parse("SELECT IF(NOT 1 IN (0, 1) OR 1 IS NOT NULL, 1, 0)")); + } + private static Stream matchPhraseQueryComplexQueries() { return Stream.of( "SELECT * FROM t WHERE matchphrasequery(c, 3)",