From f217cbfeabc6b37a852ffff86ca170eeffb9bab1 Mon Sep 17 00:00:00 2001 From: Margarit Hakobyan Date: Tue, 6 Dec 2022 15:33:00 -0800 Subject: [PATCH 1/4] Add position() string function to PPL Signed-off-by: Margarit Hakobyan --- docs/user/ppl/functions/string.rst | 25 +++++ .../sql/ppl/PositionFunctionIT.java | 100 ++++++++++++++++++ ppl/src/main/antlr/OpenSearchPPLLexer.g4 | 1 + ppl/src/main/antlr/OpenSearchPPLParser.g4 | 11 ++ .../sql/ppl/parser/AstExpressionBuilder.java | 10 ++ .../ppl/parser/AstExpressionBuilderTest.java | 13 +++ 6 files changed, 160 insertions(+) create mode 100644 integ-test/src/test/java/org/opensearch/sql/ppl/PositionFunctionIT.java diff --git a/docs/user/ppl/functions/string.rst b/docs/user/ppl/functions/string.rst index 116c28b0e2..b166bc24c3 100644 --- a/docs/user/ppl/functions/string.rst +++ b/docs/user/ppl/functions/string.rst @@ -150,6 +150,31 @@ Example:: +---------------------+---------------------+ +POSITION +------ + +Description +>>>>>>>>>>> + +Usage: The syntax POSITION(substr IN str) returns the position of the first occurrence of substring substr in string str. Returns 0 if substr is not in str. Returns NULL if any argument is NULL. + +Argument type: STRING, STRING + +Return type INTEGER: + +(STRING IN STRING) -> INTEGER + +Example:: + + os> SELECT POSITION('world' IN 'helloworld'), POSITION('invalid' IN 'helloworld'); + fetched rows / total rows = 1/1 + +-------------------------------------+---------------------------------------+ + | POSITION('world' IN 'helloworld') | POSITION('invalid' IN 'helloworld') | + |-------------------------------------+---------------------------------------| + | 6 | 0 | + +-------------------------------------+---------------------------------------+ + + RIGHT ----- diff --git a/integ-test/src/test/java/org/opensearch/sql/ppl/PositionFunctionIT.java b/integ-test/src/test/java/org/opensearch/sql/ppl/PositionFunctionIT.java new file mode 100644 index 0000000000..ab4759c77c --- /dev/null +++ b/integ-test/src/test/java/org/opensearch/sql/ppl/PositionFunctionIT.java @@ -0,0 +1,100 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.sql.ppl; + +import org.junit.Test; + +import java.io.IOException; + +import static org.opensearch.sql.legacy.TestsConstants.TEST_INDEX_CALCS; +import static org.opensearch.sql.util.MatcherUtils.rows; +import static org.opensearch.sql.util.MatcherUtils.verifyDataRows; + +public class PositionFunctionIT extends PPLIntegTestCase { + @Override + public void init() throws IOException { + loadIndex(Index.CALCS); + } + + @Test + public void test_position_function() throws IOException { + String query = "source=" + TEST_INDEX_CALCS + + " | eval f=position('ON', str1) | fields f"; + + var result = executeQuery(query); + + assertEquals(17, result.getInt("total")); + verifyDataRows(result, + rows(7), rows(7), + rows(2), rows(0), + rows(0), rows(0), + rows(0), rows(0), + rows(0), rows(0), + rows(0), rows(0), + rows(0), rows(0), + rows(0), rows(0), + rows(0)); + } + + @Test + public void test_position_function_with_fields_only() throws IOException { + String query = "source=" + TEST_INDEX_CALCS + + " | eval f=position(str3 IN str2) | where str2 IN ('one', 'two', 'three')| fields f"; + + var result = executeQuery(query); + + assertEquals(3, result.getInt("total")); + verifyDataRows(result, rows(3), rows(0), rows(4)); + } + + @Test + public void test_position_function_with_string_literals() throws IOException { + String query = "source=" + TEST_INDEX_CALCS + + " | eval f=position('world' IN 'hello world') | where str2='one' | fields f"; + + var result = executeQuery(query); + + assertEquals(1, result.getInt("total")); + verifyDataRows(result, rows(7)); + } + + @Test + public void test_position_function_with_nulls() throws IOException { + String query = "source=" + TEST_INDEX_CALCS + + " | eval f=position('ee' IN str2) | where isnull(str2) | fields str2,f"; + + var result = executeQuery(query); + + assertEquals(4, result.getInt("total")); + verifyDataRows(result, + rows(null, null), + rows(null, null), + rows(null, null), + rows(null, null)); + } + + @Test + public void test_position_function_with_function_as_arg() throws IOException { + String query = "source=" + TEST_INDEX_CALCS + + " | eval f=position(upper(str3) IN str1) | where like(str1, 'BINDING SUPPLIES') | fields f"; + + var result = executeQuery(query); + + assertEquals(1, result.getInt("total")); + verifyDataRows(result, rows(15)); + } + + @Test + public void test_position_function_with_function_in_where_clause() throws IOException { + String query = "source=" + TEST_INDEX_CALCS + + " | where position(str3 IN str2)=1 | fields str2"; + + var result = executeQuery(query); + + assertEquals(2, result.getInt("total")); + verifyDataRows(result, rows("eight"), rows("eleven")); + } +} \ No newline at end of file diff --git a/ppl/src/main/antlr/OpenSearchPPLLexer.g4 b/ppl/src/main/antlr/OpenSearchPPLLexer.g4 index a601a547ee..8c0340e7f1 100644 --- a/ppl/src/main/antlr/OpenSearchPPLLexer.g4 +++ b/ppl/src/main/antlr/OpenSearchPPLLexer.g4 @@ -215,6 +215,7 @@ LOG10: 'LOG10'; LOG2: 'LOG2'; MOD: 'MOD'; PI: 'PI'; +POSITION: 'POSITION'; POW: 'POW'; POWER: 'POWER'; RAND: 'RAND'; diff --git a/ppl/src/main/antlr/OpenSearchPPLParser.g4 b/ppl/src/main/antlr/OpenSearchPPLParser.g4 index 76d8e38eff..929598d4d2 100644 --- a/ppl/src/main/antlr/OpenSearchPPLParser.g4 +++ b/ppl/src/main/antlr/OpenSearchPPLParser.g4 @@ -258,6 +258,7 @@ valueExpression | LT_PRTHS left=valueExpression binaryOperator right=valueExpression RT_PRTHS #parentheticBinaryArithmetic | primaryExpression #valueExpressionDefault + | positionFunction #positionFunctionCall ; primaryExpression @@ -267,6 +268,10 @@ primaryExpression | literalValue ; +positionFunction + : positionFunctionName LT_PRTHS functionArg IN functionArg RT_PRTHS + ; + booleanExpression : booleanFunctionCall ; @@ -362,6 +367,7 @@ evalFunctionName | textFunctionBase | conditionFunctionBase | systemFunctionBase + | positionFunctionName ; functionArgs @@ -484,6 +490,10 @@ textFunctionBase | RIGHT | LEFT | ASCII | LOCATE | REPLACE ; + positionFunctionName + : POSITION + ; + /** operators */ comparisonOperator : EQUAL | NOT_EQUAL | LESS | NOT_LESS | GREATER | NOT_GREATER | REGEXP @@ -603,4 +613,5 @@ keywordsCanBeId | dateAndTimeFunctionBase | textFunctionBase | mathematicalFunctionBase + | positionFunctionName ; 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 115bcf3cd8..68608e23ad 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 @@ -9,6 +9,7 @@ import static org.opensearch.sql.ast.dsl.AstDSL.qualifiedName; import static org.opensearch.sql.expression.function.BuiltinFunctionName.IS_NOT_NULL; import static org.opensearch.sql.expression.function.BuiltinFunctionName.IS_NULL; +import static org.opensearch.sql.expression.function.BuiltinFunctionName.POSITION; import static org.opensearch.sql.ppl.antlr.parser.OpenSearchPPLParser.BinaryArithmeticContext; import static org.opensearch.sql.ppl.antlr.parser.OpenSearchPPLParser.BooleanFunctionCallContext; import static org.opensearch.sql.ppl.antlr.parser.OpenSearchPPLParser.BooleanLiteralContext; @@ -290,6 +291,15 @@ public UnresolvedExpression visitTableSource(TableSourceContext ctx) { } } + @Override + public UnresolvedExpression visitPositionFunction( + OpenSearchPPLParser.PositionFunctionContext ctx) { + return new Function( + POSITION.getName().getFunctionName(), + Arrays.asList(visitFunctionArg(ctx.functionArg(0)), + visitFunctionArg(ctx.functionArg(1)))); + } + /** * Literal and value. */ 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 e4048c5fe1..62930e8f9e 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 @@ -181,6 +181,19 @@ public void testEvalFunctionExprNoArgs() { )); } + @Test + public void testPositionFunctionExpr() { + assertEqual("source=t | eval f=position('substr' IN 'str')", + eval( + relation("t"), + let( + field("f"), + function("position", + stringLiteral("substr"), stringLiteral("str")) + ) + )); + } + @Test public void testEvalBinaryOperationExpr() { assertEqual("source=t | eval f=a+b", From 39be5cc3b793f2e8e41cdfc53a04a613693d4a97 Mon Sep 17 00:00:00 2001 From: Margarit Hakobyan Date: Tue, 6 Dec 2022 15:44:16 -0800 Subject: [PATCH 2/4] Edit a doctest Signed-off-by: Margarit Hakobyan --- docs/user/ppl/functions/string.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/user/ppl/functions/string.rst b/docs/user/ppl/functions/string.rst index b166bc24c3..5cc05b202b 100644 --- a/docs/user/ppl/functions/string.rst +++ b/docs/user/ppl/functions/string.rst @@ -166,7 +166,7 @@ Return type INTEGER: Example:: - os> SELECT POSITION('world' IN 'helloworld'), POSITION('invalid' IN 'helloworld'); + os> source=people | eval `POSITION('world' IN 'helloworld')` = POSITION('world' IN 'helloworld'), `POSITION('invalid' IN 'helloworld')`= POSITION('invalid' IN 'helloworld') | fields `POSITION('world' IN 'helloworld')`, `POSITION('invalid' IN 'helloworld')` fetched rows / total rows = 1/1 +-------------------------------------+---------------------------------------+ | POSITION('world' IN 'helloworld') | POSITION('invalid' IN 'helloworld') | From 8bbc0faf02ed57d1dbbc7e7efa2743a7f20f81a5 Mon Sep 17 00:00:00 2001 From: Margarit Hakobyan Date: Tue, 6 Dec 2022 15:53:29 -0800 Subject: [PATCH 3/4] Address PR review comments Signed-off-by: Margarit Hakobyan --- docs/user/ppl/functions/string.rst | 2 +- .../test/java/org/opensearch/sql/ppl/PositionFunctionIT.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/user/ppl/functions/string.rst b/docs/user/ppl/functions/string.rst index 5cc05b202b..b14acc88e0 100644 --- a/docs/user/ppl/functions/string.rst +++ b/docs/user/ppl/functions/string.rst @@ -160,7 +160,7 @@ Usage: The syntax POSITION(substr IN str) returns the position of the first occu Argument type: STRING, STRING -Return type INTEGER: +Return type INTEGER (STRING IN STRING) -> INTEGER diff --git a/integ-test/src/test/java/org/opensearch/sql/ppl/PositionFunctionIT.java b/integ-test/src/test/java/org/opensearch/sql/ppl/PositionFunctionIT.java index ab4759c77c..24319a0cb8 100644 --- a/integ-test/src/test/java/org/opensearch/sql/ppl/PositionFunctionIT.java +++ b/integ-test/src/test/java/org/opensearch/sql/ppl/PositionFunctionIT.java @@ -97,4 +97,4 @@ public void test_position_function_with_function_in_where_clause() throws IOExce assertEquals(2, result.getInt("total")); verifyDataRows(result, rows("eight"), rows("eleven")); } -} \ No newline at end of file +} From 48ac2689fa7f3bea0e9939056b961e5abe517c33 Mon Sep 17 00:00:00 2001 From: Margarit Hakobyan Date: Tue, 6 Dec 2022 16:25:30 -0800 Subject: [PATCH 4/4] Fix checkstyle error Signed-off-by: Margarit Hakobyan --- .../ppl/parser/AstExpressionBuilderTest.java | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) 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 62930e8f9e..dbdfb71aa7 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 @@ -181,18 +181,18 @@ public void testEvalFunctionExprNoArgs() { )); } - @Test - public void testPositionFunctionExpr() { - assertEqual("source=t | eval f=position('substr' IN 'str')", - eval( - relation("t"), - let( - field("f"), - function("position", - stringLiteral("substr"), stringLiteral("str")) - ) - )); - } + @Test + public void testPositionFunctionExpr() { + assertEqual("source=t | eval f=position('substr' IN 'str')", + eval( + relation("t"), + let( + field("f"), + function("position", + stringLiteral("substr"), stringLiteral("str")) + ) + )); + } @Test public void testEvalBinaryOperationExpr() {