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
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
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
Expand Up @@ -15,16 +15,26 @@

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

import static com.amazon.opendistroforelasticsearch.sql.data.model.ExprValueUtils.LITERAL_NULL;
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 @@ -41,6 +51,9 @@ 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());
}

private static FunctionResolver not() {
Expand All @@ -64,8 +77,7 @@ public ExprValue not(ExprValue v) {
}
}

private static FunctionResolver isNull() {

private static FunctionResolver is_Null() {
return FunctionDSL
.define(BuiltinFunctionName.IS_NULL.getName(), Arrays.stream(ExprCoreType.values())
.map(type -> FunctionDSL
Expand All @@ -74,6 +86,18 @@ private static FunctionResolver isNull() {
Collectors.toList()));
}

private static FunctionResolver isNull() {
FunctionName functionName = BuiltinFunctionName.ISNULL.getName();
List<ExprType> typeList = ExprCoreType.coreTypes();
typeList.add(UNKNOWN);
return FunctionDSL
.define(functionName, typeList.stream()
.map(type -> FunctionDSL
.impl((v) -> ExprBooleanValue.of(v.isNull()), BOOLEAN, type))
.collect(
Collectors.toList()));
}

private static FunctionResolver isNotNull() {
return FunctionDSL
.define(BuiltinFunctionName.IS_NOT_NULL.getName(), Arrays.stream(ExprCoreType.values())
Expand All @@ -82,4 +106,62 @@ private static FunctionResolver isNotNull() {
.collect(
Collectors.toList()));
}

private static FunctionResolver ifNull() {
FunctionName functionName = BuiltinFunctionName.IFNULL.getName();
List<ExprType> typeList = ExprCoreType.coreTypes();
typeList.add(UNKNOWN);
harold-wang marked this conversation as resolved.
Show resolved Hide resolved

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();
typeList.add(UNKNOWN);

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;
}

/** 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) {
if (v1.isNull() || v1.isMissing() || v2.isNull() || v2.isMissing()) {
return v1;
} else if (v1.value().equals(v2.value())) {
harold-wang marked this conversation as resolved.
Show resolved Hide resolved
return LITERAL_NULL;
} else {
return v1;
}
chloe-zh marked this conversation as resolved.
Show resolved Hide resolved
}

}
Expand Up @@ -26,10 +26,13 @@
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 lombok.val;
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 +62,33 @@ public void test_not_missing() {
}

@Test
public void is_null_predicate() {
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.is_null(DSL.literal(ExprNullValue.of()));
assertEquals(BOOLEAN, expression.type());
assertEquals(LITERAL_TRUE, expression.valueOf(valueEnv()));
}

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

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

expression = dsl.isnull(DSL.literal("test"));
assertEquals(BOOLEAN, expression.type());
assertEquals(LITERAL_FALSE, expression.valueOf(valueEnv()));
}

@Test
public void is_not_null_predicate() {
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 +97,85 @@ public void is_not_null_predicate() {
assertEquals(BOOLEAN, expression.type());
assertEquals(LITERAL_FALSE, expression.valueOf(valueEnv()));
}

@Test
public void test_ifnull_predicate() {
Expression v1 = dsl.literal(100);
Expression v2 = dsl.literal(200);

FunctionExpression result = dsl.ifnull(v1, v2);
assertEquals(v1.valueOf(valueEnv()), result.valueOf(valueEnv()));

v1 = DSL.literal(ExprNullValue.of());
result = dsl.ifnull(v1, v2);
assertEquals(v2.valueOf(valueEnv()), result.valueOf(valueEnv()));

v1 = dsl.literal(100);
v2 = DSL.literal(ExprNullValue.of());
result = dsl.ifnull(v1, v2);
assertEquals(v1.valueOf(valueEnv()), result.valueOf(valueEnv()));

v1 = DSL.literal(ExprNullValue.of());
v2 = DSL.literal(ExprNullValue.of());
result = dsl.ifnull(v1, v2);
assertEquals(v2.valueOf(valueEnv()), result.valueOf(valueEnv()));
}

@Test
public void test_nullif_predicate() {
Expression v1 = dsl.literal(100);
Expression v2 = dsl.literal(200);
FunctionExpression result = dsl.nullif(v1, v2);
assertEquals(v1.valueOf(valueEnv()), result.valueOf(valueEnv()));

v1 = dsl.literal(100);
v2 = dsl.literal(100);
result = dsl.nullif(v1, v2);
chloe-zh marked this conversation as resolved.
Show resolved Hide resolved
assertEquals(LITERAL_NULL, result.valueOf(valueEnv()));

v1 = DSL.literal(ExprNullValue.of());
v2 = DSL.literal(ExprNullValue.of());
result = dsl.nullif(v1, v2);
assertEquals(LITERAL_NULL, result.valueOf(valueEnv()));
}

@Test
public void test_exprIfNull() {
ExprValue result = UnaryPredicateOperator.exprIfNull(LITERAL_NULL,
ExprValueUtils.integerValue(200));
assertEquals(ExprValueUtils.integerValue(200).value(), result.value());

result = UnaryPredicateOperator.exprIfNull(LITERAL_MISSING,
ExprValueUtils.integerValue(200));
assertEquals(ExprValueUtils.integerValue(200).value(), result.value());

result = UnaryPredicateOperator.exprIfNull(LITERAL_NULL,
LITERAL_MISSING);
assertEquals(LITERAL_MISSING.value(), result.value());

result = UnaryPredicateOperator.exprIfNull(LITERAL_MISSING,
LITERAL_NULL);
assertEquals(LITERAL_NULL.value(), result.value());
}

@Test
public void test_exprNullIf() {
ExprValue result = UnaryPredicateOperator.exprNullIf(LITERAL_NULL,
ExprValueUtils.integerValue(200));
assertEquals(LITERAL_NULL.value(), result.value());

result = UnaryPredicateOperator.exprNullIf(LITERAL_MISSING,
ExprValueUtils.integerValue(200));
assertEquals(LITERAL_MISSING.value(), result.value());

result = UnaryPredicateOperator.exprNullIf(ExprValueUtils.integerValue(200), LITERAL_NULL);
assertEquals(ExprValueUtils.integerValue(200).value(), result.value());

result = UnaryPredicateOperator.exprNullIf(ExprValueUtils.integerValue(200), LITERAL_MISSING);
assertEquals(ExprValueUtils.integerValue(200).value(), result.value());

result = UnaryPredicateOperator.exprNullIf(ExprValueUtils.integerValue(150),
ExprValueUtils.integerValue(150));
assertEquals(LITERAL_NULL.value(), result.value());
}
}
Expand Up @@ -52,7 +52,8 @@
* Correctness integration test by performing comparison test with other databases.
*/
@ESIntegTestCase.SuiteScopeTestCase
@ESIntegTestCase.ClusterScope(scope = ESIntegTestCase.Scope.SUITE, numDataNodes = 3, supportsDedicatedMasters = false, transportClientRatio = 1)
@ESIntegTestCase.ClusterScope(scope = ESIntegTestCase.Scope.SUITE, numDataNodes = 3,
harold-wang marked this conversation as resolved.
Show resolved Hide resolved
supportsDedicatedMasters = false, transportClientRatio = 1)
@ThreadLeakScope(ThreadLeakScope.Scope.NONE)
public class CorrectnessIT extends ESIntegTestCase {

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

@Test
public void ifnullShouldPassJDBC() throws IOException {
Assume.assumeTrue(isNewQueryEngineEabled());
JSONObject response = executeJdbcRequest(
"SELECT IFNULL(lastname, 'unknown') AS name FROM " + TEST_INDEX_ACCOUNT
+ " GROUP BY name");
assertEquals("name", response.query("/schema/0/name"));
assertEquals("IFNULL(lastname, \'unknown\')", response.query("/schema/0/name"));
assertEquals("name", response.query("/schema/0/alias"));
assertEquals("double", response.query("/schema/0/type"));
assertEquals("keyword", response.query("/schema/0/type"));
}

@Test
Expand All @@ -782,12 +783,13 @@ public void ifnullWithNullInputTest() throws IOException {

@Test
public void isnullShouldPassJDBC() {
Assume.assumeTrue(isNewQueryEngineEabled());
JSONObject response =
executeJdbcRequest(
"SELECT ISNULL(lastname) AS name FROM " + TEST_INDEX_ACCOUNT + " GROUP BY name");
assertEquals("name", response.query("/schema/0/name"));
"SELECT ISNULL(lastname) AS name FROM " + TEST_INDEX_ACCOUNT);
assertEquals("ISNULL(lastname)", response.query("/schema/0/name"));
assertEquals("name", response.query("/schema/0/alias"));
assertEquals("integer", response.query("/schema/0/type"));
assertEquals("boolean", response.query("/schema/0/type"));
}

@Test
Expand Down