Skip to content

Commit e179bd3

Browse files
authored
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
1 parent 03f0037 commit e179bd3

File tree

9 files changed

+122
-3
lines changed

9 files changed

+122
-3
lines changed

docs/reference/sql/functions/conditional.asciidoc

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,40 @@ include-tagged::{sql-specs}/docs.csv-spec[coalesceReturnNonNull]
4444
----
4545
include-tagged::{sql-specs}/docs.csv-spec[coalesceReturnNull]
4646
----
47+
48+
49+
[[sql-functions-conditional-ifnull]]
50+
==== `IFNULL`
51+
52+
.Synopsis
53+
[source, sql]
54+
----
55+
IFNULL ( expression<1>, expression<2> )
56+
----
57+
58+
*Input*:
59+
60+
<1> 1st expression
61+
62+
<2> 2nd expression
63+
64+
65+
*Output*: 2nd expression if 1st expression is null, otherwise 1st expression.
66+
67+
.Description
68+
69+
Variant of <<sql-functions-conditional-coalesce>> with only two arguments.
70+
Returns the first of its arguments that is not null.
71+
If all arguments are null, then it returns `null`.
72+
73+
74+
75+
["source","sql",subs="attributes,callouts,macros"]
76+
----
77+
include-tagged::{sql-specs}/docs.csv-spec[ifNullReturnFirst]
78+
----
79+
80+
["source","sql",subs="attributes,callouts,macros"]
81+
----
82+
include-tagged::{sql-specs}/docs.csv-spec[ifNullReturnSecond]
83+
----

x-pack/plugin/sql/qa/src/main/resources/command.csv-spec

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ STDDEV_POP |AGGREGATE
2020
SUM_OF_SQUARES |AGGREGATE
2121
VAR_POP |AGGREGATE
2222
COALESCE |CONDITIONAL
23+
IFNULL |CONDITIONAL
2324
DAY |SCALAR
2425
DAYNAME |SCALAR
2526
DAYOFMONTH |SCALAR

x-pack/plugin/sql/qa/src/main/resources/docs.csv-spec

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@ STDDEV_POP |AGGREGATE
197197
SUM_OF_SQUARES |AGGREGATE
198198
VAR_POP |AGGREGATE
199199
COALESCE |CONDITIONAL
200+
IFNULL |CONDITIONAL
200201
DAY |SCALAR
201202
DAYNAME |SCALAR
202203
DAYOFMONTH |SCALAR
@@ -1531,3 +1532,24 @@ SELECT COALESCE(null, null, null, null) AS "coalesce";
15311532
null
15321533
// end::coalesceReturnNull
15331534
;
1535+
1536+
ifNullReturnFirst
1537+
// tag::ifNullReturnFirst
1538+
SELECT IFNULL('elastic', null) AS "ifnull";
1539+
1540+
ifnull
1541+
---------------
1542+
elastic
1543+
// end::ifNullReturnFirst
1544+
;
1545+
1546+
1547+
ifNullReturnSecond
1548+
// tag::ifNullReturnSecond
1549+
SELECT IFNULL(null, 'search') AS "ifnull";
1550+
1551+
ifnull
1552+
---------------
1553+
search
1554+
// end::ifNullReturnSecond
1555+
;

x-pack/plugin/sql/qa/src/main/resources/null.sql-spec

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,6 @@ SELECT COALESCE(null, ABS(MAX(emp_no)) + 1, 123) AS c FROM test_emp GROUP BY lan
1010

1111
coalesceWhere
1212
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;
13+
14+
ifNullField
15+
SELECT IFNULL(null, ABS(emp_no) + 1) AS c FROM test_emp ORDER BY emp_no LIMIT 5;

