Skip to content

Commit c934c5d

Browse files
committed
Add position function to PPL
Signed-off-by: Margarit Hakobyan <margarith@bitquilltech.com>
1 parent 55c44ca commit c934c5d

File tree

6 files changed

+162
-0
lines changed

6 files changed

+162
-0
lines changed

docs/user/ppl/functions/string.rst

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,32 @@ Example::
150150
+---------------------+---------------------+
151151

152152

153+
POSITION
154+
------
155+
156+
Description
157+
>>>>>>>>>>>
158+
159+
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.
160+
161+
Argument type: STRING, STRING, INTEGER
162+
163+
Return type integer:
164+
165+
(STRING IN STRING) -> INTEGER
166+
167+
Example::
168+
169+
opensearchsql> source=location | 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')`;
170+
fetched rows / total rows = 2/2
171+
+-------------------------------------+---------------------------------------+
172+
| POSITION('world' IN 'helloworld') | POSITION('invalid' IN 'helloworld') |
173+
|-------------------------------------+---------------------------------------|
174+
| 6 | 0 |
175+
| 6 | 0 |
176+
+-------------------------------------+---------------------------------------+
177+
178+
153179
RIGHT
154180
-----
155181

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package org.opensearch.sql.ppl;
7+
8+
import org.junit.Test;
9+
10+
import java.io.IOException;
11+
12+
import static org.opensearch.sql.legacy.TestsConstants.TEST_INDEX_BEER;
13+
import static org.opensearch.sql.legacy.TestsConstants.TEST_INDEX_CALCS;
14+
import static org.opensearch.sql.util.MatcherUtils.rows;
15+
import static org.opensearch.sql.util.MatcherUtils.schema;
16+
import static org.opensearch.sql.util.MatcherUtils.verifyDataRows;
17+
import static org.opensearch.sql.util.MatcherUtils.verifySchema;
18+
19+
public class PositionFunctionIT extends PPLIntegTestCase {
20+
@Override
21+
public void init() throws IOException {
22+
loadIndex(Index.CALCS);
23+
}
24+
25+
@Test
26+
public void test_position_function() throws IOException {
27+
String query = "source=" + TEST_INDEX_CALCS
28+
+ " | eval f=position('ON', str1) | fields f";
29+
30+
var result = executeQuery(query);
31+
32+
assertEquals(17, result.getInt("total"));
33+
verifyDataRows(result,
34+
rows(7), rows(7),
35+
rows(2), rows(0),
36+
rows(0), rows(0),
37+
rows(0), rows(0),
38+
rows(0), rows(0),
39+
rows(0), rows(0),
40+
rows(0), rows(0),
41+
rows(0), rows(0),
42+
rows(0));
43+
}
44+
45+
@Test
46+
public void test_position_function_with_fields_only() throws IOException {
47+
String query = "source=" + TEST_INDEX_CALCS
48+
+ " | eval f=position(str3 IN str2) | where str2 IN ('one', 'two', 'three')| fields f";
49+
50+
var result = executeQuery(query);
51+
52+
assertEquals(3, result.getInt("total"));
53+
verifyDataRows(result, rows(3), rows(0), rows(4));
54+
}
55+
56+
@Test
57+
public void test_position_function_with_string_literals() throws IOException {
58+
String query = "source=" + TEST_INDEX_CALCS
59+
+ " | eval f=position('world' IN 'hello world') | where str2='one' | fields f";
60+
61+
var result = executeQuery(query);
62+
63+
assertEquals(1, result.getInt("total"));
64+
verifyDataRows(result, rows(7));
65+
}
66+
67+
@Test
68+
public void test_position_function_with_nulls() throws IOException {
69+
String query = "source=" + TEST_INDEX_CALCS
70+
+ " | eval f=position('ee' IN str2) | where isnull(str2) | fields str2,f";
71+
72+
var result = executeQuery(query);
73+
74+
assertEquals(4, result.getInt("total"));
75+
verifyDataRows(result,
76+
rows(null, null),
77+
rows(null, null),
78+
rows(null, null),
79+
rows(null, null));
80+
}
81+
82+
@Test
83+
public void test_position_function_with_function_as_arg() throws IOException {
84+
String query = "source=" + TEST_INDEX_CALCS
85+
+ " | eval f=position(upper(str3) IN str1) | where like(str1, 'BINDING SUPPLIES') | fields f";
86+
87+
var result = executeQuery(query);
88+
89+
assertEquals(1, result.getInt("total"));
90+
verifyDataRows(result, rows(15));
91+
}
92+
93+
@Test
94+
public void test_position_function_with_function_in_where_clause() throws IOException {
95+
String query = "source=" + TEST_INDEX_CALCS
96+
+ " | where position(str3 IN str2)=1 | fields str2";
97+
98+
var result = executeQuery(query);
99+
100+
assertEquals(2, result.getInt("total"));
101+
verifyDataRows(result, rows("eight"), rows("eleven"));
102+
}
103+
}

