Forráskód Böngészése

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
Marios Trivyzas 7 éve
szülő
commit
e179bd393d

+ 37 - 0
docs/reference/sql/functions/conditional.asciidoc

@@ -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]
+----

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

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

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

@@ -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
+;

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

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

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

@@ -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"),

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

@@ -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());
     }
 

+ 36 - 0
x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/IFNull.java

@@ -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));
+    }
+}

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

@@ -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
     //

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

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