Skip to content
This repository has been archived by the owner on Aug 2, 2022. It is now read-only.

Add Flow control function IF(expr1, expr2, expr3) #990

Merged
merged 9 commits into from Jan 25, 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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -526,6 +526,10 @@ public FunctionExpression nullif(Expression... expressions) {
return function(BuiltinFunctionName.NULLIF, expressions);
}

public FunctionExpression iffunction(Expression... expressions) {
return function(BuiltinFunctionName.IF, expressions);
}

public static Expression cases(Expression defaultResult,
WhenClause... whenClauses) {
return new CaseClause(Arrays.asList(whenClauses), defaultResult);
Expand Down
Expand Up @@ -139,6 +139,7 @@ public enum BuiltinFunctionName {
IS_NULL(FunctionName.of("is null")),
IS_NOT_NULL(FunctionName.of("is not null")),
IFNULL(FunctionName.of("ifnull")),
IF(FunctionName.of("if")),
NULLIF(FunctionName.of("nullif")),
ISNULL(FunctionName.of("isnull")),

Expand Down
Expand Up @@ -15,8 +15,6 @@

package com.amazon.opendistroforelasticsearch.sql.expression.operator.predicate;

import static com.amazon.opendistroforelasticsearch.sql.data.model.ExprValueUtils.LITERAL_FALSE;
import static com.amazon.opendistroforelasticsearch.sql.data.model.ExprValueUtils.LITERAL_MISSING;
import static com.amazon.opendistroforelasticsearch.sql.data.model.ExprValueUtils.LITERAL_NULL;
import static com.amazon.opendistroforelasticsearch.sql.data.model.ExprValueUtils.LITERAL_TRUE;

Expand Down Expand Up @@ -59,6 +57,7 @@ public static void register(BuiltinFunctionRepository repository) {
repository.register(nullIf());
repository.register(isNull(BuiltinFunctionName.IS_NULL));
repository.register(isNull(BuiltinFunctionName.ISNULL));
repository.register(ifFunction());
}

private static FunctionResolver not() {
Expand Down Expand Up @@ -100,21 +99,28 @@ private static FunctionResolver isNotNull() {
Collectors.toList()));
}

private static FunctionResolver ifNull() {
FunctionName functionName = BuiltinFunctionName.IFNULL.getName();
private static FunctionResolver ifFunction() {
FunctionName functionName = BuiltinFunctionName.IF.getName();
List<ExprCoreType> typeList = ExprCoreType.coreTypes();

List<SerializableFunction<FunctionName, org.apache.commons.lang3.tuple.Pair<FunctionSignature,
FunctionBuilder>>> functionsOne = typeList.stream().map(v ->
impl((UnaryPredicateOperator::exprIfNull), v, v, v))
impl((UnaryPredicateOperator::exprIf), v, BOOLEAN, v, v))
.collect(Collectors.toList());

FunctionResolver functionResolver = FunctionDSL.define(functionName, functionsOne);
return functionResolver;
}

private static FunctionResolver ifNull() {
FunctionName functionName = BuiltinFunctionName.IFNULL.getName();
List<ExprCoreType> typeList = ExprCoreType.coreTypes();

List<SerializableFunction<FunctionName, org.apache.commons.lang3.tuple.Pair<FunctionSignature,
FunctionBuilder>>> functionsTwo = typeList.stream().map(v ->
impl((UnaryPredicateOperator::exprIfNull), v, UNKNOWN, v))
FunctionBuilder>>> functionsOne = typeList.stream().map(v ->
impl((UnaryPredicateOperator::exprIfNull), v, v, v))
.collect(Collectors.toList());

functionsOne.addAll(functionsTwo);
FunctionResolver functionResolver = FunctionDSL.define(functionName, functionsOne);
return functionResolver;
}
Expand Down Expand Up @@ -149,4 +155,8 @@ public static ExprValue exprNullIf(ExprValue v1, ExprValue v2) {
return v1.equals(v2) ? LITERAL_NULL : v1;
}

public static ExprValue exprIf(ExprValue v1, ExprValue v2, ExprValue v3) {
return !v1.isNull() && !v1.isMissing() && LITERAL_TRUE.equals(v1) ? v2 : v3;
}

}
Expand Up @@ -80,9 +80,9 @@ private static Stream<Arguments> isNullArguments() {
private static Stream<Arguments> ifNullArguments() {
ArrayList<Expression> exprValueArrayList = new ArrayList<>();
exprValueArrayList.add(DSL.literal(123));
exprValueArrayList.add(DSL.literal("test"));
exprValueArrayList.add(DSL.literal(LITERAL_NULL));
exprValueArrayList.add(DSL.literal(321));
exprValueArrayList.add(DSL.literal(""));
exprValueArrayList.add(DSL.literal(LITERAL_NULL));

return Lists.cartesianProduct(exprValueArrayList, exprValueArrayList).stream()
.map(list -> {
Expand Down Expand Up @@ -115,12 +115,30 @@ private static Stream<Arguments> nullIfArguments() {
});
}

private static Stream<Arguments> ifArguments() {
ArrayList<Expression> exprValueArrayList = new ArrayList<>();
exprValueArrayList.add(DSL.literal(LITERAL_TRUE));
exprValueArrayList.add(DSL.literal(LITERAL_FALSE));
exprValueArrayList.add(DSL.literal(LITERAL_NULL));
exprValueArrayList.add(DSL.literal(LITERAL_MISSING));

return Lists.cartesianProduct(exprValueArrayList, exprValueArrayList).stream()
.map(list -> {
Expression e1 = list.get(0);
if (e1.valueOf(valueEnv()).value() == LITERAL_TRUE.value()) {
return Arguments.of(e1, DSL.literal("123"), DSL.literal("321"), DSL.literal("123"));
} else {
return Arguments.of(e1, DSL.literal("123"), DSL.literal("321"), DSL.literal("321"));
}
});
}

private static Stream<Arguments> exprIfNullArguments() {
ArrayList<ExprValue> exprValues = new ArrayList<>();
exprValues.add(LITERAL_NULL);
exprValues.add(LITERAL_MISSING);
exprValues.add(ExprValueUtils.integerValue(123));
exprValues.add(ExprValueUtils.stringValue("test"));
exprValues.add(ExprValueUtils.integerValue(456));

return Lists.cartesianProduct(exprValues, exprValues).stream()
.map(list -> {
Expand Down Expand Up @@ -200,18 +218,24 @@ public void test_ifnull_predicate(Expression v1, Expression v2, Expression expec
assertEquals(expected.valueOf(valueEnv()), dsl.ifnull(v1, v2).valueOf(valueEnv()));
}

@ParameterizedTest
@MethodSource("exprIfNullArguments")
public void test_exprIfNull_predicate(ExprValue v1, ExprValue v2, ExprValue expected) {
assertEquals(expected.value(), UnaryPredicateOperator.exprIfNull(v1, v2).value());
}

@ParameterizedTest
@MethodSource("nullIfArguments")
public void test_nullif_predicate(Expression v1, Expression v2, Expression expected) {
assertEquals(expected.valueOf(valueEnv()), dsl.nullif(v1, v2).valueOf(valueEnv()));
}

@ParameterizedTest
@MethodSource("ifArguments")
public void test_if_predicate(Expression v1, Expression v2, Expression v3, Expression expected) {
assertEquals(expected.valueOf(valueEnv()), dsl.iffunction(v1, v2, v3).valueOf(valueEnv()));
}

@ParameterizedTest
@MethodSource("exprIfNullArguments")
public void test_exprIfNull_predicate(ExprValue v1, ExprValue v2, ExprValue expected) {
assertEquals(expected.value(), UnaryPredicateOperator.exprIfNull(v1, v2).value());
}

@ParameterizedTest
@MethodSource("exprNullIfArguments")
public void test_exprNullIf_predicate(ExprValue v1, ExprValue v2, ExprValue expected) {
Expand Down
36 changes: 36 additions & 0 deletions docs/experiment/ppl/functions/condition.rst
Expand Up @@ -145,3 +145,39 @@ Example::
| False | Quility | Nanette |
| True | null | Dale |
+----------+------------+-------------+

IF
------

Description
>>>>>>>>>>>

Usage: if(condition, expr1, expr2) return expr1 if condition is true, otherwiser return expr2.

Argument type: all the supported data type, (NOTE : if expr1 and expr2 are different type, you will fail semantic check

Return type: any

Example::

od> source=accounts | eval result = if(true, firstname, lastname) | fields result, firstname, lastname
fetched rows / total rows = 4/4
+----------+-------------+------------+
| result | firstname | lastname |
|----------+-------------+------------|
| Amber | Amber | Duke |
| Hattie | Hattie | Bond |
| Nanette | Nanette | Bates |
| Dale | Dale | Adams |
+----------+-------------+------------+

od> source=accounts | eval result = if(false, firstname, lastname) | fields result, firstname, lastname
fetched rows / total rows = 4/4
+----------+-------------+------------+
| result | firstname | lastname |
|----------+-------------+------------|
| Duke | Amber | Duke |
| Bond | Hattie | Bond |
| Bates | Nanette | Bates |
| Adams | Dale | Adams |
+----------+-------------+------------+
34 changes: 34 additions & 0 deletions docs/user/dql/functions.rst
Expand Up @@ -1982,6 +1982,40 @@ Example::
| True | False |
+---------------+---------------+

IF
------
harold-wang marked this conversation as resolved.
Show resolved Hide resolved

Description
>>>>>>>>>>>

Specifications:

1. IF(condition, ES_TYPE1, ES_TYPE2) -> ES_TYPE1 or ES_TYPE2

Usage: if first parameter is true, return second parameter, otherwise return third one.

Argument type: condition as BOOLEAN, second and third can by any type

Return type: Any (NOTE : if parameters #2 and #3 has different type, you will fail semantic check"

Example::

od> SELECT IF(100 > 200, '100', '200')
fetched rows / total rows = 1/1
+-------------------------------+
| IF(100 > 200, '100', '200') |
|-------------------------------|
| 200 |
+-------------------------------+

od> SELECT IF(200 > 100, '100', '200')
fetched rows / total rows = 1/1
+-------------------------------+
| IF(200 > 100, '100', '200') |
|-------------------------------|
| 100 |
+-------------------------------+

CASE
----

Expand Down
Expand Up @@ -714,12 +714,13 @@ public void right() throws IOException {

@Test
public void ifFuncShouldPassJDBC() {
Assume.assumeTrue(isNewQueryEngineEabled());
JSONObject response = executeJdbcRequest(
"SELECT IF(age > 30, 'True', 'False') AS Ages FROM " + TEST_INDEX_ACCOUNT
+ " WHERE age IS NOT NULL GROUP BY Ages");
assertEquals("Ages", response.query("/schema/0/name"));
assertEquals("IF(age > 30, \'True\', \'False\')", response.query("/schema/0/name"));
assertEquals("Ages", response.query("/schema/0/alias"));
assertEquals("double", response.query("/schema/0/type"));
assertEquals("keyword", response.query("/schema/0/type"));
}

@Test
Expand Down
Expand Up @@ -63,16 +63,18 @@ public void ifnullShouldPassJDBC() throws IOException {
@Test
public void ifnullWithNullInputTest() {
Assume.assumeTrue(isNewQueryEngineEabled());

JSONObject response = new JSONObject(executeQuery(
"SELECT IFNULL(1/0, firstname) as IFNULL1 ,"
+ " IFNULL(firstname, 1/0) as IFNULL2 ,"
+ " IFNULL(1/0, 1/0) as IFNULL3 "
"SELECT IFNULL(null, firstname) as IFNULL1 ,"
+ " IFNULL(firstname, null) as IFNULL2 ,"
+ " IFNULL(null, null) as IFNULL3 "
+ " FROM " + TEST_INDEX_BANK_WITH_NULL_VALUES
+ " WHERE balance is null limit 2", "jdbc"));

verifySchema(response,
schema("IFNULL(1/0, firstname)", "IFNULL1", "keyword"),
schema("IFNULL(firstname, 1/0)", "IFNULL2", "integer"),
schema("IFNULL(1/0, 1/0)", "IFNULL3", "integer"));
schema("IFNULL(null, firstname)", "IFNULL1", "keyword"),
schema("IFNULL(firstname, null)", "IFNULL2", "keyword"),
schema("IFNULL(null, null)", "IFNULL3", "byte"));
verifyDataRows(response,
rows("Hattie", "Hattie", LITERAL_NULL.value()),
rows( "Elinor", "Elinor", LITERAL_NULL.value())
Expand All @@ -83,18 +85,19 @@ public void ifnullWithNullInputTest() {
public void ifnullWithMissingInputTest() {
Assume.assumeTrue(isNewQueryEngineEabled());
JSONObject response = new JSONObject(executeQuery(
"SELECT IFNULL(balance, firstname) as IFNULL1 ,"
+ " IFNULL(firstname, balance) as IFNULL2 ,"
"SELECT IFNULL(balance, 100) as IFNULL1, "
+ " IFNULL(200, balance) as IFNULL2, "
+ " IFNULL(balance, balance) as IFNULL3 "
+ " FROM " + TEST_INDEX_BANK_WITH_NULL_VALUES
+ " WHERE balance is null limit 2", "jdbc"));
+ " WHERE balance is null limit 3", "jdbc"));
verifySchema(response,
schema("IFNULL(balance, firstname)", "IFNULL1", "keyword"),
schema("IFNULL(firstname, balance)", "IFNULL2", "long"),
schema("IFNULL(balance, 100)", "IFNULL1", "long"),
schema("IFNULL(200, balance)", "IFNULL2", "long"),
schema("IFNULL(balance, balance)", "IFNULL3", "long"));
verifyDataRows(response,
rows("Hattie", "Hattie", LITERAL_NULL.value()),
rows( "Elinor", "Elinor", LITERAL_NULL.value())
rows(100, 200, null),
rows(100, 200, null),
rows(100, 200, null)
);
}

Expand All @@ -113,8 +116,8 @@ public void nullifWithNotNullInputTestOne(){
Assume.assumeTrue(isNewQueryEngineEabled());
JSONObject response = new JSONObject(executeQuery(
"SELECT NULLIF(firstname, 'Amber JOHnny') as testnullif "
+ "FROM " + TEST_INDEX_BANK_WITH_NULL_VALUES
+ " limit 2 ", "jdbc"));
+ "FROM " + TEST_INDEX_BANK_WITH_NULL_VALUES
+ " limit 2 ", "jdbc"));
verifySchema(response,
schema("NULLIF(firstname, 'Amber JOHnny')", "testnullif", "keyword"));
verifyDataRows(response,
Expand Down Expand Up @@ -193,6 +196,39 @@ public void isnullWithMathExpr() throws IOException{
);
}

@Test
public void ifShouldPassJDBC() throws IOException {
Assume.assumeTrue(isNewQueryEngineEabled());
JSONObject response = executeJdbcRequest(
"SELECT IF(2 > 0, \'hello\', \'world\') AS name FROM " + TEST_INDEX_ACCOUNT);
assertEquals("IF(2 > 0, \'hello\', \'world\')", response.query("/schema/0/name"));
assertEquals("name", response.query("/schema/0/alias"));
assertEquals("keyword", response.query("/schema/0/type"));
}

@Test
public void ifWithTrueAndFalseCondition() throws IOException {
Assume.assumeTrue(isNewQueryEngineEabled());
JSONObject response = new JSONObject(executeQuery(
"SELECT IF(2 < 0, firstname, lastname) as IF0, "
+ " IF(2 > 0, firstname, lastname) as IF1, "
+ " firstname as IF2, "
+ " lastname as IF3 "
+ " FROM " + TEST_INDEX_BANK_WITH_NULL_VALUES
+ " limit 2 ", "jdbc" ));
verifySchema(response,
schema("IF(2 < 0, firstname, lastname)", "IF0", "keyword"),
schema("IF(2 > 0, firstname, lastname)", "IF1", "keyword"),
schema("firstname", "IF2", "text"),
schema("lastname", "IF3", "keyword")
);
verifyDataRows(response,
rows("Duke Willmington", "Amber JOHnny", "Amber JOHnny", "Duke Willmington"),
rows("Bond", "Hattie", "Hattie", "Bond")
);

}

private SearchHits query(String query) throws IOException {
final String rsp = executeQueryWithStringOutput(query);

Expand Down
Expand Up @@ -13,6 +13,6 @@ CASE WHEN 'test' = 'hello world' THEN 'Hello' WHEN 1.0 = 2.0 THEN 'One' ELSE TRI
CASE 1 WHEN 1 THEN 'One' ELSE NULL END AS cases
CASE 1 WHEN 1 THEN 'One' WHEN 2 THEN 'Two' ELSE NULL END AS cases
CASE 2 WHEN 1 THEN NULL WHEN 2 THEN 'Two' ELSE NULL END AS cases
IFNULL(1/0, AvgTicketPrice) from kibana_sample_data_flights
IFNULL(FlightTimeHour, AvgTicketPrice) from kibana_sample_data_flights
IFNULL(AvgTicketPrice, 1/0) from kibana_sample_data_flights
IFNULL(null, AvgTicketPrice) from kibana_sample_data_flights
IFNULL(AvgTicketPrice, 100) from kibana_sample_data_flights
IFNULL(AvgTicketPrice, null) from kibana_sample_data_flights
2 changes: 2 additions & 0 deletions ppl/src/main/antlr/OpenDistroPPLLexer.g4
Expand Up @@ -238,6 +238,8 @@ ISNOTNULL: 'ISNOTNULL';
// FLOWCONTROL FUNCTIONS
IFNULL: 'IFNULL';
NULLIF: 'NULLIF';
IF: 'IF';


// LITERALS AND VALUES
//STRING_LITERAL: DQUOTA_STRING | SQUOTA_STRING | BQUOTA_STRING;
Expand Down
2 changes: 1 addition & 1 deletion ppl/src/main/antlr/OpenDistroPPLParser.g4
Expand Up @@ -254,7 +254,7 @@ dateAndTimeFunctionBase
/** condition function return boolean value */
conditionFunctionBase
: LIKE
| ISNULL | ISNOTNULL | IFNULL | NULLIF
| IF | ISNULL | ISNOTNULL | IFNULL | NULLIF
;

textFunctionBase
Expand Down
2 changes: 1 addition & 1 deletion sql/src/main/antlr/OpenDistroSQLParser.g4
Expand Up @@ -358,7 +358,7 @@ textFunctionName
;

flowControlFunctionName
: IFNULL | NULLIF | ISNULL
: IF | IFNULL | NULLIF | ISNULL
;

functionArgs
Expand Down