ppl/src/main/antlr/OpenSearchPPLLexer.g4

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@ LOG10: 'LOG10';
215215
LOG2: 'LOG2';
216216
MOD: 'MOD';
217217
PI: 'PI';
218+
POSITION: 'POSITION';
218219
POW: 'POW';
219220
POWER: 'POWER';
220221
RAND: 'RAND';

ppl/src/main/antlr/OpenSearchPPLParser.g4

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@ valueExpression
258258
| LT_PRTHS left=valueExpression binaryOperator
259259
right=valueExpression RT_PRTHS #parentheticBinaryArithmetic
260260
| primaryExpression #valueExpressionDefault
261+
| positionFunction #positionFunctionCall
261262
;
262263

263264
primaryExpression
@@ -272,6 +273,10 @@ constantFunction
272273
: constantFunctionName LT_PRTHS functionArgs? RT_PRTHS
273274
;
274275

276+
positionFunction
277+
: positionFunctionName LT_PRTHS functionArg IN functionArg RT_PRTHS
278+
;
279+
275280
booleanExpression
276281
: booleanFunctionCall
277282
;
@@ -367,6 +372,7 @@ evalFunctionName
367372
| textFunctionBase
368373
| conditionFunctionBase
369374
| systemFunctionBase
375+
| positionFunctionName
370376
;
371377

372378
functionArgs
@@ -455,6 +461,10 @@ textFunctionBase
455461
| RIGHT | LEFT | ASCII | LOCATE | REPLACE
456462
;
457463

464+
positionFunctionName
465+
: POSITION
466+
;
467+
458468
/** operators */
459469
comparisonOperator
460470
: EQUAL | NOT_EQUAL | LESS | NOT_LESS | GREATER | NOT_GREATER | REGEXP
@@ -575,4 +585,5 @@ keywordsCanBeId
575585
| dateAndTimeFunctionBase
576586
| textFunctionBase
577587
| mathematicalFunctionBase
588+
| positionFunctionName
578589
;

ppl/src/main/java/org/opensearch/sql/ppl/parser/AstExpressionBuilder.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
import org.opensearch.sql.ast.expression.Literal;
7373
import org.opensearch.sql.ast.expression.Not;
7474
import org.opensearch.sql.ast.expression.Or;
75+
import org.opensearch.sql.ast.expression.PositionFunction;
7576
import org.opensearch.sql.ast.expression.QualifiedName;
7677
import org.opensearch.sql.ast.expression.RelevanceFieldList;
7778
import org.opensearch.sql.ast.expression.Span;
@@ -270,6 +271,13 @@ private UnresolvedExpression visitConstantFunction(String functionName,
270271
.collect(Collectors.toList()));
271272
}
272273

274+
@Override
275+
public UnresolvedExpression visitPositionFunction(
276+
OpenSearchPPLParser.PositionFunctionContext ctx) {
277+
return new PositionFunction(visitFunctionArg(ctx.functionArg(0)),
278+
visitFunctionArg(ctx.functionArg(1)));
279+
}
280+
273281
private Function visitFunction(String functionName, FunctionArgsContext args) {
274282
return new Function(
275283
functionName,

ppl/src/test/java/org/opensearch/sql/ppl/parser/AstExpressionBuilderTest.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import static org.opensearch.sql.ast.dsl.AstDSL.not;
3636
import static org.opensearch.sql.ast.dsl.AstDSL.nullLiteral;
3737
import static org.opensearch.sql.ast.dsl.AstDSL.or;
38+
import static org.opensearch.sql.ast.dsl.AstDSL.position;
3839
import static org.opensearch.sql.ast.dsl.AstDSL.projectWithArg;
3940
import static org.opensearch.sql.ast.dsl.AstDSL.qualifiedName;
4041
import static org.opensearch.sql.ast.dsl.AstDSL.relation;
@@ -181,6 +182,18 @@ public void testEvalFunctionExprNoArgs() {
181182
));
182183
}
183184

185+
@Test
186+
public void testPositionFunctionExpr() {
187+
assertEqual("source=t | eval f=position(fieldA IN fieldB)",
188+
eval(
189+
relation("t"),
190+
let(
191+
field("f"),
192+
position(field("fieldA"), field("fieldB"))
193+
)
194+
));
195+
}
196+
184197
@Test
185198
public void testEvalBinaryOperationExpr() {
186199
assertEqual("source=t | eval f=a+b",

0 commit comments

Comments
 (0)