x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/FunctionRegistry.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@
8383
import org.elasticsearch.xpack.sql.expression.function.scalar.string.Substring;
8484
import org.elasticsearch.xpack.sql.expression.function.scalar.string.UCase;
8585
import org.elasticsearch.xpack.sql.expression.predicate.conditional.Coalesce;
86+
import org.elasticsearch.xpack.sql.expression.predicate.conditional.IFNull;
8687
import org.elasticsearch.xpack.sql.expression.predicate.operator.arithmetic.Mod;
8788
import org.elasticsearch.xpack.sql.parser.ParsingException;
8889
import org.elasticsearch.xpack.sql.tree.Location;
@@ -145,6 +146,7 @@ private void defineDefaultFunctions() {
145146
// Scalar functions
146147
// conditional
147148
addToMap(def(Coalesce.class, Coalesce::new));
149+
addToMap(def(IFNull.class, IFNull::new));
148150
// Date
149151
addToMap(def(DayName.class, DayName::new, "DAYNAME"),
150152
def(DayOfMonth.class, DayOfMonth::new, "DAYOFMONTH", "DAY", "DOM"),

x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/Coalesce.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public Coalesce(Location location, List<Expression> fields) {
3131
}
3232

3333
@Override
34-
protected NodeInfo<Coalesce> info() {
34+
protected NodeInfo<? extends Coalesce> info() {
3535
return NodeInfo.create(this, Coalesce::new, children());
3636
}
3737

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
package org.elasticsearch.xpack.sql.expression.predicate.conditional;
8+
9+
import org.elasticsearch.xpack.sql.expression.Expression;
10+
import org.elasticsearch.xpack.sql.tree.Location;
11+
import org.elasticsearch.xpack.sql.tree.NodeInfo;
12+
13+
import java.util.Arrays;
14+
import java.util.List;
15+
16+
/**
17+
* Variant of {@link Coalesce} with two args used by MySQL and ODBC.
18+
*
19+
* Name is `IFNull` to avoid having it registered as `IF_NULL` instead of `IFNULL`.
20+
*/
21+
public class IFNull extends Coalesce {
22+
23+
public IFNull(Location location, Expression first, Expression second) {
24+
super(location, Arrays.asList(first, second));
25+
}
26+
27+
@Override
28+
public Expression replaceChildren(List<Expression> newChildren) {
29+
return new IFNull(location(), newChildren.get(0), newChildren.get(1));
30+
}
31+
32+
@Override
33+
protected NodeInfo<IFNull> info() {
34+
return NodeInfo.create(this, IFNull::new, children().get(0), children().get(1));
35+
}
36+
}

x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/optimizer/OptimizerTests.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,14 @@
3535
import org.elasticsearch.xpack.sql.expression.function.scalar.string.Ascii;
3636
import org.elasticsearch.xpack.sql.expression.function.scalar.string.Repeat;
3737
import org.elasticsearch.xpack.sql.expression.predicate.BinaryOperator;
38-
import org.elasticsearch.xpack.sql.expression.predicate.nulls.IsNull;
3938
import org.elasticsearch.xpack.sql.expression.predicate.Range;
4039
import org.elasticsearch.xpack.sql.expression.predicate.conditional.Coalesce;
40+
import org.elasticsearch.xpack.sql.expression.predicate.conditional.IFNull;
4141
import org.elasticsearch.xpack.sql.expression.predicate.logical.And;
4242
import org.elasticsearch.xpack.sql.expression.predicate.logical.Not;
4343
import org.elasticsearch.xpack.sql.expression.predicate.logical.Or;
4444
import org.elasticsearch.xpack.sql.expression.predicate.nulls.IsNotNull;
45+
import org.elasticsearch.xpack.sql.expression.predicate.nulls.IsNull;
4546
import org.elasticsearch.xpack.sql.expression.predicate.operator.arithmetic.Add;
4647
import org.elasticsearch.xpack.sql.expression.predicate.operator.arithmetic.Div;
4748
import org.elasticsearch.xpack.sql.expression.predicate.operator.arithmetic.Mod;
@@ -448,6 +449,22 @@ public void testSimplifyCoalesceFirstLiteral() {
448449
assertEquals(Literal.TRUE, e.children().get(0));
449450
}
450451

452+
public void testSimplifyIfNullNulls() {
453+
Expression e = new SimplifyCoalesce().rule(new IFNull(EMPTY, Literal.NULL, Literal.NULL));
454+
assertEquals(Coalesce.class, e.getClass());
455+
assertEquals(0, e.children().size());
456+
}
457+
458+
public void testSimplifyIfNullWithNullAndValue() {
459+
Expression e = new SimplifyCoalesce().rule(new IFNull(EMPTY, Literal.NULL, ONE));
460+
assertEquals(1, e.children().size());
461+
assertEquals(ONE, e.children().get(0));
462+
463+
e = new SimplifyCoalesce().rule(new IFNull(EMPTY, ONE, Literal.NULL));
464+
assertEquals(1, e.children().size());
465+
assertEquals(ONE, e.children().get(0));
466+
}
467+
451468
//
452469
// Logical simplifications
453470
//

x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/tree/NodeSubclassTests.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.elasticsearch.xpack.sql.expression.gen.pipeline.Pipe;
2626
import org.elasticsearch.xpack.sql.expression.gen.processor.ConstantProcessor;
2727
import org.elasticsearch.xpack.sql.expression.gen.processor.Processor;
28+
import org.elasticsearch.xpack.sql.expression.predicate.conditional.IFNull;
2829
import org.elasticsearch.xpack.sql.expression.predicate.operator.comparison.In;
2930
import org.elasticsearch.xpack.sql.expression.predicate.fulltext.FullTextPredicate;
3031
import org.elasticsearch.xpack.sql.expression.predicate.operator.comparison.InPipe;
@@ -87,7 +88,7 @@
8788
public class NodeSubclassTests<T extends B, B extends Node<B>> extends ESTestCase {
8889

8990
private static final List<Class<? extends Node<?>>> CLASSES_WITH_MIN_TWO_CHILDREN = Arrays.asList(
90-
In.class, InPipe.class, Percentile.class, Percentiles.class, PercentileRanks.class);
91+
IFNull.class, In.class, InPipe.class, Percentile.class, Percentiles.class, PercentileRanks.class);
9192

9293
private final Class<T> subclass;
9394

0 commit comments

Comments
 (0)