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

Enable sql function ifnull, nullif and isnull #962

Merged
merged 13 commits into from
Jan 14, 2021
Original file line number Diff line number Diff line change
Expand Up @@ -507,13 +507,25 @@ private Aggregator aggregate(BuiltinFunctionName functionName, Expression... exp
}

public FunctionExpression isnull(Expression... expressions) {
return function(BuiltinFunctionName.ISNULL, expressions);
}

public FunctionExpression is_null(Expression... expressions) {
return function(BuiltinFunctionName.IS_NULL, expressions);
}

public FunctionExpression isnotnull(Expression... expressions) {
return function(BuiltinFunctionName.IS_NOT_NULL, expressions);
}

public FunctionExpression ifnull(Expression... expressions) {
return function(BuiltinFunctionName.IFNULL, expressions);
}

public FunctionExpression nullif(Expression... expressions) {
return function(BuiltinFunctionName.NULLIF, expressions);
}

public static Expression cases(Expression defaultResult,
WhenClause... whenClauses) {
return new CaseClause(Arrays.asList(whenClauses), defaultResult);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,9 @@ public enum BuiltinFunctionName {
*/
IS_NULL(FunctionName.of("is null")),
IS_NOT_NULL(FunctionName.of("is not null")),
IFNULL(FunctionName.of("ifnull")),
NULLIF(FunctionName.of("nullif")),
ISNULL(FunctionName.of("isnull")),
harold-wang marked this conversation as resolved.
Show resolved Hide resolved

ROW_NUMBER(FunctionName.of("row_number")),
RANK(FunctionName.of("rank")),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,31 @@

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;

import static com.amazon.opendistroforelasticsearch.sql.data.type.ExprCoreType.BOOLEAN;
import static com.amazon.opendistroforelasticsearch.sql.data.type.ExprCoreType.UNKNOWN;

import static com.amazon.opendistroforelasticsearch.sql.expression.function.FunctionDSL.impl;

import com.amazon.opendistroforelasticsearch.sql.data.model.ExprBooleanValue;
import com.amazon.opendistroforelasticsearch.sql.data.model.ExprValue;
import com.amazon.opendistroforelasticsearch.sql.data.type.ExprCoreType;
import com.amazon.opendistroforelasticsearch.sql.data.type.ExprType;
import com.amazon.opendistroforelasticsearch.sql.expression.function.BuiltinFunctionName;
import com.amazon.opendistroforelasticsearch.sql.expression.function.BuiltinFunctionRepository;
import com.amazon.opendistroforelasticsearch.sql.expression.function.FunctionBuilder;
import com.amazon.opendistroforelasticsearch.sql.expression.function.FunctionDSL;
import com.amazon.opendistroforelasticsearch.sql.expression.function.FunctionName;
import com.amazon.opendistroforelasticsearch.sql.expression.function.FunctionResolver;
import com.amazon.opendistroforelasticsearch.sql.expression.function.FunctionSignature;
import com.amazon.opendistroforelasticsearch.sql.expression.function.SerializableFunction;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import lombok.experimental.UtilityClass;

Expand All @@ -39,8 +54,11 @@ public class UnaryPredicateOperator {
*/
public static void register(BuiltinFunctionRepository repository) {
repository.register(not());
repository.register(isNull());
repository.register(isNotNull());
repository.register(ifNull());
repository.register(nullIf());
repository.register(is_Null(BuiltinFunctionName.IS_NULL));
repository.register(is_Null(BuiltinFunctionName.ISNULL));
}

private static FunctionResolver not() {
Expand All @@ -64,10 +82,9 @@ public ExprValue not(ExprValue v) {
}
}

private static FunctionResolver isNull() {

private static FunctionResolver is_Null(BuiltinFunctionName funcName) {
harold-wang marked this conversation as resolved.
Show resolved Hide resolved
return FunctionDSL
.define(BuiltinFunctionName.IS_NULL.getName(), Arrays.stream(ExprCoreType.values())
.define(funcName.getName(), Arrays.stream(ExprCoreType.values())
.map(type -> FunctionDSL
.impl((v) -> ExprBooleanValue.of(v.isNull()), BOOLEAN, type))
.collect(
Expand All @@ -82,4 +99,54 @@ private static FunctionResolver isNotNull() {
.collect(
Collectors.toList()));
}

private static FunctionResolver ifNull() {
FunctionName functionName = BuiltinFunctionName.IFNULL.getName();
List<ExprType> 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))
harold-wang marked this conversation as resolved.
Show resolved Hide resolved
.collect(Collectors.toList());

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

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

private static FunctionResolver nullIf() {
FunctionName functionName = BuiltinFunctionName.NULLIF.getName();
List<ExprType> typeList = ExprCoreType.coreTypes();

FunctionResolver functionResolver =
FunctionDSL.define(functionName,
typeList.stream().map(v ->
impl((UnaryPredicateOperator::exprNullIf), v, v, v))
.collect(Collectors.toList()));
return functionResolver;
}

/** v2 if v1 is null.
* @param v1 varable 1
* @param v2 varable 2
* @return v2 if v1 is null
*/
public static ExprValue exprIfNull(ExprValue v1, ExprValue v2) {
return (v1.isNull() || v1.isMissing()) ? v2 : v1;
}

/** return null if v1 equls to v2.
* @param v1 varable 1
* @param v2 varable 2
* @return null if v1 equls to v2
*/
public static ExprValue exprNullIf(ExprValue v1, ExprValue v2) {
return v1.equals(v2) ? LITERAL_NULL : v1;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,16 @@
import static org.junit.jupiter.api.Assertions.assertEquals;

import com.amazon.opendistroforelasticsearch.sql.data.model.ExprNullValue;
import com.amazon.opendistroforelasticsearch.sql.data.model.ExprValue;
import com.amazon.opendistroforelasticsearch.sql.data.model.ExprValueUtils;
import com.amazon.opendistroforelasticsearch.sql.expression.DSL;
import com.amazon.opendistroforelasticsearch.sql.expression.Expression;
import com.amazon.opendistroforelasticsearch.sql.expression.ExpressionTestBase;
import com.amazon.opendistroforelasticsearch.sql.expression.FunctionExpression;

import java.lang.reflect.Array;
import java.util.ArrayList;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
Expand Down Expand Up @@ -59,18 +65,37 @@ public void test_not_missing() {
}

@Test
public void is_null_predicate() {
FunctionExpression expression = dsl.isnull(DSL.literal(1));
public void test_is_null_predicate() {
FunctionExpression expression = dsl.is_null(DSL.literal(1));
assertEquals(BOOLEAN, expression.type());
assertEquals(LITERAL_FALSE, expression.valueOf(valueEnv()));

expression = dsl.isnull(DSL.literal(ExprNullValue.of()));
expression = dsl.is_null(DSL.literal(ExprNullValue.of()));
assertEquals(BOOLEAN, expression.type());
assertEquals(LITERAL_TRUE, expression.valueOf(valueEnv()));
}

@Test
public void is_not_null_predicate() {
public void test_isnull_predicate() {
ArrayList<Expression> exprValueArrayList = new ArrayList<>();
exprValueArrayList.add(dsl.literal("test"));
exprValueArrayList.add(dsl.literal(100));
exprValueArrayList.add(dsl.literal(""));
harold-wang marked this conversation as resolved.
Show resolved Hide resolved

for (Expression expression : exprValueArrayList) {
FunctionExpression functionExpression = dsl.isnull(expression);
assertEquals(BOOLEAN, functionExpression.type());
if (expression.valueOf(valueEnv()) == LITERAL_NULL
|| expression.valueOf(valueEnv()) == LITERAL_MISSING) {
assertEquals(LITERAL_TRUE, functionExpression.valueOf(valueEnv()));
} else {
assertEquals(LITERAL_FALSE, functionExpression.valueOf(valueEnv()));
}
}
}

@Test
public void test_is_not_null_predicate() {
FunctionExpression expression = dsl.isnotnull(DSL.literal(1));
assertEquals(BOOLEAN, expression.type());
assertEquals(LITERAL_TRUE, expression.valueOf(valueEnv()));
Expand All @@ -79,4 +104,82 @@ public void is_not_null_predicate() {
assertEquals(BOOLEAN, expression.type());
assertEquals(LITERAL_FALSE, expression.valueOf(valueEnv()));
}

@Test
public void test_ifnull_predicate() {
ArrayList<Expression> exprValueArrayList = new ArrayList<>();
exprValueArrayList.add(dsl.literal(100));
exprValueArrayList.add(dsl.literal(200));

for (Expression expressionOne : exprValueArrayList) {
for (Expression expressionTwo : exprValueArrayList) {
FunctionExpression functionExpression = dsl.ifnull(expressionOne, expressionTwo);
if (expressionOne.valueOf(valueEnv()) == LITERAL_NULL
|| expressionOne.valueOf(valueEnv()) == LITERAL_MISSING) {
assertEquals(expressionTwo.valueOf(valueEnv()), functionExpression.valueOf(valueEnv()));
} else {
assertEquals(expressionOne.valueOf(valueEnv()), functionExpression.valueOf(valueEnv()));
}
}
}

}

@Test
public void test_nullif_predicate() {
ArrayList<Expression> exprValueArrayList = new ArrayList<>();
exprValueArrayList.add(DSL.literal(123));
exprValueArrayList.add(DSL.literal(321));

for (Expression v1 : exprValueArrayList) {
for (Expression v2 : exprValueArrayList) {
FunctionExpression result = dsl.nullif(v1, v2);
if (v1.valueOf(valueEnv()) == v2.valueOf(valueEnv())) {
assertEquals(LITERAL_NULL, result.valueOf(valueEnv()));
} else {
assertEquals(v1.valueOf(valueEnv()), result.valueOf(valueEnv()));
}
}
}
}

@Test
public void test_exprIfNull() {
ArrayList<ExprValue> exprValues = new ArrayList<>();
exprValues.add(LITERAL_NULL);
exprValues.add(LITERAL_MISSING);
exprValues.add(ExprValueUtils.integerValue(123));
exprValues.add(ExprValueUtils.stringValue("test"));

for (ExprValue exprValueOne : exprValues) {
for (ExprValue exprValueTwo : exprValues) {
ExprValue result = UnaryPredicateOperator.exprIfNull(exprValueOne, exprValueTwo);
if (exprValueOne.isNull() || exprValueOne.isMissing()) {
assertEquals(exprValueTwo.value(), result.value());
} else {
assertEquals(exprValueOne.value(), result.value());
}
}
}
}

@Test
public void test_exprNullIf() {
ArrayList<ExprValue> exprValues = new ArrayList<>();
exprValues.add(LITERAL_NULL);
exprValues.add(LITERAL_MISSING);
exprValues.add(ExprValueUtils.integerValue(123));
exprValues.add(ExprValueUtils.integerValue(456));

for (ExprValue exprValueOne : exprValues) {
for (ExprValue exprValueTwo : exprValues) {
ExprValue result = UnaryPredicateOperator.exprNullIf(exprValueOne, exprValueTwo);
if (exprValueOne.equals(exprValueTwo)) {
assertEquals(LITERAL_NULL.value(), result.value());
} else {
assertEquals(exprValueOne.value(), result.value());
}
}
}
}
}
75 changes: 75 additions & 0 deletions docs/experiment/ppl/functions/condition.rst
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,78 @@ Example, the account 13 doesn't have email field::
| 13 | null |
+------------------+---------+

IFNULL
------

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

Usage: ifnull(field1, field2) return field2 if field1 is null.

Argument type: all the supported data type, (NOTE : if two parametershas different type, return result is correct, but return type could be wrong)

Return type: any
harold-wang marked this conversation as resolved.
Show resolved Hide resolved

Example::

od> source=accounts | eval result = ifnull(employer, 'default') | fields result, employer, firstname
fetched rows / total rows = 4/4
+----------+------------+-------------+
| result | employer | firstname |
|----------+------------+-------------|
| Pyrami | Pyrami | Amber |
| Netagy | Netagy | Hattie |
| Quility | Quility | Nanette |
| default | null | Dale |
+----------+------------+-------------+

NULLIF
------

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

Usage: nullif(field1, field2) return null if two parameters are same, otherwiser return field1.

Argument type: all the supported data type, (NOTE : if two parametershas different type, return result is correct, but return type could be wrong)

Return type: any

Example::

od> source=accounts | eval result = nullif(employer, 'Pyrami') | fields result, employer, firstname
fetched rows / total rows = 4/4
+----------+------------+-------------+
| result | employer | firstname |
|----------+------------+-------------|
| null | Pyrami | Amber |
| Netagy | Netagy | Hattie |
| Quility | Quility | Nanette |
| null | null | Dale |
+----------+------------+-------------+


ISNULL
------

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

Usage: nullif(field1, field2) return null if two parameters are same, otherwiser return field1.

Argument type: all the supported data type

Return type: any

Example::

od> source=accounts | eval result = isnull(employer) | fields result, employer, firstname
fetched rows / total rows = 4/4
+----------+------------+-------------+
| result | employer | firstname |
|----------+------------+-------------|
| False | Pyrami | Amber |
| False | Netagy | Hattie |
| False | Quility | Nanette |
| True | null | Dale |
+----------+------------+-------------+
Loading