SQL: Implement IFNULL variant of COALESCE (#35762)

IFNULL is a MySQL variant (also used in other DBs) which
takes only 2 arguments and returns the first one that is not null.

Closes: #35749
This commit is contained in:
Marios Trivyzas 2018-11-21 17:07:07 +01:00 committed by GitHub
parent 03f003733d
commit e179bd393d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 122 additions and 3 deletions

View File

@ -44,3 +44,40 @@ include-tagged::{sql-specs}/docs.csv-spec[coalesceReturnNonNull]
----
include-tagged::{sql-specs}/docs.csv-spec[coalesceReturnNull]
----
[[sql-functions-conditional-ifnull]]
==== `IFNULL`
.Synopsis
[source, sql]
----
IFNULL ( expression<1>, expression<2> )
----
*Input*:
<1> 1st expression
<2> 2nd expression
*Output*: 2nd expression if 1st expression is null, otherwise 1st expression.
.Description
Variant of <<sql-functions-conditional-coalesce>> with only two arguments.
Returns the first of its arguments that is not null.
If all arguments are null, then it returns `null`.
["source","sql",subs="attributes,callouts,macros"]
----
include-tagged::{sql-specs}/docs.csv-spec[ifNullReturnFirst]
----
["source","sql",subs="attributes,callouts,macros"]
----
include-tagged::{sql-specs}/docs.csv-spec[ifNullReturnSecond]
----

View File

@ -20,6 +20,7 @@ STDDEV_POP |AGGREGATE
SUM_OF_SQUARES |AGGREGATE
VAR_POP |AGGREGATE
COALESCE |CONDITIONAL
IFNULL |CONDITIONAL
DAY |SCALAR
DAYNAME |SCALAR
DAYOFMONTH |SCALAR

View File

@ -197,6 +197,7 @@ STDDEV_POP |AGGREGATE
SUM_OF_SQUARES |AGGREGATE
VAR_POP |AGGREGATE
COALESCE |CONDITIONAL
IFNULL |CONDITIONAL
DAY |SCALAR
DAYNAME |SCALAR
DAYOFMONTH |SCALAR
@ -1531,3 +1532,24 @@ SELECT COALESCE(null, null, null, null) AS "coalesce";
null
// end::coalesceReturnNull
;
ifNullReturnFirst
// tag::ifNullReturnFirst
SELECT IFNULL('elastic', null) AS "ifnull";
ifnull
---------------
elastic
// end::ifNullReturnFirst
;
ifNullReturnSecond
// tag::ifNullReturnSecond
SELECT IFNULL(null, 'search') AS "ifnull";
ifnull
---------------
search
// end::ifNullReturnSecond
;

View File

@ -10,3 +10,6 @@ SELECT COALESCE(null, ABS(MAX(emp_no)) + 1, 123) AS c FROM test_emp GROUP BY lan
coalesceWhere
SELECT COALESCE(null, ABS(emp_no) + 1, 123) AS c FROM test_emp WHERE COALESCE(null, ABS(emp_no) + 1, 123, 321) > 100 ORDER BY emp_no NULLS FIRST LIMIT 5;
ifNullField
SELECT IFNULL(null, ABS(emp_no) + 1) AS c FROM test_emp ORDER BY emp_no LIMIT 5;

View File

@ -83,6 +83,7 @@ import org.elasticsearch.xpack.sql.expression.function.scalar.string.Space;
import org.elasticsearch.xpack.sql.expression.function.scalar.string.Substring;
import org.elasticsearch.xpack.sql.expression.function.scalar.string.UCase;
import org.elasticsearch.xpack.sql.expression.predicate.conditional.Coalesce;
import org.elasticsearch.xpack.sql.expression.predicate.conditional.IFNull;
import org.elasticsearch.xpack.sql.expression.predicate.operator.arithmetic.Mod;
import org.elasticsearch.xpack.sql.parser.ParsingException;
import org.elasticsearch.xpack.sql.tree.Location;
@ -145,6 +146,7 @@ public class FunctionRegistry {
// Scalar functions
// conditional
addToMap(def(Coalesce.class, Coalesce::new));
addToMap(def(IFNull.class, IFNull::new));
// Date
addToMap(def(DayName.class, DayName::new, "DAYNAME"),
def(DayOfMonth.class, DayOfMonth::new, "DAYOFMONTH", "DAY", "DOM"),

View File

@ -31,7 +31,7 @@ public class Coalesce extends ConditionalFunction {
}
@Override
protected NodeInfo<Coalesce> info() {
protected NodeInfo<? extends Coalesce> info() {
return NodeInfo.create(this, Coalesce::new, children());
}

View File

@ -0,0 +1,36 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.sql.expression.predicate.conditional;
import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.tree.Location;
import org.elasticsearch.xpack.sql.tree.NodeInfo;
import java.util.Arrays;
import java.util.List;
/**
* Variant of {@link Coalesce} with two args used by MySQL and ODBC.
*
* Name is `IFNull` to avoid having it registered as `IF_NULL` instead of `IFNULL`.
*/
public class IFNull extends Coalesce {
public IFNull(Location location, Expression first, Expression second) {
super(location, Arrays.asList(first, second));
}
@Override
public Expression replaceChildren(List<Expression> newChildren) {
return new IFNull(location(), newChildren.get(0), newChildren.get(1));
}
@Override
protected NodeInfo<IFNull> info() {
return NodeInfo.create(this, IFNull::new, children().get(0), children().get(1));
}
}

View File

@ -35,13 +35,14 @@ import org.elasticsearch.xpack.sql.expression.function.scalar.math.Floor;
import org.elasticsearch.xpack.sql.expression.function.scalar.string.Ascii;
import org.elasticsearch.xpack.sql.expression.function.scalar.string.Repeat;
import org.elasticsearch.xpack.sql.expression.predicate.BinaryOperator;
import org.elasticsearch.xpack.sql.expression.predicate.nulls.IsNull;
import org.elasticsearch.xpack.sql.expression.predicate.Range;
import org.elasticsearch.xpack.sql.expression.predicate.conditional.Coalesce;
import org.elasticsearch.xpack.sql.expression.predicate.conditional.IFNull;
import org.elasticsearch.xpack.sql.expression.predicate.logical.And;
import org.elasticsearch.xpack.sql.expression.predicate.logical.Not;
import org.elasticsearch.xpack.sql.expression.predicate.logical.Or;
import org.elasticsearch.xpack.sql.expression.predicate.nulls.IsNotNull;
import org.elasticsearch.xpack.sql.expression.predicate.nulls.IsNull;
import org.elasticsearch.xpack.sql.expression.predicate.operator.arithmetic.Add;
import org.elasticsearch.xpack.sql.expression.predicate.operator.arithmetic.Div;
import org.elasticsearch.xpack.sql.expression.predicate.operator.arithmetic.Mod;
@ -448,6 +449,22 @@ public class OptimizerTests extends ESTestCase {
assertEquals(Literal.TRUE, e.children().get(0));
}
public void testSimplifyIfNullNulls() {
Expression e = new SimplifyCoalesce().rule(new IFNull(EMPTY, Literal.NULL, Literal.NULL));
assertEquals(Coalesce.class, e.getClass());
assertEquals(0, e.children().size());
}
public void testSimplifyIfNullWithNullAndValue() {
Expression e = new SimplifyCoalesce().rule(new IFNull(EMPTY, Literal.NULL, ONE));
assertEquals(1, e.children().size());
assertEquals(ONE, e.children().get(0));
e = new SimplifyCoalesce().rule(new IFNull(EMPTY, ONE, Literal.NULL));
assertEquals(1, e.children().size());
assertEquals(ONE, e.children().get(0));
}
//
// Logical simplifications
//

View File

@ -25,6 +25,7 @@ import org.elasticsearch.xpack.sql.expression.gen.pipeline.BinaryPipesTests;
import org.elasticsearch.xpack.sql.expression.gen.pipeline.Pipe;
import org.elasticsearch.xpack.sql.expression.gen.processor.ConstantProcessor;
import org.elasticsearch.xpack.sql.expression.gen.processor.Processor;
import org.elasticsearch.xpack.sql.expression.predicate.conditional.IFNull;
import org.elasticsearch.xpack.sql.expression.predicate.operator.comparison.In;
import org.elasticsearch.xpack.sql.expression.predicate.fulltext.FullTextPredicate;
import org.elasticsearch.xpack.sql.expression.predicate.operator.comparison.InPipe;
@ -87,7 +88,7 @@ import static org.mockito.Mockito.mock;
public class NodeSubclassTests<T extends B, B extends Node<B>> extends ESTestCase {
private static final List<Class<? extends Node<?>>> CLASSES_WITH_MIN_TWO_CHILDREN = Arrays.asList(
In.class, InPipe.class, Percentile.class, Percentiles.class, PercentileRanks.class);
IFNull.class, In.class, InPipe.class, Percentile.class, Percentiles.class, PercentileRanks.class);
private final Class<T> subclass;