diff --git a/core/src/main/java/com/amazon/opendistroforelasticsearch/sql/expression/DSL.java b/core/src/main/java/com/amazon/opendistroforelasticsearch/sql/expression/DSL.java index dbeb5453b5..2aba72f407 100644 --- a/core/src/main/java/com/amazon/opendistroforelasticsearch/sql/expression/DSL.java +++ b/core/src/main/java/com/amazon/opendistroforelasticsearch/sql/expression/DSL.java @@ -409,6 +409,10 @@ public FunctionExpression strcmp(Expression... expressions) { return function(BuiltinFunctionName.STRCMP, expressions); } + public FunctionExpression right(Expression... expressions) { + return function(BuiltinFunctionName.RIGHT, expressions); + } + public FunctionExpression and(Expression... expressions) { return function(BuiltinFunctionName.AND, expressions); } diff --git a/core/src/main/java/com/amazon/opendistroforelasticsearch/sql/expression/function/BuiltinFunctionName.java b/core/src/main/java/com/amazon/opendistroforelasticsearch/sql/expression/function/BuiltinFunctionName.java index 8c00c120d0..3c009ea067 100644 --- a/core/src/main/java/com/amazon/opendistroforelasticsearch/sql/expression/function/BuiltinFunctionName.java +++ b/core/src/main/java/com/amazon/opendistroforelasticsearch/sql/expression/function/BuiltinFunctionName.java @@ -131,6 +131,7 @@ public enum BuiltinFunctionName { CONCAT_WS(FunctionName.of("concat_ws")), LENGTH(FunctionName.of("length")), STRCMP(FunctionName.of("strcmp")), + RIGHT(FunctionName.of("right")), /** * NULL Test. diff --git a/core/src/main/java/com/amazon/opendistroforelasticsearch/sql/expression/text/TextFunction.java b/core/src/main/java/com/amazon/opendistroforelasticsearch/sql/expression/text/TextFunction.java index 4b2de00f97..e1a24f29bc 100644 --- a/core/src/main/java/com/amazon/opendistroforelasticsearch/sql/expression/text/TextFunction.java +++ b/core/src/main/java/com/amazon/opendistroforelasticsearch/sql/expression/text/TextFunction.java @@ -60,6 +60,7 @@ public void register(BuiltinFunctionRepository repository) { repository.register(concat_ws()); repository.register(length()); repository.register(strcmp()); + repository.register(right()); } /** @@ -194,6 +195,16 @@ private FunctionResolver strcmp() { INTEGER, STRING, STRING)); } + /** + * Returns the rightmost len characters from the string str, or NULL if any argument is NULL. + * Supports following signatures: + * (STRING, INTEGER) -> STRING + */ + private FunctionResolver right() { + return define(BuiltinFunctionName.RIGHT.getName(), + impl(nullMissingHandling(TextFunction::exprRight), STRING, STRING, INTEGER)); + } + private static ExprValue exprSubstrStart(ExprValue exprValue, ExprValue start) { int startIdx = start.integerValue(); if (startIdx == 0) { @@ -225,5 +236,10 @@ private static ExprValue exprSubStr(String str, int start, int len) { } return new ExprStringValue(str.substring(start, start + len)); } + + private static ExprValue exprRight(ExprValue str, ExprValue len) { + return new ExprStringValue(str.stringValue().substring( + str.stringValue().length() - len.integerValue())); + } } diff --git a/core/src/test/java/com/amazon/opendistroforelasticsearch/sql/expression/text/TextFunctionTest.java b/core/src/test/java/com/amazon/opendistroforelasticsearch/sql/expression/text/TextFunctionTest.java index d66c89089f..5c1f1728c5 100644 --- a/core/src/test/java/com/amazon/opendistroforelasticsearch/sql/expression/text/TextFunctionTest.java +++ b/core/src/test/java/com/amazon/opendistroforelasticsearch/sql/expression/text/TextFunctionTest.java @@ -25,6 +25,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.when; +import com.amazon.opendistroforelasticsearch.sql.data.model.ExprIntegerValue; import com.amazon.opendistroforelasticsearch.sql.data.model.ExprStringValue; import com.amazon.opendistroforelasticsearch.sql.data.model.ExprValue; import com.amazon.opendistroforelasticsearch.sql.expression.DSL; @@ -296,6 +297,23 @@ void strcmp() { assertEquals(missingValue(), eval(dsl.strcmp(missingRef, nullRef))); } + @Test + void right() { + FunctionExpression expression = dsl.right( + DSL.literal(new ExprStringValue("foobarbar")), + DSL.literal(new ExprIntegerValue(4))); + assertEquals(STRING, expression.type()); + assertEquals("rbar", eval(expression).stringValue()); + + when(nullRef.type()).thenReturn(STRING); + when(missingRef.type()).thenReturn(INTEGER); + assertEquals(missingValue(), eval(dsl.right(nullRef, missingRef))); + assertEquals(nullValue(), eval(dsl.right(nullRef, DSL.literal(new ExprIntegerValue(1))))); + + when(nullRef.type()).thenReturn(INTEGER); + assertEquals(nullValue(), eval(dsl.right(DSL.literal(new ExprStringValue("value")), nullRef))); + } + void testConcatString(List strings) { String expected = null; if (strings.stream().noneMatch(Objects::isNull)) { diff --git a/docs/experiment/ppl/functions/string.rst b/docs/experiment/ppl/functions/string.rst index 0afdd90195..3f90395d38 100644 --- a/docs/experiment/ppl/functions/string.rst +++ b/docs/experiment/ppl/functions/string.rst @@ -150,6 +150,29 @@ Example:: +---------------------+---------------------+ +RIGHT +----- + +Description +>>>>>>>>>>> + +Usage: right(str, len) returns the rightmost len characters from the string str, or NULL if any argument is NULL. + +Argument type: STRING, INTEGER + +Return type: STRING + +Example:: + + od> source=people | eval `RIGHT('helloworld', 5)` = RIGHT('helloworld', 5), `RIGHT('HELLOWORLD', 0)` = RIGHT('HELLOWORLD', 0) | fields `RIGHT('helloworld', 5)`, `RIGHT('HELLOWORLD', 0)` + fetched rows / total rows = 1/1 + +--------------------------+--------------------------+ + | RIGHT('helloworld', 5) | RIGHT('HELLOWORLD', 0) | + |--------------------------+--------------------------| + | world | | + +--------------------------+--------------------------+ + + RTRIM ----- diff --git a/docs/user/dql/functions.rst b/docs/user/dql/functions.rst index 0e3831673f..9233084d4d 100644 --- a/docs/user/dql/functions.rst +++ b/docs/user/dql/functions.rst @@ -1760,9 +1760,21 @@ RIGHT Description >>>>>>>>>>> -Specifications: +Usage: right(str, len) returns the rightmost len characters from the string str, or NULL if any argument is NULL. + +Argument type: STRING, INTEGER + +Return type: STRING -1. RIGHT(STRING T, INTEGER) -> T +Example:: + + od> SELECT RIGHT('helloworld', 5), RIGHT('HELLOWORLD', 0) + fetched rows / total rows = 1/1 + +--------------------------+--------------------------+ + | RIGHT('helloworld', 5) | RIGHT('HELLOWORLD', 0) | + |--------------------------+--------------------------| + | world | | + +--------------------------+--------------------------+ RTRIM diff --git a/integ-test/src/test/java/com/amazon/opendistroforelasticsearch/sql/ppl/TextCommandIT.java b/integ-test/src/test/java/com/amazon/opendistroforelasticsearch/sql/ppl/TextCommandIT.java index 6b4e3182f7..ef8b1b1e50 100644 --- a/integ-test/src/test/java/com/amazon/opendistroforelasticsearch/sql/ppl/TextCommandIT.java +++ b/integ-test/src/test/java/com/amazon/opendistroforelasticsearch/sql/ppl/TextCommandIT.java @@ -92,6 +92,11 @@ public void testTrim() throws IOException { verifyQuery("trim", "", "", "hello", "world", "helloworld"); } + @Test + public void testRight() throws IOException { + verifyQuery("right", "", ", 3", "llo", "rld", "rld"); + } + @Test public void testRtrim() throws IOException { verifyQuery("rtrim", "", "", "hello", "world", "helloworld"); diff --git a/integ-test/src/test/java/com/amazon/opendistroforelasticsearch/sql/sql/TextFunctionIT.java b/integ-test/src/test/java/com/amazon/opendistroforelasticsearch/sql/sql/TextFunctionIT.java index d972a10c36..a096b5b201 100644 --- a/integ-test/src/test/java/com/amazon/opendistroforelasticsearch/sql/sql/TextFunctionIT.java +++ b/integ-test/src/test/java/com/amazon/opendistroforelasticsearch/sql/sql/TextFunctionIT.java @@ -126,6 +126,11 @@ public void testStrcmp() throws IOException { verifyQuery("strcmp('hello', 'hello')", "integer", 0); } + @Test + public void testRight() throws IOException { + verifyQuery("right('variable', 4)", "keyword", "able"); + } + protected JSONObject executeQuery(String query) throws IOException { Request request = new Request("POST", QUERY_API_ENDPOINT); request.setJsonEntity(String.format(Locale.ROOT, "{\n" + " \"query\": \"%s\"\n" + "}", query)); diff --git a/integ-test/src/test/resources/correctness/expressions/text_functions.txt b/integ-test/src/test/resources/correctness/expressions/text_functions.txt new file mode 100644 index 0000000000..a9d4ea76e8 --- /dev/null +++ b/integ-test/src/test/resources/correctness/expressions/text_functions.txt @@ -0,0 +1 @@ +RIGHT('Hello World', 5) as column \ No newline at end of file diff --git a/ppl/src/main/antlr/OpenDistroPPLLexer.g4 b/ppl/src/main/antlr/OpenDistroPPLLexer.g4 index c13656cdf3..15b8830a31 100644 --- a/ppl/src/main/antlr/OpenDistroPPLLexer.g4 +++ b/ppl/src/main/antlr/OpenDistroPPLLexer.g4 @@ -228,6 +228,7 @@ CONCAT: 'CONCAT'; CONCAT_WS: 'CONCAT_WS'; LENGTH: 'LENGTH'; STRCMP: 'STRCMP'; +RIGHT: 'RIGHT'; // BOOL FUNCTIONS LIKE: 'LIKE'; diff --git a/ppl/src/main/antlr/OpenDistroPPLParser.g4 b/ppl/src/main/antlr/OpenDistroPPLParser.g4 index 3378c407b2..d724beb0de 100644 --- a/ppl/src/main/antlr/OpenDistroPPLParser.g4 +++ b/ppl/src/main/antlr/OpenDistroPPLParser.g4 @@ -258,6 +258,7 @@ conditionFunctionBase textFunctionBase : SUBSTR | SUBSTRING | TRIM | LTRIM | RTRIM | LOWER | UPPER | CONCAT | CONCAT_WS | LENGTH | STRCMP + | RIGHT ; /** operators */ diff --git a/sql/src/main/antlr/OpenDistroSQLParser.g4 b/sql/src/main/antlr/OpenDistroSQLParser.g4 index 5dbc92c86d..cf4df2f4fe 100644 --- a/sql/src/main/antlr/OpenDistroSQLParser.g4 +++ b/sql/src/main/antlr/OpenDistroSQLParser.g4 @@ -345,7 +345,7 @@ dateTimeFunctionName textFunctionName : SUBSTR | SUBSTRING | TRIM | LTRIM | RTRIM | LOWER | UPPER - | CONCAT | CONCAT_WS | SUBSTR | LENGTH | STRCMP + | CONCAT | CONCAT_WS | SUBSTR | LENGTH | STRCMP | RIGHT ; functionArgs