Browse Source

SQL: Implement NULLIF(expr1, expr2) function (#35826)

NULLIF returns null if the 2 expressions are equal or the
expr1 otherwise.

Closes: #35818
Marios Trivyzas 6 years ago
parent
commit
410f570d5f
15 changed files with 316 additions and 17 deletions
  1. 35 0
      docs/reference/sql/functions/conditional.asciidoc
  2. 1 0
      x-pack/plugin/sql/qa/src/main/resources/command.csv-spec
  3. 22 0
      x-pack/plugin/sql/qa/src/main/resources/docs.csv-spec
  4. 10 1
      x-pack/plugin/sql/qa/src/main/resources/null.sql-spec
  5. 20 3
      x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/FunctionRegistry.java
  6. 2 0
      x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/Processors.java
  7. 5 0
      x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/whitelist/InternalSqlScriptUtils.java
  8. 5 7
      x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/IfNull.java
  9. 88 0
      x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/NullIf.java
  10. 36 0
      x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/NullIfPipe.java
  11. 73 0
      x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/NullIfProcessor.java
  12. 5 0
      x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/optimizer/Optimizer.java
  13. 1 0
      x-pack/plugin/sql/src/main/resources/org/elasticsearch/xpack/sql/plugin/sql_whitelist.txt
  14. 11 4
      x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/optimizer/OptimizerTests.java
  15. 2 2
      x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/tree/NodeSubclassTests.java

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

@@ -155,3 +155,38 @@ include-tagged::{sql-specs}/docs.csv-spec[nvlReturnFirst]
 ----
 include-tagged::{sql-specs}/docs.csv-spec[nvlReturnSecond]
 ----
+
+
+[[sql-functions-conditional-nullif]]
+==== `NULLIF`
+
+.Synopsis
+[source, sql]
+----
+NULLIF ( expression<1>, expression<2> )
+----
+
+*Input*:
+
+<1> 1st expression
+
+<2> 2nd expression
+
+
+*Output*: `null` if the 2 expressions are equal, otherwise the 1st expression.
+
+.Description
+
+Returns `null` when the two input expressions are equal and
+if not, it returns the 1st expression.
+
+
+["source","sql",subs="attributes,callouts,macros"]
+----
+include-tagged::{sql-specs}/docs.csv-spec[nullIfReturnFirst]
+----
+
+["source","sql",subs="attributes,callouts,macros"]
+----
+include-tagged::{sql-specs}/docs.csv-spec[nullIfReturnNull]
+----

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

@@ -23,6 +23,7 @@ COALESCE        |CONDITIONAL
 IFNULL          |CONDITIONAL
 ISNULL          |CONDITIONAL
 NVL             |CONDITIONAL
+NULLIF          |CONDITIONAL
 DAY             |SCALAR
 DAYNAME         |SCALAR
 DAYOFMONTH      |SCALAR         

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

@@ -200,6 +200,7 @@ COALESCE        |CONDITIONAL
 IFNULL          |CONDITIONAL
 ISNULL          |CONDITIONAL
 NVL             |CONDITIONAL
+NULLIF          |CONDITIONAL
 DAY             |SCALAR
 DAYNAME         |SCALAR
 DAYOFMONTH      |SCALAR         
@@ -1597,3 +1598,24 @@ SELECT NVL(null, 'search') AS "nvl";
 search
 // end::nvlReturnSecond
 ;
+
+
+nullIfReturnFirst
+// tag::nullIfReturnFirst
+SELECT NULLIF('elastic', 'search') AS "nullif";
+    nullif
+---------------
+elastic
+// end::nullIfReturnFirst
+;
+
+
+nullIfReturnNull
+// tag::nullIfReturnNull
+SELECT NULLIF('elastic', 'elastic') AS "nullif";
+
+    nullif:s
+---------------
+null
+// end::nullIfReturnNull
+;

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

@@ -12,4 +12,13 @@ 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;
+SELECT IFNULL(null, ABS(emp_no) + 1) AS "ifnull" FROM test_emp ORDER BY emp_no LIMIT 5;
+
+nullIfField
+SELECT NULLIF(emp_no - 2 + 3, ABS(emp_no) + 1) AS "nullif1", NULLIF(emp_no + 1, emp_no - 1) as "nullif2" FROM test_emp ORDER BY emp_no LIMIT 5;
+
+nullIfWhere
+SELECT NULLIF(10002, ABS(emp_no) + 1) AS c, emp_no FROM test_emp WHERE NULLIF(10003, ABS(emp_no) + 1) IS NOT NULL ORDER BY emp_no NULLS FIRST LIMIT 5;
+
+nullIfHaving
+SELECT NULLIF(10030, ABS(MAX(emp_no)) + 1) AS nif FROM test_emp GROUP BY languages HAVING nif IS NOT NULL ORDER BY languages;

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

@@ -83,7 +83,8 @@ 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.conditional.IfNull;
+import org.elasticsearch.xpack.sql.expression.predicate.conditional.NullIf;
 import org.elasticsearch.xpack.sql.expression.predicate.operator.arithmetic.Mod;
 import org.elasticsearch.xpack.sql.parser.ParsingException;
 import org.elasticsearch.xpack.sql.tree.Location;
@@ -93,11 +94,13 @@ import org.elasticsearch.xpack.sql.util.StringUtils;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.Set;
 import java.util.TimeZone;
 import java.util.function.BiFunction;
 import java.util.regex.Pattern;
@@ -108,6 +111,15 @@ import static java.util.Collections.unmodifiableList;
 import static java.util.stream.Collectors.toList;
 
 public class FunctionRegistry {
+
+    private static final Set<String> EXCLUDE_FROM_NAME_NORMALIZATION = new HashSet<>();
+
+    static {
+        EXCLUDE_FROM_NAME_NORMALIZATION.add(IfNull.class.getSimpleName());
+        EXCLUDE_FROM_NAME_NORMALIZATION.add(NullIf.class.getSimpleName());
+    }
+
+
     // list of functions grouped by type of functions (aggregate, statistics, math etc) and ordered alphabetically inside each group
     // a single function will have one entry for itself with its name associated to its instance and, also, one entry for each alias
     // it has with the alias name associated to the FunctionDefinition instance
@@ -146,7 +158,8 @@ public class FunctionRegistry {
         // Scalar functions
         // conditional
         addToMap(def(Coalesce.class, Coalesce::new));
-        addToMap(def(IFNull.class, IFNull::new, "ISNULL", "NVL"));
+        addToMap(def(IfNull.class, IfNull::new, "ISNULL", "NVL"));
+        addToMap(def(NullIf.class, NullIf::new));
         // Date
         addToMap(def(DayName.class, DayName::new, "DAYNAME"),
                 def(DayOfMonth.class, DayOfMonth::new, "DAYOFMONTH", "DAY", "DOM"),
@@ -485,7 +498,11 @@ public class FunctionRegistry {
     }
 
     private static String normalize(String name) {
-        // translate CamelCase to camel_case
+        if (EXCLUDE_FROM_NAME_NORMALIZATION.contains(name)) {
+            return name.toUpperCase(Locale.ROOT);
+        }
+
+        // translate CamelCase to CAMEL_CASE
         return StringUtils.camelCaseToUnderscore(name);
     }
 }

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

@@ -26,6 +26,7 @@ import org.elasticsearch.xpack.sql.expression.gen.processor.ConstantProcessor;
 import org.elasticsearch.xpack.sql.expression.gen.processor.HitExtractorProcessor;
 import org.elasticsearch.xpack.sql.expression.gen.processor.Processor;
 import org.elasticsearch.xpack.sql.expression.predicate.conditional.CoalesceProcessor;
+import org.elasticsearch.xpack.sql.expression.predicate.conditional.NullIfProcessor;
 import org.elasticsearch.xpack.sql.expression.predicate.logical.BinaryLogicProcessor;
 import org.elasticsearch.xpack.sql.expression.predicate.logical.NotProcessor;
 import org.elasticsearch.xpack.sql.expression.predicate.nulls.CheckNullProcessor;
@@ -61,6 +62,7 @@ public final class Processors {
         // null
         entries.add(new Entry(Processor.class, CheckNullProcessor.NAME, CheckNullProcessor::new));
         entries.add(new Entry(Processor.class, CoalesceProcessor.NAME, CoalesceProcessor::new));
+        entries.add(new Entry(Processor.class, NullIfProcessor.NAME, NullIfProcessor::new));
 
         // arithmetic
         entries.add(new Entry(Processor.class, BinaryArithmeticProcessor.NAME, BinaryArithmeticProcessor::new));

+ 5 - 0
x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/whitelist/InternalSqlScriptUtils.java

@@ -24,6 +24,7 @@ import org.elasticsearch.xpack.sql.expression.function.scalar.string.SubstringFu
 import org.elasticsearch.xpack.sql.expression.literal.IntervalDayTime;
 import org.elasticsearch.xpack.sql.expression.literal.IntervalYearMonth;
 import org.elasticsearch.xpack.sql.expression.predicate.conditional.CoalesceProcessor;
+import org.elasticsearch.xpack.sql.expression.predicate.conditional.NullIfProcessor;
 import org.elasticsearch.xpack.sql.expression.predicate.logical.BinaryLogicProcessor.BinaryLogicOperation;
 import org.elasticsearch.xpack.sql.expression.predicate.logical.NotProcessor;
 import org.elasticsearch.xpack.sql.expression.predicate.nulls.CheckNullProcessor.CheckNullOperation;
@@ -142,6 +143,10 @@ public final class InternalSqlScriptUtils {
         return CoalesceProcessor.apply(expressions);
     }
 
+    public static Object nullif(Object left, Object right) {
+        return NullIfProcessor.apply(left, right);
+    }
+
     //
     // Regex
     //

+ 5 - 7
x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/IFNull.java → x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/IfNull.java

@@ -15,22 +15,20 @@ 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 class IfNull extends Coalesce {
 
-    public IFNull(Location location, Expression first, Expression second) {
+    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));
+        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));
+    protected NodeInfo<IfNull> info() {
+        return NodeInfo.create(this, IfNull::new, children().get(0), children().get(1));
     }
 }

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

@@ -0,0 +1,88 @@
+/*
+ * 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.expression.Expressions;
+import org.elasticsearch.xpack.sql.expression.gen.pipeline.Pipe;
+import org.elasticsearch.xpack.sql.expression.gen.script.ParamsBuilder;
+import org.elasticsearch.xpack.sql.expression.gen.script.ScriptTemplate;
+import org.elasticsearch.xpack.sql.tree.Location;
+import org.elasticsearch.xpack.sql.tree.NodeInfo;
+import org.elasticsearch.xpack.sql.type.DataType;
+
+import java.util.Arrays;
+import java.util.List;
+
+import static org.elasticsearch.xpack.sql.expression.gen.script.ParamsBuilder.paramsBuilder;
+
+/**
+ * Accepts 2 arguments of any data type and returns null if they are equal,
+ * and the 1st argument otherwise.
+ */
+public class NullIf extends ConditionalFunction {
+
+    private DataType dataType;
+
+    public NullIf(Location location, Expression left, Expression right) {
+        super(location, Arrays.asList(left, right));
+    }
+
+    @Override
+    protected NodeInfo<? extends NullIf> info() {
+        return NodeInfo.create(this, NullIf::new, children().get(0), children().get(1));
+    }
+
+    @Override
+    public Expression replaceChildren(List<Expression> newChildren) {
+        return new NullIf(location(), newChildren.get(0), newChildren.get(1));
+    }
+
+    @Override
+    protected TypeResolution resolveType() {
+        dataType = children().get(0).dataType();
+        return TypeResolution.TYPE_RESOLVED;
+    }
+
+    @Override
+    public DataType dataType() {
+        return dataType;
+    }
+
+    @Override
+    public boolean foldable() {
+        return Expressions.foldable(children());
+    }
+
+    @Override
+    public boolean nullable() {
+        return true;
+    }
+
+    @Override
+    public Object fold() {
+        return NullIfProcessor.apply(children().get(0).fold(), children().get(1).fold());
+    }
+
+    @Override
+    public ScriptTemplate asScript() {
+        ScriptTemplate left = asScript(children().get(0));
+        ScriptTemplate right = asScript(children().get(1));
+        String template = "{sql}.nullif(" + left.template() + "," + right.template() + ")";
+        ParamsBuilder params = paramsBuilder();
+        params.script(left.params());
+        params.script(right.params());
+
+        return new ScriptTemplate(template, params.build(), dataType);
+    }
+
+    @Override
+    protected Pipe makePipe() {
+        return new NullIfPipe(location(), this,
+            Expressions.pipe(children().get(0)), Expressions.pipe(children().get(1)));
+    }
+}

+ 36 - 0
x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/NullIfPipe.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.expression.gen.pipeline.BinaryPipe;
+import org.elasticsearch.xpack.sql.expression.gen.pipeline.Pipe;
+import org.elasticsearch.xpack.sql.expression.gen.processor.Processor;
+import org.elasticsearch.xpack.sql.tree.Location;
+import org.elasticsearch.xpack.sql.tree.NodeInfo;
+
+public class NullIfPipe extends BinaryPipe {
+
+    public NullIfPipe(Location location, Expression expression, Pipe left, Pipe right) {
+        super(location, expression, left, right);
+    }
+
+    @Override
+    protected BinaryPipe replaceChildren(Pipe left, Pipe right) {
+        return new NullIfPipe(location(), expression(), left, right);
+    }
+
+    @Override
+    protected NodeInfo<NullIfPipe> info() {
+        return NodeInfo.create(this, NullIfPipe::new, expression(), children().get(0), children().get(1));
+    }
+
+    @Override
+    public Processor asProcessor() {
+        return new NullIfProcessor(left().asProcessor(), right().asProcessor());
+    }
+}

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

@@ -0,0 +1,73 @@
+/*
+ * 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.common.io.stream.StreamInput;
+import org.elasticsearch.common.io.stream.StreamOutput;
+import org.elasticsearch.xpack.sql.expression.gen.processor.Processor;
+
+import java.io.IOException;
+import java.util.Objects;
+
+import static org.elasticsearch.xpack.sql.expression.predicate.operator.comparison.BinaryComparisonProcessor.BinaryComparisonOperation.EQ;
+
+public class NullIfProcessor implements Processor {
+
+    public static final String NAME = "nni";
+
+    private final Processor leftProcessor;
+    private final Processor rightProcessor;
+
+
+    public NullIfProcessor(Processor leftProcessor, Processor rightProcessor) {
+        this.leftProcessor = leftProcessor;
+        this.rightProcessor = rightProcessor;
+    }
+
+    public NullIfProcessor(StreamInput in) throws IOException {
+        leftProcessor = in.readNamedWriteable(Processor.class);
+        rightProcessor = in.readNamedWriteable(Processor.class);
+    }
+
+    @Override
+    public String getWriteableName() {
+        return NAME;
+    }
+
+    @Override
+    public void writeTo(StreamOutput out) throws IOException {
+        out.writeNamedWriteable(leftProcessor);
+        out.writeNamedWriteable(rightProcessor);
+    }
+
+    @Override
+    public Object process(Object input) {
+        Object leftValue = leftProcessor.process(input);
+        Object rightValue = rightProcessor.process(input);
+        return apply(leftValue, rightValue);
+    }
+
+    public static Object apply(Object leftValue, Object rightValue) {
+        if (EQ.apply(leftValue, rightValue) == Boolean.TRUE) {
+            return null;
+        }
+        return leftValue;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        NullIfProcessor that = (NullIfProcessor) o;
+        return Objects.equals(leftProcessor, that.leftProcessor) &&
+            Objects.equals(rightProcessor, that.rightProcessor);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(leftProcessor, rightProcessor);
+    }
+}

+ 5 - 0
x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/optimizer/Optimizer.java

@@ -42,6 +42,7 @@ import org.elasticsearch.xpack.sql.expression.predicate.Negatable;
 import org.elasticsearch.xpack.sql.expression.predicate.Predicates;
 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.NullIf;
 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;
@@ -1172,6 +1173,9 @@ public class Optimizer extends RuleExecutor<LogicalPlan> {
                     return Literal.of(in, null);
                 }
 
+            } else if (e instanceof NullIf) {
+                return e;
+
             } else if (e.nullable() && Expressions.anyMatch(e.children(), Expressions::isNull)) {
                 return Literal.of(e, null);
             }
@@ -1229,6 +1233,7 @@ public class Optimizer extends RuleExecutor<LogicalPlan> {
         }
     }
 
+
     static class BooleanSimplification extends OptimizerExpressionRule {
 
         BooleanSimplification() {

+ 1 - 0
x-pack/plugin/sql/src/main/resources/org/elasticsearch/xpack/sql/plugin/sql_whitelist.txt

@@ -46,6 +46,7 @@ class org.elasticsearch.xpack.sql.expression.function.scalar.whitelist.InternalS
 # Null
 #
   Object coalesce(java.util.List)
+  Object nullif(Object, Object)
 
 #
 # Regex

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

@@ -37,7 +37,8 @@ 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.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.conditional.IfNull;
+import org.elasticsearch.xpack.sql.expression.predicate.conditional.NullIf;
 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;
@@ -450,21 +451,27 @@ public class OptimizerTests extends ESTestCase {
     }
 
     public void testSimplifyIfNullNulls() {
-        Expression e = new SimplifyCoalesce().rule(new IFNull(EMPTY, Literal.NULL, Literal.NULL));
+        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));
+        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));
+        e = new SimplifyCoalesce().rule(new IfNull(EMPTY, ONE, Literal.NULL));
         assertEquals(1, e.children().size());
         assertEquals(ONE, e.children().get(0));
     }
 
+    public void testFoldNullNotAppliedOnNullIf() {
+        Expression orig = new NullIf(EMPTY, ONE, Literal.NULL);
+        Expression f = new FoldNull().rule(orig);
+        assertEquals(orig, f);
+    }
+
     //
     // Logical simplifications
     //

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

@@ -25,7 +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.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;
@@ -88,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(
-        IFNull.class, 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;