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

Support text functions ASCII, LEFT, LOCATE, REPLACE in new engine #88

Merged
merged 9 commits into from
May 24, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions core/src/main/java/org/opensearch/sql/expression/DSL.java
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,22 @@ public FunctionExpression right(Expression... expressions) {
return function(BuiltinFunctionName.RIGHT, expressions);
}

public FunctionExpression left(Expression... expressions) {
return function(BuiltinFunctionName.LEFT, expressions);
}

public FunctionExpression ascii(Expression... expressions) {
return function(BuiltinFunctionName.ASCII, expressions);
}

public FunctionExpression locate(Expression... expressions) {
return function(BuiltinFunctionName.LOCATE, expressions);
}

public FunctionExpression replace(Expression... expressions) {
return function(BuiltinFunctionName.REPLACE, expressions);
}

public FunctionExpression and(Expression... expressions) {
return function(BuiltinFunctionName.AND, expressions);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,10 @@ public enum BuiltinFunctionName {
LENGTH(FunctionName.of("length")),
STRCMP(FunctionName.of("strcmp")),
RIGHT(FunctionName.of("right")),
LEFT(FunctionName.of("left")),
ASCII(FunctionName.of("ascii")),
LOCATE(FunctionName.of("locate")),
REPLACE(FunctionName.of("replace")),

/**
* NULL Test.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@
import org.opensearch.sql.expression.function.BuiltinFunctionRepository;
import org.opensearch.sql.expression.function.FunctionName;
import org.opensearch.sql.expression.function.FunctionResolver;
import org.opensearch.sql.expression.function.SerializableBiFunction;
import org.opensearch.sql.expression.function.SerializableTriFunction;


/**
Expand Down Expand Up @@ -71,6 +73,10 @@ public void register(BuiltinFunctionRepository repository) {
repository.register(length());
repository.register(strcmp());
repository.register(right());
repository.register(left());
repository.register(ascii());
repository.register(locate());
repository.register(replace());
}

/**
Expand Down Expand Up @@ -215,6 +221,60 @@ private FunctionResolver right() {
impl(nullMissingHandling(TextFunction::exprRight), STRING, STRING, INTEGER));
}

/**
* Returns the leftmost len characters from the string str, or NULL if any argument is NULL.
* Supports following signature:
* (STRING, INTEGER) -> STRING
*/
private FunctionResolver left() {
return define(BuiltinFunctionName.LEFT.getName(),
impl(nullMissingHandling(TextFunction::exprLeft), STRING, STRING, INTEGER));
}

/**
* Returns the numeric value of the leftmost character of the string str.
* Returns 0 if str is the empty string. Returns NULL if str is NULL.
* ASCII() works for 8-bit characters.
* Supports following signature:
* STRING -> INTEGER
*/
private FunctionResolver ascii() {
return define(BuiltinFunctionName.ASCII.getName(),
impl(nullMissingHandling(TextFunction::exprAscii), INTEGER, STRING));
}

/**
* LOCATE(substr, str) returns the position of the first occurrence of substring substr
* in string str. LOCATE(substr, str, pos) returns the position of the first occurrence
* of substring substr in string str, starting at position pos.
* Returns 0 if substr is not in str.
* Returns NULL if any argument is NULL.
* Supports following signature:
* (STRING, STRING) -> INTEGER
* (STRING, STRING, INTEGER) -> INTEGER
*/
private FunctionResolver locate() {
return define(BuiltinFunctionName.LOCATE.getName(),
impl(nullMissingHandling(
(SerializableBiFunction<ExprValue, ExprValue, ExprValue>)
TextFunction::exprLocate), INTEGER, STRING, STRING),
impl(nullMissingHandling(
(SerializableTriFunction<ExprValue, ExprValue, ExprValue, ExprValue>)
TextFunction::exprLocate), INTEGER, STRING, STRING, INTEGER));
}

/**
* REPLACE(str, from_str, to_str) returns the string str with all occurrences of
* the string from_str replaced by the string to_str.
* REPLACE() performs a case-sensitive match when searching for from_str.
* Supports following signature:
* (STRING, STRING, STRING) -> STRING
*/
private FunctionResolver replace() {
return define(BuiltinFunctionName.REPLACE.getName(),
impl(nullMissingHandling(TextFunction::exprReplace), STRING, STRING, STRING, STRING));
}

private static ExprValue exprSubstrStart(ExprValue exprValue, ExprValue start) {
int startIdx = start.integerValue();
if (startIdx == 0) {
Expand Down Expand Up @@ -251,5 +311,26 @@ private static ExprValue exprRight(ExprValue str, ExprValue len) {
return new ExprStringValue(str.stringValue().substring(
str.stringValue().length() - len.integerValue()));
}

private static ExprValue exprLeft(ExprValue expr, ExprValue length) {
return new ExprStringValue(expr.stringValue().substring(0, length.integerValue()));
}

private static ExprValue exprAscii(ExprValue expr) {
return new ExprIntegerValue((int) expr.stringValue().charAt(0));
}

private static ExprValue exprLocate(ExprValue subStr, ExprValue str) {
return new ExprIntegerValue(str.stringValue().indexOf(subStr.stringValue()) + 1);
}

private static ExprValue exprLocate(ExprValue subStr, ExprValue str, ExprValue pos) {
return new ExprIntegerValue(
str.stringValue().indexOf(subStr.stringValue(), pos.integerValue() - 1) + 1);
}

private static ExprValue exprReplace(ExprValue str, ExprValue from, ExprValue to) {
return new ExprStringValue(str.stringValue().replaceAll(from.stringValue(), to.stringValue()));
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,74 @@ void right() {
assertEquals(nullValue(), eval(dsl.right(DSL.literal(new ExprStringValue("value")), nullRef)));
}

@Test
void left() {
FunctionExpression expression = dsl.left(
DSL.literal(new ExprStringValue("helloworld")),
DSL.literal(new ExprIntegerValue(5)));
assertEquals(STRING, expression.type());
assertEquals("hello", eval(expression).stringValue());

when(nullRef.type()).thenReturn(STRING);
when(missingRef.type()).thenReturn(INTEGER);
assertEquals(missingValue(), eval(dsl.left(nullRef, missingRef)));
assertEquals(nullValue(), eval(dsl.left(nullRef, DSL.literal(new ExprIntegerValue(1)))));

when(nullRef.type()).thenReturn(INTEGER);
assertEquals(nullValue(), eval(dsl.left(DSL.literal(new ExprStringValue("value")), nullRef)));
}

@Test
void ascii() {
FunctionExpression expression = dsl.ascii(DSL.literal(new ExprStringValue("hello")));
assertEquals(INTEGER, expression.type());
assertEquals(104, eval(expression).integerValue());

when(nullRef.type()).thenReturn(STRING);
assertEquals(nullValue(), eval(dsl.ascii(nullRef)));
when(missingRef.type()).thenReturn(STRING);
assertEquals(missingValue(), eval(dsl.ascii(missingRef)));
}

@Test
void locate() {
FunctionExpression expression = dsl.locate(
DSL.literal("world"),
DSL.literal("helloworld"));
assertEquals(INTEGER, expression.type());
assertEquals(6, eval(expression).integerValue());

expression = dsl.locate(
DSL.literal("world"),
DSL.literal("helloworldworld"),
DSL.literal(7));
assertEquals(INTEGER, expression.type());
assertEquals(11, eval(expression).integerValue());

when(nullRef.type()).thenReturn(STRING);
assertEquals(nullValue(), eval(dsl.locate(nullRef, DSL.literal("hello"))));
assertEquals(nullValue(), eval(dsl.locate(nullRef, DSL.literal("hello"), DSL.literal(1))));
when(missingRef.type()).thenReturn(STRING);
assertEquals(missingValue(), eval(dsl.locate(missingRef, DSL.literal("hello"))));
assertEquals(missingValue(), eval(
dsl.locate(missingRef, DSL.literal("hello"), DSL.literal(1))));
}

@Test
void replace() {
FunctionExpression expression = dsl.replace(
DSL.literal("helloworld"),
DSL.literal("world"),
DSL.literal("opensearch"));
assertEquals(STRING, expression.type());
assertEquals("helloopensearch", eval(expression).stringValue());

when(nullRef.type()).thenReturn(STRING);
assertEquals(nullValue(), eval(dsl.replace(nullRef, DSL.literal("a"), DSL.literal("b"))));
when(missingRef.type()).thenReturn(STRING);
assertEquals(missingValue(), eval(dsl.replace(missingRef, DSL.literal("a"), DSL.literal("b"))));
}

void testConcatString(List<String> strings) {
String expected = null;
if (strings.stream().noneMatch(Objects::isNull)) {
Expand Down
70 changes: 59 additions & 11 deletions docs/user/dql/functions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1596,9 +1596,21 @@ ASCII
Description
>>>>>>>>>>>

Specifications:
Usage: ASCII(expr) returns the numeric value of the leftmost character of the string str. Returns 0 if str is the empty string. Returns NULL if str is NULL. ASCII() works for 8-bit characters.

Argument type: STRING

Return type: INTEGER

1. ASCII(STRING T) -> INTEGER
Example::

os> SELECT ASCII('hello')
fetched rows / total rows = 1/1
+------------------+
| ASCII('hello') |
|------------------|
| 104 |
+------------------+


CONCAT
Expand Down Expand Up @@ -1650,12 +1662,21 @@ Example::
LEFT
----

Description
>>>>>>>>>>>
Usage: left(str, len) returns the leftmost len characters from the string str, or NULL if any argument is NULL.

Specifications:
Argument type: STRING, INTEGER

1. LEFT(STRING T, INTEGER) -> T
Return type: STRING

Example::

os> SELECT LEFT('helloworld', 5), LEFT('HELLOWORLD', 0)
fetched rows / total rows = 1/1
+-------------------------+-------------------------+
| LEFT('helloworld', 5) | LEFT('HELLOWORLD', 0) |
|-------------------------+-------------------------|
| hello | |
+-------------------------+-------------------------+


LENGTH
Expand Down Expand Up @@ -1691,10 +1712,24 @@ LOCATE
Description
>>>>>>>>>>>

Specifications:
Usage: The first syntax LOCATE(substr, str) returns the position of the first occurrence of substring substr in string str. The second syntax LOCATE(substr, str, pos) returns the position of the first occurrence of substring substr in string str, starting at position pos. Returns 0 if substr is not in str. Returns NULL if any argument is NULL.

Argument type: STRING, STRING, INTEGER

Return type map:

1. LOCATE(STRING, STRING, INTEGER) -> INTEGER
2. LOCATE(STRING, STRING) -> INTEGER
(STRING, STRING) -> INTEGER
(STRING, STRING, INTEGER) -> INTEGER

Example::

os> SELECT LOCATE('world', 'helloworld'), LOCATE('world', 'helloworldworld', 7)
fetched rows / total rows = 1/1
+---------------------------------+-----------------------------------------+
| LOCATE('world', 'helloworld') | LOCATE('world', 'helloworldworld', 7) |
|---------------------------------+-----------------------------------------|
| 6 | 11 |
+---------------------------------+-----------------------------------------+


LOWER
Expand Down Expand Up @@ -1749,9 +1784,21 @@ REPLACE
Description
>>>>>>>>>>>

Specifications:
Usage: REPLACE(str, from_str, to_str) returns the string str with all occurrences of the string from_str replaced by the string to_str. REPLACE() performs a case-sensitive match when searching for from_str.

1. REPLACE(STRING T, STRING, STRING) -> T
Argument type: STRING, STRING, STRING

Return type: STRING

Example::

os> SELECT REPLACE('Hello World!', 'World', 'OpenSearch')
fetched rows / total rows = 1/1
+--------------------------------------------------+
| REPLACE('Hello World!', 'World', 'OpenSearch') |
|--------------------------------------------------|
| Hello OpenSearch! |
+--------------------------------------------------+


RIGHT
Expand Down Expand Up @@ -2093,3 +2140,4 @@ Here are examples for searched case syntax::
|-----------------+------------------+-----------|
| One | Hello | null |
+-----------------+------------------+-----------+

Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
import org.json.JSONObject;
import org.junit.jupiter.api.Test;

public class TextCommandIT extends PPLIntegTestCase {
public class TextFunctionIT extends PPLIntegTestCase {
@Override
public void init() throws IOException {
loadIndex(Index.BANK);
Expand Down Expand Up @@ -138,4 +138,25 @@ public void testLength() throws IOException {
public void testStrcmp() throws IOException {
verifyQuery("strcmp", "", ", 'world'", -1, 0, -1);
}

@Test
public void testLeft() throws IOException {
verifyQuery("left", "", ", 3", "hel", "wor", "hel");
}

@Test
public void testAscii() throws IOException {
verifyQuery("ascii", "", "", 104, 119, 104);
}

@Test
public void testLocate() throws IOException {
verifyQuery("locate", "'world', ", "", 0, 1, 6);
verifyQuery("locate", "'world', ", ", 2", 0, 0, 6);
}

@Test
public void testReplace() throws IOException {
verifyQuery("replace", "", ", 'world', ' opensearch'", "hello", " opensearch", "hello opensearch");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,11 @@ public void testRight() throws IOException {
verifyQuery("right('variable', 4)", "keyword", "able");
}

@Test
public void testLeft() throws IOException {
verifyQuery("left('variable', 3)", "keyword", "var");
}

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));
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,8 @@
RIGHT('Hello World', 5) as column
RIGHT('Hello World', 5) as column
LEFT('Hello World', 5) as column
ASCII('hello') as column
LOCATE('world', 'helloworld') as column
LOCATE('world', 'hello') as column
LOCATE('world', 'helloworld', 7) as column
REPLACE('helloworld', 'world', 'opensearch') as column
REPLACE('hello', 'world', 'opensearch') as column
4 changes: 4 additions & 0 deletions ppl/src/main/antlr/OpenSearchPPLLexer.g4
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,10 @@ CONCAT_WS: 'CONCAT_WS';
LENGTH: 'LENGTH';
STRCMP: 'STRCMP';
RIGHT: 'RIGHT';
LEFT: 'LEFT';
ASCII: 'ASCII';
LOCATE: 'LOCATE';
REPLACE: 'REPLACE';

// BOOL FUNCTIONS
LIKE: 'LIKE';
Expand Down
Loading