Browse Source

SQL: Implement GREATEST and LEAST functions (#35879)

Add GREATEST(expr1, expr2, ... exprN) and LEAST(expr1, expr2, exprN)
functions which are in the family of CONDITIONAL functions.

Implementation follows PostgreSQL behaviour, so the functions return
`NULL` when all of their arguments evaluate to `NULL`.

Renamed `CoalescePipe` and `CoalesceProcessor` to `ConditionalPipe` and
`ConditionalProcessor` respectively, to be able to reuse them for
`Greatest` and `Least` evaluations. To achieve that `ConditionalOperation`
has been added to differentiate between the functionalities at execution
time.

Closes: #35878
Marios Trivyzas 6 years ago
parent
commit
3f7cae3f0d
26 changed files with 681 additions and 222 deletions
  1. 84 0
      docs/reference/sql/functions/conditional.asciidoc
  2. 8 4
      x-pack/plugin/sql/qa/src/main/java/org/elasticsearch/xpack/sql/qa/cli/ShowTestCase.java
  3. 3 1
      x-pack/plugin/sql/qa/src/main/resources/command.csv-spec
  4. 45 1
      x-pack/plugin/sql/qa/src/main/resources/docs.csv-spec
  5. 20 2
      x-pack/plugin/sql/qa/src/main/resources/null.sql-spec
  6. 14 4
      x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/Foldables.java
  7. 7 3
      x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/FunctionRegistry.java
  8. 2 2
      x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/Processors.java
  9. 10 2
      x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/whitelist/InternalSqlScriptUtils.java
  10. 66 0
      x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/ArbitraryConditionalFunction.java
  11. 3 54
      x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/Coalesce.java
  12. 0 38
      x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/CoalescePipe.java
  13. 0 81
      x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/CoalesceProcessor.java
  14. 20 1
      x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/ConditionalFunction.java
  15. 57 0
      x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/ConditionalPipe.java
  16. 101 0
      x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/ConditionalProcessor.java
  17. 84 0
      x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/Conditionals.java
  18. 40 0
      x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/Greatest.java
  19. 6 2
      x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/IfNull.java
  20. 40 0
      x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/Least.java
  21. 0 2
      x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/NullIf.java
  22. 2 2
      x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/operator/comparison/Comparisons.java
  23. 12 10
      x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/optimizer/Optimizer.java
  24. 0 4
      x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/planner/QueryFolder.java
  25. 2 0
      x-pack/plugin/sql/src/main/resources/org/elasticsearch/xpack/sql/plugin/sql_whitelist.txt
  26. 55 9
      x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/optimizer/OptimizerTests.java

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

@@ -190,3 +190,87 @@ include-tagged::{sql-specs}/docs.csv-spec[nullIfReturnFirst]
 ----
 include-tagged::{sql-specs}/docs.csv-spec[nullIfReturnNull]
 ----
+
+
+[[sql-functions-conditional-greatest]]
+==== `GREATEST`
+
+.Synopsis
+[source, sql]
+----
+GREATEST ( expression<1>, expression<2>, ... )
+----
+
+*Input*:
+
+<1> 1st expression
+
+<2> 2nd expression
+
+...
+
+**N**th expression
+
+GREATEST can take an arbitrary number of arguments and
+all of them must be of the same data type.
+
+*Output*: one of the expressions or `null`
+
+.Description
+
+Returns the argument that has the largest value which 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[greatestReturnNonNull]
+----
+
+["source","sql",subs="attributes,callouts,macros"]
+----
+include-tagged::{sql-specs}/docs.csv-spec[greatestReturnNull]
+----
+
+
+[[sql-functions-conditional-least]]
+==== `LEAST`
+
+.Synopsis
+[source, sql]
+----
+LEAST ( expression<1>, expression<2>, ... )
+----
+
+*Input*:
+
+<1> 1st expression
+
+<2> 2nd expression
+
+...
+
+**N**th expression
+
+LEAST can take an arbitrary number of arguments and
+all of them must be of the same data type.
+
+*Output*: one of the expressions or `null`
+
+.Description
+
+Returns the argument that has the smallest value which 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[leastReturnNonNull]
+----
+
+["source","sql",subs="attributes,callouts,macros"]
+----
+include-tagged::{sql-specs}/docs.csv-spec[leastReturnNull]
+----

+ 8 - 4
x-pack/plugin/sql/qa/src/main/java/org/elasticsearch/xpack/sql/qa/cli/ShowTestCase.java

@@ -13,11 +13,14 @@ import java.util.regex.Pattern;
 import static org.hamcrest.Matchers.containsString;
 
 public abstract class ShowTestCase extends CliIntegrationTestCase {
+
+    private static final String HEADER_SEPARATOR = "----------";
+
     public void testShowTables() throws IOException {
         index("test1", body -> body.field("test_field", "test_value"));
         index("test2", body -> body.field("test_field", "test_value"));
         assertThat(command("SHOW TABLES"), RegexMatcher.matches("\\s*name\\s*"));
-        assertThat(readLine(), containsString("----------"));
+        assertThat(readLine(), containsString(HEADER_SEPARATOR));
         assertThat(readLine(), RegexMatcher.matches("\\s*test[12]\\s*"));
         assertThat(readLine(), RegexMatcher.matches("\\s*test[12]\\s*"));
         assertEquals("", readLine());
@@ -25,7 +28,7 @@ public abstract class ShowTestCase extends CliIntegrationTestCase {
 
     public void testShowFunctions() throws IOException {
         assertThat(command("SHOW FUNCTIONS"), RegexMatcher.matches("\\s*name\\s*\\|\\s*type\\s*"));
-        assertThat(readLine(), containsString("----------"));
+        assertThat(readLine(), containsString(HEADER_SEPARATOR));
         assertThat(readLine(), RegexMatcher.matches("\\s*AVG\\s*\\|\\s*AGGREGATE\\s*"));
         assertThat(readLine(), RegexMatcher.matches("\\s*COUNT\\s*\\|\\s*AGGREGATE\\s*"));
         assertThat(readLine(), RegexMatcher.matches("\\s*MAX\\s*\\|\\s*AGGREGATE\\s*"));
@@ -50,7 +53,8 @@ public abstract class ShowTestCase extends CliIntegrationTestCase {
 
     public void testShowFunctionsLikePrefix() throws IOException {
         assertThat(command("SHOW FUNCTIONS LIKE 'L%'"), RegexMatcher.matches("\\s*name\\s*\\|\\s*type\\s*"));
-        assertThat(readLine(), containsString("----------"));
+        assertThat(readLine(), containsString(HEADER_SEPARATOR));
+        assertThat(readLine(), RegexMatcher.matches("\\s*LEAST\\s*\\|\\s*CONDITIONAL\\s*"));
         assertThat(readLine(), RegexMatcher.matches("\\s*LOG\\s*\\|\\s*SCALAR\\s*"));
         assertThat(readLine(), RegexMatcher.matches("\\s*LOG10\\s*\\|\\s*SCALAR\\s*"));
         assertThat(readLine(), RegexMatcher.matches("\\s*LCASE\\s*\\|\\s*SCALAR\\s*"));
@@ -63,7 +67,7 @@ public abstract class ShowTestCase extends CliIntegrationTestCase {
 
     public void testShowFunctionsLikeInfix() throws IOException {
         assertThat(command("SHOW FUNCTIONS LIKE '%DAY%'"), RegexMatcher.matches("\\s*name\\s*\\|\\s*type\\s*"));
-        assertThat(readLine(), containsString("----------"));
+        assertThat(readLine(), containsString(HEADER_SEPARATOR));
         assertThat(readLine(), RegexMatcher.matches("\\s*DAY\\s*\\|\\s*SCALAR\\s*"));
         assertThat(readLine(), RegexMatcher.matches("\\s*DAYNAME\\s*\\|\\s*SCALAR\\s*"));
         assertThat(readLine(), RegexMatcher.matches("\\s*DAYOFMONTH\\s*\\|\\s*SCALAR\\s*"));

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

@@ -20,10 +20,12 @@ STDDEV_POP      |AGGREGATE
 SUM_OF_SQUARES  |AGGREGATE      
 VAR_POP         |AGGREGATE      
 COALESCE        |CONDITIONAL
+GREATEST        |CONDITIONAL
 IFNULL          |CONDITIONAL
 ISNULL          |CONDITIONAL
-NVL             |CONDITIONAL
+LEAST           |CONDITIONAL
 NULLIF          |CONDITIONAL
+NVL             |CONDITIONAL
 DAY             |SCALAR
 DAYNAME         |SCALAR
 DAYOFMONTH      |SCALAR         

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

@@ -197,10 +197,12 @@ STDDEV_POP      |AGGREGATE
 SUM_OF_SQUARES  |AGGREGATE      
 VAR_POP         |AGGREGATE      
 COALESCE        |CONDITIONAL
+GREATEST        |CONDITIONAL
 IFNULL          |CONDITIONAL
 ISNULL          |CONDITIONAL
-NVL             |CONDITIONAL
+LEAST           |CONDITIONAL
 NULLIF          |CONDITIONAL
+NVL             |CONDITIONAL
 DAY             |SCALAR
 DAYNAME         |SCALAR
 DAYOFMONTH      |SCALAR         
@@ -1620,6 +1622,48 @@ null
 // end::nullIfReturnNull
 ;
 
+greatestReturnNonNull
+// tag::greatestReturnNonNull
+SELECT GREATEST(null, 1, 2) AS "greatest";
+
+    greatest
+---------------
+2
+// end::greatestReturnNonNull
+;
+
+
+greatestReturnNull
+// tag::greatestReturnNull
+SELECT GREATEST(null, null, null, null) AS "greatest";
+
+    greatest
+---------------
+null
+// end::greatestReturnNull
+;
+
+leastReturnNonNull
+// tag::leastReturnNonNull
+SELECT LEAST(null, 2, 1) AS "least";
+
+    least
+---------------
+1
+// end::leastReturnNonNull
+;
+
+
+leastReturnNull
+// tag::leastReturnNull
+SELECT LEAST(null, null, null, null) AS "least";
+
+    least
+---------------
+null
+// end::leastReturnNull
+;
+
 nullEqualsCompareWithNull
 // tag::nullEqualsCompareWithNull
 SELECT 'elastic' <=> null AS "equals";

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

@@ -15,10 +15,28 @@ ifNullField
 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;
+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;
+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 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;
+
+greatestField
+SELECT GREATEST(emp_no - 1 + 3, ABS(emp_no) + 1) AS "greatest" FROM test_emp ORDER BY emp_no LIMIT 5;
+
+greatestWhere
+SELECT emp_no FROM test_emp WHERE GREATEST(10005, ABS(emp_no) + 1, null, emp_no - 1 + 3) > 10008 ORDER BY emp_no LIMIT 5;
+
+greatestHaving
+SELECT GREATEST(10096, ABS(MAX(emp_no)) + 1) AS gt FROM test_emp GROUP BY languages HAVING gt >= 10098 ORDER BY languages;
+
+leastField
+SELECT LEAST(emp_no - 1 + 3, ABS(emp_no) + 1) AS "least" FROM test_emp ORDER BY emp_no LIMIT 5;
+
+leastWhere
+SELECT emp_no FROM test_emp WHERE LEAST(10005, ABS(emp_no) + 1, null, emp_no - 1 + 3) > 10004 ORDER BY emp_no LIMIT 5;
+
+leastHaving
+SELECT LEAST(10098, ABS(MAX(emp_no)) + 1) AS lt FROM test_emp GROUP BY languages HAVING lt >= 10095 ORDER BY languages;

+ 14 - 4
x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/Foldables.java

@@ -10,7 +10,10 @@ import org.elasticsearch.xpack.sql.type.DataType;
 import org.elasticsearch.xpack.sql.type.DataTypeConversion;
 
 import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedHashSet;
 import java.util.List;
+import java.util.Set;
 
 public abstract class Foldables {
 
@@ -46,11 +49,18 @@ public abstract class Foldables {
     }
 
     public static <T> List<T> valuesOf(List<Expression> list, DataType to) {
-        List<T> l = new ArrayList<>(list.size());
-        for (Expression e : list) {
-             l.add(valueOf(e, to));
+        return foldTo(list, to, new ArrayList<>(list.size()));
+    }
+
+    public static <T> Set<T> valuesOfNoDuplicates(List<Expression> list, DataType to) {
+        return foldTo(list, to, new LinkedHashSet<>(list.size()));
+    }
+
+    private static <T, C extends Collection<T>> C foldTo(Collection<Expression> expressions, DataType to, C values) {
+        for (Expression e : expressions) {
+            values.add(valueOf(e, to));
         }
-        return l;
+        return values;
     }
 
     public static List<Double> doubleValuesOf(List<Expression> list) {

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

@@ -83,7 +83,9 @@ 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.Greatest;
 import org.elasticsearch.xpack.sql.expression.predicate.conditional.IfNull;
+import org.elasticsearch.xpack.sql.expression.predicate.conditional.Least;
 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;
@@ -157,9 +159,11 @@ public class FunctionRegistry {
                 def(Kurtosis.class, Kurtosis::new));
         // Scalar functions
         // conditional
-        addToMap(def(Coalesce.class, Coalesce::new));
-        addToMap(def(IfNull.class, IfNull::new, "ISNULL", "NVL"));
-        addToMap(def(NullIf.class, NullIf::new));
+        addToMap(def(Coalesce.class, Coalesce::new),
+                 def(IfNull.class, IfNull::new, "ISNULL", "NVL"),
+                 def(NullIf.class, NullIf::new),
+                 def(Greatest.class, Greatest::new),
+                 def(Least.class, Least::new));
         // Date
         addToMap(def(DayName.class, DayName::new, "DAYNAME"),
                 def(DayOfMonth.class, DayOfMonth::new, "DAYOFMONTH", "DAY", "DOM"),

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

@@ -25,7 +25,7 @@ import org.elasticsearch.xpack.sql.expression.gen.processor.ChainingProcessor;
 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.ConditionalProcessor;
 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;
@@ -61,7 +61,7 @@ public final class Processors {
         entries.add(new Entry(Processor.class, NotProcessor.NAME, NotProcessor::new));
         // 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, ConditionalProcessor.NAME, ConditionalProcessor::new));
         entries.add(new Entry(Processor.class, NullIfProcessor.NAME, NullIfProcessor::new));
 
         // arithmetic

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

@@ -23,7 +23,7 @@ import org.elasticsearch.xpack.sql.expression.function.scalar.string.StringProce
 import org.elasticsearch.xpack.sql.expression.function.scalar.string.SubstringFunctionProcessor;
 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.ConditionalProcessor.ConditionalOperation;
 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;
@@ -144,7 +144,15 @@ public final class InternalSqlScriptUtils {
     // Null
     //
     public static Object coalesce(List<Object> expressions) {
-        return CoalesceProcessor.apply(expressions);
+        return ConditionalOperation.COALESCE.apply(expressions);
+    }
+
+    public static Object greatest(List<Object> expressions) {
+        return ConditionalOperation.GREATEST.apply(expressions);
+    }
+
+    public static Object least(List<Object> expressions) {
+        return ConditionalOperation.LEAST.apply(expressions);
     }
 
     public static Object nullif(Object left, Object right) {

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

@@ -0,0 +1,66 @@
+/*
+ * 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.expression.predicate.conditional.ConditionalProcessor.ConditionalOperation;
+import org.elasticsearch.xpack.sql.tree.Location;
+import org.elasticsearch.xpack.sql.type.DataTypeConversion;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringJoiner;
+
+import static org.elasticsearch.xpack.sql.expression.gen.script.ParamsBuilder.paramsBuilder;
+
+/**
+ * Base class for conditional predicates with arbitrary number of arguments
+ */
+public abstract class ArbitraryConditionalFunction extends ConditionalFunction {
+
+    private final ConditionalOperation operation;
+
+    ArbitraryConditionalFunction(Location location, List<Expression> fields, ConditionalOperation operation) {
+        super(location, fields);
+        this.operation = operation;
+    }
+
+    @Override
+    protected TypeResolution resolveType() {
+        for (Expression e : children()) {
+            dataType = DataTypeConversion.commonType(dataType, e.dataType());
+        }
+        return TypeResolution.TYPE_RESOLVED;
+    }
+
+    @Override
+    protected Pipe makePipe() {
+        return new ConditionalPipe(location(), this, Expressions.pipe(children()), operation);
+    }
+
+    @Override
+    public ScriptTemplate asScript() {
+        List<ScriptTemplate> templates = new ArrayList<>();
+        for (Expression ex : children()) {
+            templates.add(asScript(ex));
+        }
+
+        StringJoiner template = new StringJoiner(",", "{sql}." + operation.scriptMethodName() +"([", "])");
+        ParamsBuilder params = paramsBuilder();
+
+        for (ScriptTemplate scriptTemplate : templates) {
+            template.add(scriptTemplate.template());
+            params.script(scriptTemplate.params());
+        }
+
+        return new ScriptTemplate(template.toString(), params.build(), dataType());
+    }
+}

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

@@ -7,27 +7,17 @@
 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 org.elasticsearch.xpack.sql.type.DataTypeConversion;
 
-import java.util.ArrayList;
 import java.util.List;
-import java.util.StringJoiner;
 
-import static org.elasticsearch.xpack.sql.expression.gen.script.ParamsBuilder.paramsBuilder;
+import static org.elasticsearch.xpack.sql.expression.predicate.conditional.ConditionalProcessor.ConditionalOperation.COALESCE;
 
-public class Coalesce extends ConditionalFunction {
-
-    private DataType dataType = DataType.NULL;
+public class Coalesce extends ArbitraryConditionalFunction {
 
     public Coalesce(Location location, List<Expression> fields) {
-        super(location, fields);
+        super(location, fields, COALESCE);
     }
 
     @Override
@@ -40,19 +30,6 @@ public class Coalesce extends ConditionalFunction {
         return new Coalesce(location(), newChildren);
     }
 
-    @Override
-    protected TypeResolution resolveType() {
-        for (Expression e : children()) {
-            dataType = DataTypeConversion.commonType(dataType, e.dataType());
-        }
-        return TypeResolution.TYPE_RESOLVED;
-    }
-
-    @Override
-    public DataType dataType() {
-        return dataType;
-    }
-
     @Override
     public boolean foldable() {
         // if the first entry is foldable, so is coalesce
@@ -62,37 +39,9 @@ public class Coalesce extends ConditionalFunction {
         return (children.isEmpty() || (children.get(0).foldable() && children.get(0).fold() != null));
     }
 
-    @Override
-    public boolean nullable() {
-        return false;
-    }
-
     @Override
     public Object fold() {
         List<Expression> children = children();
         return children.isEmpty() ? null : children.get(0).fold();
     }
-
-    @Override
-    public ScriptTemplate asScript() {
-        List<ScriptTemplate> templates = new ArrayList<>();
-        for (Expression ex : children()) {
-            templates.add(asScript(ex));
-        }
-
-        StringJoiner template = new StringJoiner(",", "{sql}.coalesce([", "])");
-        ParamsBuilder params = paramsBuilder();
-
-        for (ScriptTemplate scriptTemplate : templates) {
-            template.add(scriptTemplate.template());
-            params.script(scriptTemplate.params());
-        }
-
-        return new ScriptTemplate(template.toString(), params.build(), dataType);
-    }
-
-    @Override
-    protected Pipe makePipe() {
-        return new CoalescePipe(location(), this, Expressions.pipe(children()));
-    }
 }

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

@@ -1,38 +0,0 @@
-/*
- * 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.MultiPipe;
-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;
-
-import java.util.List;
-
-public class CoalescePipe extends MultiPipe {
-
-    public CoalescePipe(Location location, Expression expression, List<Pipe> children) {
-        super(location, expression, children);
-    }
-
-    @Override
-    protected NodeInfo<CoalescePipe> info() {
-        return NodeInfo.create(this, CoalescePipe::new, expression(), children());
-    }
-
-    @Override
-    public Pipe replaceChildren(List<Pipe> newChildren) {
-        return new CoalescePipe(location(), expression(), newChildren);
-    }
-
-    @Override
-    public Processor asProcessor(List<Processor> procs) {
-        return new CoalesceProcessor(procs);
-    }
-}

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

@@ -1,81 +0,0 @@
-/*
- * 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.List;
-import java.util.Objects;
-
-public class CoalesceProcessor implements Processor {
-    
-    public static final String NAME = "nco";
-
-    private final List<Processor> processsors;
-
-    public CoalesceProcessor(List<Processor> processors) {
-        this.processsors = processors;
-    }
-
-    public CoalesceProcessor(StreamInput in) throws IOException {
-        processsors = in.readNamedWriteableList(Processor.class);
-    }
-
-    @Override
-    public String getWriteableName() {
-        return NAME;
-    }
-
-    @Override
-    public void writeTo(StreamOutput out) throws IOException {
-        out.writeNamedWriteableList(processsors);
-    }
-
-    @Override
-    public Object process(Object input) {
-        for (Processor proc : processsors) {
-            Object result = proc.process(input);
-            if (result != null) {
-                return result;
-            }
-        }
-        return null;
-    }
-
-    public static Object apply(List<?> values) {
-        if (values == null || values.isEmpty()) {
-            return null;
-        }
-
-        for (Object object : values) {
-            if (object != null) {
-                return object;
-            }
-        }
-
-        return null;
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(processsors);
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (o == null || getClass() != o.getClass()) {
-            return false;
-        }
-        CoalesceProcessor that = (CoalesceProcessor) o;
-        return Objects.equals(processsors, that.processsors);
-    }
-}

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

@@ -7,8 +7,10 @@
 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.function.scalar.ScalarFunction;
 import org.elasticsearch.xpack.sql.tree.Location;
+import org.elasticsearch.xpack.sql.type.DataType;
 
 import java.util.List;
 
@@ -17,7 +19,24 @@ import java.util.List;
  */
 public abstract class ConditionalFunction extends ScalarFunction {
 
-    protected ConditionalFunction(Location location, List<Expression> fields) {
+    protected DataType dataType = DataType.NULL;
+
+    ConditionalFunction(Location location, List<Expression> fields) {
         super(location, fields);
     }
+
+    @Override
+    public DataType dataType() {
+        return dataType;
+    }
+
+    @Override
+    public boolean foldable() {
+        return Expressions.foldable(children());
+    }
+
+    @Override
+    public boolean nullable() {
+        return false;
+    }
 }

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

@@ -0,0 +1,57 @@
+/*
+ * 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.MultiPipe;
+import org.elasticsearch.xpack.sql.expression.gen.pipeline.Pipe;
+import org.elasticsearch.xpack.sql.expression.gen.processor.Processor;
+import org.elasticsearch.xpack.sql.expression.predicate.conditional.ConditionalProcessor.ConditionalOperation;
+import org.elasticsearch.xpack.sql.tree.Location;
+import org.elasticsearch.xpack.sql.tree.NodeInfo;
+
+import java.util.List;
+import java.util.Objects;
+
+public class ConditionalPipe extends MultiPipe {
+
+    private final ConditionalOperation operation;
+
+    public ConditionalPipe(Location location, Expression expression, List<Pipe> children, ConditionalOperation operation) {
+        super(location, expression, children);
+        this.operation = operation;
+    }
+
+    @Override
+    protected NodeInfo<ConditionalPipe> info() {
+        return NodeInfo.create(this, ConditionalPipe::new, expression(), children(), operation);
+    }
+
+    @Override
+    public Pipe replaceChildren(List<Pipe> newChildren) {
+        return new ConditionalPipe(location(), expression(), newChildren, operation);
+    }
+
+    @Override
+    public Processor asProcessor(List<Processor> procs) {
+        return new ConditionalProcessor(procs, operation);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(super.hashCode(), operation);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (super.equals(obj)) {
+            ConditionalPipe other = (ConditionalPipe) obj;
+            return Objects.equals(operation, other.operation);
+        }
+        return false;
+    }
+}

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

@@ -0,0 +1,101 @@
+/*
+ * 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.Collection;
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+public class ConditionalProcessor implements Processor {
+
+    public enum ConditionalOperation implements Function<Collection<Object>, Object> {
+
+        COALESCE(Conditionals::coalesce, Conditionals::coalesceInput),
+        GREATEST(Conditionals::greatest, Conditionals::greatestInput),
+        LEAST(Conditionals::least, Conditionals::leastInput);
+
+
+        String scriptMethodName() {
+            return name().toLowerCase(Locale.ROOT);
+        }
+
+        private final Function<Collection<Object>, Object> process;
+        private final BiFunction<List<Processor>, Object, Object> inputProcess;
+
+        ConditionalOperation(Function<Collection<Object>, Object> process,
+                             BiFunction<List<Processor>, Object, Object> inputProcess) {
+            this.process = process;
+            this.inputProcess = inputProcess;
+        }
+
+        @Override
+        public Object apply(Collection<Object> objects) {
+            return process.apply(objects);
+        }
+
+        Object applyOnInput(List<Processor> processors, Object input) {
+            return inputProcess.apply(processors, input);
+        }
+    }
+    
+    public static final String NAME = "nco";
+
+    private final List<Processor> processors;
+    private final ConditionalOperation operation;
+
+    public ConditionalProcessor(List<Processor> processors, ConditionalOperation operation) {
+        this.processors = processors;
+        this.operation = operation;
+    }
+
+    public ConditionalProcessor(StreamInput in) throws IOException {
+        processors = in.readNamedWriteableList(Processor.class);
+        operation = in.readEnum(ConditionalOperation.class);
+    }
+
+    @Override
+    public String getWriteableName() {
+        return NAME;
+    }
+
+    @Override
+    public void writeTo(StreamOutput out) throws IOException {
+        out.writeNamedWriteableList(processors);
+        out.writeEnum(operation);
+    }
+
+    @Override
+    public Object process(Object input) {
+        return operation.applyOnInput(processors, input);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        ConditionalProcessor that = (ConditionalProcessor) o;
+        return Objects.equals(processors, that.processors) &&
+            operation == that.operation;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(processors, operation);
+    }
+}

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

@@ -0,0 +1,84 @@
+/*
+ * 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.gen.processor.Processor;
+import org.elasticsearch.xpack.sql.expression.predicate.operator.comparison.Comparisons;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.function.BiFunction;
+
+final class Conditionals {
+
+    private Conditionals() {}
+
+    static Object coalesce(Collection<Object> values) {
+        if (values == null || values.isEmpty()) {
+            return null;
+        }
+
+        for (Object object : values) {
+            if (object != null) {
+                return object;
+            }
+        }
+
+        return null;
+    }
+
+    static Object coalesceInput(List<Processor> processors, Object input) {
+        for (Processor proc : processors) {
+            Object result = proc.process(input);
+            if (result != null) {
+                return result;
+            }
+        }
+        return null;
+    }
+
+    static Object greatest(Collection<Object> values) {
+        return extremum(values, Comparisons::gt);
+    }
+
+    static Object greatestInput(Collection<Processor> processors, Object input) {
+        List<Object> values = new ArrayList<>(processors.size());
+        for (Processor processor : processors) {
+            values.add(processor.process(input));
+        }
+        return greatest(values);
+    }
+
+    static Object least(Collection<Object> values) {
+        return extremum(values, Comparisons::lt);
+    }
+
+    static Object leastInput(List<Processor> processors, Object input) {
+        List<Object> values = new ArrayList<>(processors.size());
+        for (Processor processor : processors) {
+            values.add(processor.process(input));
+        }
+        return least(values);
+    }
+
+    private static Object extremum(Collection<Object> values, BiFunction<Object, Object, Boolean> comparison) {
+        if (values == null || values.isEmpty()) {
+            return null;
+        }
+
+        Object result = null;
+        boolean isFirst = true;
+        for (Object value : values) {
+            if (isFirst || (result == null) || (comparison.apply(value, result) == Boolean.TRUE)) {
+                result = value;
+            }
+            isFirst = false;
+        }
+        return result;
+    }
+}

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

@@ -0,0 +1,40 @@
+/*
+ * 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.Foldables;
+import org.elasticsearch.xpack.sql.tree.Location;
+import org.elasticsearch.xpack.sql.tree.NodeInfo;
+
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+
+import static org.elasticsearch.xpack.sql.expression.predicate.conditional.ConditionalProcessor.ConditionalOperation.GREATEST;
+
+public class Greatest extends ArbitraryConditionalFunction {
+
+    public Greatest(Location location, List<Expression> fields) {
+        super(location, new ArrayList<>(new LinkedHashSet<>(fields)), GREATEST);
+    }
+
+    @Override
+    protected NodeInfo<? extends Greatest> info() {
+        return NodeInfo.create(this, Greatest::new, children());
+    }
+
+    @Override
+    public Expression replaceChildren(List<Expression> newChildren) {
+        return new Greatest(location(), newChildren);
+    }
+
+    @Override
+    public Object fold() {
+        return GREATEST.apply(Foldables.valuesOfNoDuplicates(children(), dataType));
+    }
+}

+ 6 - 2
x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/IfNull.java

@@ -19,12 +19,16 @@ import java.util.List;
 public class IfNull extends Coalesce {
 
     public IfNull(Location location, Expression first, Expression second) {
-        super(location, Arrays.asList(first, second));
+        this(location, Arrays.asList(first, second));
+    }
+
+    private IfNull(Location location, List<Expression> expressions) {
+        super(location, expressions);
     }
 
     @Override
     public Expression replaceChildren(List<Expression> newChildren) {
-        return new IfNull(location(), newChildren.get(0), newChildren.get(1));
+        return new IfNull(location(), newChildren);
     }
 
     @Override

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

@@ -0,0 +1,40 @@
+/*
+ * 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.Foldables;
+import org.elasticsearch.xpack.sql.tree.Location;
+import org.elasticsearch.xpack.sql.tree.NodeInfo;
+
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+
+import static org.elasticsearch.xpack.sql.expression.predicate.conditional.ConditionalProcessor.ConditionalOperation.LEAST;
+
+public class Least extends ArbitraryConditionalFunction {
+
+    public Least(Location location, List<Expression> fields) {
+        super(location, new ArrayList<>(new LinkedHashSet<>(fields)), LEAST);
+    }
+
+    @Override
+    protected NodeInfo<? extends Least> info() {
+        return NodeInfo.create(this, Least::new, children());
+    }
+
+    @Override
+    public Expression replaceChildren(List<Expression> newChildren) {
+        return new Least(location(), newChildren);
+    }
+
+    @Override
+    public Object fold() {
+        return LEAST.apply(Foldables.valuesOfNoDuplicates(children(), dataType));
+    }
+}

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

@@ -26,8 +26,6 @@ import static org.elasticsearch.xpack.sql.expression.gen.script.ParamsBuilder.pa
  */
 public class NullIf extends ConditionalFunction {
 
-    private DataType dataType;
-
     public NullIf(Location location, Expression left, Expression right) {
         super(location, Arrays.asList(left, right));
     }

+ 2 - 2
x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/operator/comparison/Comparisons.java

@@ -32,7 +32,7 @@ public final class Comparisons {
         return i == null ? null : i.intValue() != 0;
     }
 
-    static Boolean lt(Object l, Object r) {
+    public static Boolean lt(Object l, Object r) {
         Integer i = compare(l, r);
         return i == null ? null : i.intValue() < 0;
     }
@@ -42,7 +42,7 @@ public final class Comparisons {
         return i == null ? null : i.intValue() <= 0;
     }
 
-    static Boolean gt(Object l, Object r) {
+    public static Boolean gt(Object l, Object r) {
         Integer i = compare(l, r);
         return i == null ? null : i.intValue() > 0;
     }

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

@@ -41,6 +41,7 @@ import org.elasticsearch.xpack.sql.expression.predicate.BinaryPredicate;
 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.ArbitraryConditionalFunction;
 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;
@@ -132,7 +133,7 @@ public class Optimizer extends RuleExecutor<LogicalPlan> {
                 new ReplaceFoldableAttributes(),
                 new FoldNull(),
                 new ConstantFolding(),
-                new SimplifyCoalesce(),
+                new SimplifyConditional(),
                 // boolean
                 new BooleanSimplification(),
                 new BooleanLiteralsOnTheRight(),
@@ -1202,34 +1203,35 @@ public class Optimizer extends RuleExecutor<LogicalPlan> {
         }
     }
     
-    static class SimplifyCoalesce extends OptimizerExpressionRule {
+    static class SimplifyConditional extends OptimizerExpressionRule {
 
-        SimplifyCoalesce() {
+        SimplifyConditional() {
             super(TransformDirection.DOWN);
         }
 
         @Override
         protected Expression rule(Expression e) {
-            if (e instanceof Coalesce) {
-                Coalesce c = (Coalesce) e;
+            if (e instanceof ArbitraryConditionalFunction) {
+                ArbitraryConditionalFunction c = (ArbitraryConditionalFunction) e;
 
-                // find the first non-null foldable child (if any) and remove the rest
-                // while at it, exclude any nulls found
+                // exclude any nulls found
                 List<Expression> newChildren = new ArrayList<>();
-
                 for (Expression child : c.children()) {
                     if (Expressions.isNull(child) == false) {
                         newChildren.add(child);
-                        if (child.foldable()) {
+
+                        // For Coalesce find the first non-null foldable child (if any) and break early
+                        if (e instanceof Coalesce && child.foldable()) {
                             break;
                         }
                     }
                 }
 
                 if (newChildren.size() < c.children().size()) {
-                    return new Coalesce(c.location(), newChildren);
+                    return c.replaceChildren(newChildren);
                 }
             }
+
             return e;
         }
     }

+ 0 - 4
x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/planner/QueryFolder.java

@@ -30,7 +30,6 @@ import org.elasticsearch.xpack.sql.expression.gen.pipeline.AggPathInput;
 import org.elasticsearch.xpack.sql.expression.gen.pipeline.Pipe;
 import org.elasticsearch.xpack.sql.expression.gen.pipeline.UnaryPipe;
 import org.elasticsearch.xpack.sql.expression.gen.processor.Processor;
-import org.elasticsearch.xpack.sql.expression.predicate.operator.comparison.In;
 import org.elasticsearch.xpack.sql.plan.physical.AggregateExec;
 import org.elasticsearch.xpack.sql.plan.physical.EsQueryExec;
 import org.elasticsearch.xpack.sql.plan.physical.FilterExec;
@@ -140,9 +139,6 @@ class QueryFolder extends RuleExecutor<PhysicalPlan> {
                         if (pj instanceof ScalarFunction) {
                             ScalarFunction f = (ScalarFunction) pj;
                             processors.put(f.toAttribute(), Expressions.pipe(f));
-                        } else if (pj instanceof In) {
-                            In in = (In) pj;
-                            processors.put(in.toAttribute(), Expressions.pipe(in));
                         }
                     }
                 }

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

@@ -47,6 +47,8 @@ class org.elasticsearch.xpack.sql.expression.function.scalar.whitelist.InternalS
 # Null
 #
   Object coalesce(java.util.List)
+  Object greatest(java.util.List)
+  Object least(java.util.List)
   Object nullif(Object, Object)
 
 #

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

@@ -37,7 +37,9 @@ 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.Greatest;
 import org.elasticsearch.xpack.sql.expression.predicate.conditional.IfNull;
+import org.elasticsearch.xpack.sql.expression.predicate.conditional.Least;
 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;
@@ -71,7 +73,7 @@ import org.elasticsearch.xpack.sql.optimizer.Optimizer.PropagateEquals;
 import org.elasticsearch.xpack.sql.optimizer.Optimizer.PruneDuplicateFunctions;
 import org.elasticsearch.xpack.sql.optimizer.Optimizer.PruneSubqueryAliases;
 import org.elasticsearch.xpack.sql.optimizer.Optimizer.ReplaceFoldableAttributes;
-import org.elasticsearch.xpack.sql.optimizer.Optimizer.SimplifyCoalesce;
+import org.elasticsearch.xpack.sql.optimizer.Optimizer.SimplifyConditional;
 import org.elasticsearch.xpack.sql.plan.logical.Filter;
 import org.elasticsearch.xpack.sql.plan.logical.LocalRelation;
 import org.elasticsearch.xpack.sql.plan.logical.LogicalPlan;
@@ -420,19 +422,19 @@ public class OptimizerTests extends ESTestCase {
     }
 
     public void testSimplifyCoalesceNulls() {
-        Expression e = new SimplifyCoalesce().rule(new Coalesce(EMPTY, asList(Literal.NULL, Literal.NULL)));
+        Expression e = new SimplifyConditional().rule(new Coalesce(EMPTY, asList(Literal.NULL, Literal.NULL)));
         assertEquals(Coalesce.class, e.getClass());
         assertEquals(0, e.children().size());
     }
 
     public void testSimplifyCoalesceRandomNulls() {
-        Expression e = new SimplifyCoalesce().rule(new Coalesce(EMPTY, randomListOfNulls()));
+        Expression e = new SimplifyConditional().rule(new Coalesce(EMPTY, randomListOfNulls()));
         assertEquals(Coalesce.class, e.getClass());
         assertEquals(0, e.children().size());
     }
 
     public void testSimplifyCoalesceRandomNullsWithValue() {
-        Expression e = new SimplifyCoalesce().rule(new Coalesce(EMPTY,
+        Expression e = new SimplifyConditional().rule(new Coalesce(EMPTY,
                 CollectionUtils.combine(
                         CollectionUtils.combine(randomListOfNulls(), Literal.TRUE, Literal.FALSE, Literal.TRUE),
                         randomListOfNulls())));
@@ -445,7 +447,7 @@ public class OptimizerTests extends ESTestCase {
     }
 
     public void testSimplifyCoalesceFirstLiteral() {
-        Expression e = new SimplifyCoalesce()
+        Expression e = new SimplifyConditional()
                 .rule(new Coalesce(EMPTY,
                         Arrays.asList(Literal.NULL, Literal.TRUE, Literal.FALSE, new Abs(EMPTY, getFieldAttribute()))));
         assertEquals(Coalesce.class, e.getClass());
@@ -454,17 +456,19 @@ public class OptimizerTests extends ESTestCase {
     }
 
     public void testSimplifyIfNullNulls() {
-        Expression e = new SimplifyCoalesce().rule(new IfNull(EMPTY, Literal.NULL, Literal.NULL));
-        assertEquals(Coalesce.class, e.getClass());
+        Expression e = new SimplifyConditional().rule(new IfNull(EMPTY, Literal.NULL, Literal.NULL));
+        assertEquals(IfNull.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 SimplifyConditional().rule(new IfNull(EMPTY, Literal.NULL, ONE));
+        assertEquals(IfNull.class, e.getClass());
         assertEquals(1, e.children().size());
         assertEquals(ONE, e.children().get(0));
 
-        e = new SimplifyCoalesce().rule(new IfNull(EMPTY, ONE, Literal.NULL));
+        e = new SimplifyConditional().rule(new IfNull(EMPTY, ONE, Literal.NULL));
+        assertEquals(IfNull.class, e.getClass());
         assertEquals(1, e.children().size());
         assertEquals(ONE, e.children().get(0));
     }
@@ -475,6 +479,48 @@ public class OptimizerTests extends ESTestCase {
         assertEquals(orig, f);
     }
 
+    public void testSimplifyGreatestNulls() {
+        Expression e = new SimplifyConditional().rule(new Greatest(EMPTY, asList(Literal.NULL, Literal.NULL)));
+        assertEquals(Greatest.class, e.getClass());
+        assertEquals(0, e.children().size());
+    }
+
+    public void testSimplifyGreatestRandomNulls() {
+        Expression e = new SimplifyConditional().rule(new Greatest(EMPTY, randomListOfNulls()));
+        assertEquals(Greatest.class, e.getClass());
+        assertEquals(0, e.children().size());
+    }
+
+    public void testSimplifyGreatestRandomNullsWithValue() {
+        Expression e = new SimplifyConditional().rule(new Greatest(EMPTY,
+            CollectionUtils.combine(CollectionUtils.combine(randomListOfNulls(), ONE, TWO, ONE), randomListOfNulls())));
+        assertEquals(Greatest.class, e.getClass());
+        assertEquals(2, e.children().size());
+        assertEquals(ONE, e.children().get(0));
+        assertEquals(TWO, e.children().get(1));
+    }
+
+    public void testSimplifyLeastNulls() {
+        Expression e = new SimplifyConditional().rule(new Least(EMPTY, asList(Literal.NULL, Literal.NULL)));
+        assertEquals(Least.class, e.getClass());
+        assertEquals(0, e.children().size());
+    }
+
+    public void testSimplifyLeastRandomNulls() {
+        Expression e = new SimplifyConditional().rule(new Least(EMPTY, randomListOfNulls()));
+        assertEquals(Least.class, e.getClass());
+        assertEquals(0, e.children().size());
+    }
+
+    public void testSimplifyLeastRandomNullsWithValue() {
+        Expression e = new SimplifyConditional().rule(new Least(EMPTY,
+            CollectionUtils.combine(CollectionUtils.combine(randomListOfNulls(), ONE, TWO, ONE), randomListOfNulls())));
+        assertEquals(Least.class, e.getClass());
+        assertEquals(2, e.children().size());
+        assertEquals(ONE, e.children().get(0));
+        assertEquals(TWO, e.children().get(1));
+    }
+
     //
     // Logical simplifications
     //